성능
원칙: 측정하지 않은 성능 우위는 주장하지 않으며, 차이가 측정 잡음 안에 있으면 "개선"이나 "저하"라고 쓰지 않습니다.
측정 1 — 게임 테스트 서버 시작~종료 시간 (2026-10-19)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmark |
| 시나리오 | Mojang 게임 테스트 서버, 새 월드 생성 → minecraft:always_pass → 저장 → 종료 |
| 비교 | 바닐라(게임 jar를 클래스 경로에 두고 Poly 모드 로더 없이 실행) 대 Poly 모드 로더(개발용 모드 4개, 변환 규칙 4개, 데워진 변환 캐시) |
| 반복 | 각 5회, 1회 예열은 버림. 바닐라와 Poly 모드 로더를 번갈아 실행, 실행마다 새 디렉터리 |
| 환경 | Windows 11, JDK 25.0.3, Minecraft 26.2, Poly 모드 로더 0.1.0-alpha.1 |
| 변형 | 중앙값(초) | 표준편차(초) | 개별 실행(초) |
|---|---|---|---|
| 바닐라 | 7.137 | 0.122 | 6.99, 7.25, 6.96, 7.16, 7.14 |
| Poly Mod Loader | 7.225 | 0.312 | 7.84, 7.03, 7.23, 7.17, 7.33 |
결론: 중앙값 차이는 +0.09초(+1.2%)로, Poly 모드 로더 측 표준편차(0.31초) 안에 있습니다. 이 시나리오에서는 Poly 모드 로더 부트스트랩·검색·변환 계획의 비용을 측정 잡음과 구분할 수 없습니다. 성능 개선은 주장하지 않습니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-19-startup.json에 있습니다.
측정 2 — 같은 부하에서 서버 틱 시간·힙·GC (2026-10-21)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:tickBenchmark (약 7분, 창 없음) |
| 시나리오 | 게임 테스트 서버에서 데이터 팩 테스트가 1,200틱을 기다리는 동안 돼지 40마리 유지, 매 틱 16×16×16 블록 채우기·비우기. 데이터 팩이라 바닐라에서도 같은 일을 함 |
| 비교 | 바닐라 대 Poly 모드 로더(개발용 모드 5개, 변환 규칙 32개, 접근 확대 1개) |
| 측정 도구 | 틱마다 시간을 재는 에이전트 tools/tick-probe와 Java Flight Recorder를 양쪽에 똑같이 붙임. 실행마다 처음 200틱은 버림 |
| 반복 | 각 5회, 예열 1회는 버림, 번갈아 실행 |
| 환경 | Intel Core i9-14900HX, RAM 32 GB, Windows 11, JDK 25.0.3(G1, 기본 힙), Minecraft 26.2 |
실행 5회의 중앙값(괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 |
|---|---|---|
| 틱 p50 (ms) | 18.13 (±0.97) | 18.20 (±2.25) |
| 틱 p95 (ms) | 22.10 (±1.48) | 21.55 (±1.73) |
| 틱 p99 (ms) | 53.1 (±16.0) | 46.6 (±26.5) |
| 50ms를 넘은 틱 (%) | 1.10 | 0.99 |
| 피크 힙 사용량 (MiB) | 1,386 (±1,141) | 1,621 (±420) |
| GC 정지 합계 (ms) | 1,263 | 1,227 |
결론: 모든 지표의 차이가 실행 간 표준편차보다 작아 "유의한 차이 없음"입니다. 개선도 저하도 주장하지 않습니다. 피크 힙은 G1의 힙 크기 결정에 따라 실행마다 642~3,597 MiB로 흔들려, 이 측정으로는 메모리 차이를 판단할 수 없습니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-21-ticks.json에 있습니다.
측정 3 — 힙을 고정한 서버 틱 시간·힙·GC (2026-10-21)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:tickBenchmark -PtickBenchmarkHeap=2g |
| 시나리오 | 측정 2와 같지만 정육면체를 유리로 채움. 돌로 채우면 조명 계산 대기열이 실행 내내 커져서 힙 수치가 로더가 아니라 조명 엔진의 사용량을 반영했음 |
| 힙 | 양쪽 모두 -Xms2g -Xmx2g. GC 직후에 남은 힙의 최댓값도 기록 |
| 그 밖 | 측정 2와 같음 |
실행 5회의 중앙값(괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 |
|---|---|---|
| 틱 p50 (ms) | 12.97 (±2.23) | 12.25 (±1.44) |
| 틱 p95 (ms) | 15.60 (±2.94) | 14.48 (±1.01) |
| 틱 p99 (ms) | 18.12 (±3.25) | 15.85 (±0.94) |
| GC 직후 힙 최댓값 (MiB) | 158.1 (±12.7) | 157.4 (±10.7) |
| GC 횟수 | 18 | 18 |
| GC 정지 합계 (ms) | 365.3 (±62.5) | 368.3 (±56.6) |
결론: 힙을 고정하자 메모리를 비교할 수 있게 됐고, GC 직후 힙·GC 횟수·정지 시간 모두 바닐라와 구분할 수 없습니다. 틱 시간은 Poly 모드 로더 중앙값이 조금 낮지만 바닐라 쪽 표준편차 안이라 개선으로 주장하지 않습니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-21-ticks-fixed-heap.json에 있습니다.
측정 4 — 클라이언트 프레임 시간 (2026-10-21)
frameBenchmark: 저장된 월드를 클라이언트로 열고 30초 동안 모든 프레임(Minecraft.runTick)을 잽니다. vsync 끔, 프레임 제한 없음, 각 5회. Poly 모드 로더 쪽에는 매 프레임 그리기 작업을 하는 개발용 모드(HUD, 월드 위 그리기, 셰이더)가 들어 있습니다.
| 지표 | 바닐라 | Poly 모드 로더 + 개발용 모드 |
|---|---|---|
| 평균 FPS | 1,331 (±163) | 1,163 (±170) |
| 프레임 p50 (ms) | 0.656 (±0.052) | 0.800 (±0.075) |
| 프레임 p95 (ms) | 1.545 (±0.457) | 1.083 (±0.693) |
| 프레임 p99 (ms) | 2.070 (±0.579) | 1.962 (±0.747) |
결론: p50이 0.14ms 늘었으므로 측정된 저하로 기록합니다(60 FPS 프레임 예산의 0.9%). 로더 비용과 모드의 그리기 비용은 이 측정으로 나눌 수 없습니다. p95·p99와 평균 FPS의 차이는 실행 간 편차 안입니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-21-frames.json에 있습니다.
측정 5 — 실제 Fabric 모드 묶음의 시작 시간과 서버 틱 (2026-10-22)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmarkFabric, :poly-bootstrap:tickBenchmarkFabric -PtickBenchmarkHeap=4g |
| 시나리오 | 시작 시간은 측정 1, 틱은 측정 3과 같습니다(유리 정육면체, 양쪽 모두 힙 4 GiB 고정) |
| 비교 | 바닐라(모드 없음) 대 Poly 모드 로더 + 26.2 호환성 묶음의 Fabric 모드. jar 41개이고, 이 서버에서 안에 든 jar를 포함해 Fabric 모드 97개와 Mixin 설정 71개가 로드됩니다 |
| 비교하지 않은 것 | 같은 모드를 Fabric Loader로 실행한 경우. 그래서 이 측정은 Poly 모드 로더가 Fabric Loader보다 빠른지 느린지 말하지 않습니다. 모드 묶음을 Poly 모드 로더로 돌렸을 때 바닐라와 얼마나 다른지만 보여 줍니다 |
| 지표 | 바닐라 | Poly 모드 로더 + Fabric 모드 |
|---|---|---|
| 시작 시간 중앙값 (초) | 6.03 (±0.23) | 15.01 (±0.64) |
| 틱 p50 (ms) | 7.91 (±0.32) | 4.04 (±0.09) |
| 틱 p95 (ms) | 13.29 (±0.56) | 6.83 (±0.10) |
| 틱 p99 (ms) | 15.16 (±0.61) | 8.81 (±0.62) |
| GC 직후 힙 최댓값 (MiB) | 141.6 (±6.5) | 182.9 (±1.3) |
| GC 횟수 | 12 | 16 |
| GC 정지 합계 (ms) | 168.8 (±7.9) | 250.9 (±16.3) |
결론:
- 모드 묶음과 함께 시작이 9.0초 늘었고, GC 직후 힙이 41 MiB 많습니다. 둘 다 실행 간 편차보다 훨씬 커서 측정된 비용으로 기록합니다. 모드 97개의 초기화, 링크 검사, Mixin 설정 71개 적용이 모두 들어 있고, 이 측정으로 로더 몫과 모드 몫을 나눌 수 없습니다.
- 틱 시간은 바닐라의 약 절반입니다. Poly 모드 로더의 개선이 아닙니다. 묶음에 서버 최적화 모드 Lithium과 FerriteCore가 들어 있고, 이 서버에서 두 모드의 Mixin 설정이 로드됐으며 Mixin 적용 실패는 기록되지 않았습니다. Lithium을 뺀 변형은 돌리지 않았으므로 차이를 특정 모드 덕분이라고 하지 않습니다. 말할 수 있는 것은 "Lithium 같은 최적화 모드가 Poly 모드 로더에서도 적용되고, 이 부하에서 틱이 바닐라보다 짧았다"까지입니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-22-startup-fabric.json, docs/benchmarks/2026-10-22-ticks-fabric.json에 있습니다.
측정 6 — 실제 Forge 모드 묶음의 시작 시간과 서버 틱 (2026-10-23)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmarkForge, :poly-bootstrap:tickBenchmarkForge -PtickBenchmarkHeap=4g |
| 시나리오 | 측정 5와 같습니다. 시작 시간은 측정 1, 틱은 측정 3과 같습니다(유리 정육면체, 양쪽 모두 힙 4 GiB 고정) |
| 비교 | 바닐라(모드 없음) 대 Poly 모드 로더 + Forge 65.1.5 + 26.2 호환성 묶음의 Forge 모드. corpusForge가 게임 테스트 서버에 넣는 jar 그대로이며, 38개 중 32개가 실행됩니다(나머지 6개는 서버의 클라이언트 전용, POLY-COMPAT-209). 모든 실행에서 "Forge mod loading, version 65.1.5"를 확인했습니다 |
| 비교하지 않은 것 | 같은 모드를 Forge 자체 실행기로 실행한 경우. 그래서 이 측정은 Poly 모드 로더가 Forge보다 빠른지 느린지 말하지 않습니다. Forge와 모드 묶음을 Poly 모드 로더로 돌렸을 때 바닐라와 얼마나 다른지만 보여 줍니다 |
| 반복·틱 측정·환경 | 측정 3과 같습니다(Poly 모드 로더 0.1.0-alpha.18, 커밋 e1756cd 이후 + libIPN 고정) |
시작 시간(실행 5회, 초):
| 변형 | 중앙값 | 표준편차 | 개별 실행 |
|---|---|---|---|
| 바닐라 | 6.281 | 0.090 | 6.18, 6.38, 6.40, 6.28, 6.24 |
| Poly 모드 로더 + Forge 모드 | 22.864 | 0.865 | 21.36, 23.25, 22.86, 23.33, 21.93 |
서버 틱(실행 5회의 중앙값, 괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 + Forge 모드 |
|---|---|---|
| 실행당 측정 틱 수 | 1,006 | 1,007 |
| 틱 p50 (ms) | 8.13 (±0.21) | 12.49 (±0.31) |
| 틱 p95 (ms) | 9.81 (±0.40) | 15.08 (±4.21) |
| 틱 p99 (ms) | 12.79 (±0.93) | 20.93 (±9.10) |
| 가장 긴 틱 (ms) | 47.4 | 60.9 |
| 50ms를 넘은 틱 (%) | 0.00 | 0.70 |
| GC 직후 힙 최댓값 (MiB) | 139.2 (±2.2) | 267.3 (±2.4) |
| GC 횟수 | 12 | 21 |
| GC 정지 합계 (ms) | 185.0 (±17.9) | 564.8 (±138.5) |
| 가장 긴 GC 정지 (ms) | 39.0 | 61.2 |
결론:
- 시작 시간이 16.6초(+264%) 늘었습니다. 모든 Poly 모드 로더 실행이 모든 바닐라 실행보다 15초 이상 길어서 측정된 비용으로 기록합니다. 여기에는 Forge 파일 확인, 패치된 게임 jar, FML의 모드 탐색·레이어, Mixin, 모드 32개의 생성과 설정 파일 쓰기가 모두 들어 있습니다. 이 측정으로는 Poly 모드 로더 몫, Forge 몫, 모드 몫을 나눌 수 없습니다. 같은 세션의
startupBenchmark(개발용 모드만)는 바닐라 6.24초, Poly 모드 로더 6.98초였습니다. - 메모리: GC 직후 힙이 128 MiB(+92%) 많고, GC는 9번, 정지 합계는 380ms 더 많습니다. 차이가 양쪽 표준편차보다 훨씬 커서 Forge와 모드 묶음의 메모리 비용으로 기록합니다.
- 틱 시간: p50이 4.4ms(+54%) 깁니다. 다섯 실행 모두 Poly 모드 로더 쪽이 12.25
13.06ms, 바닐라가 7.838.36ms로 겹치지 않아 측정된 비용으로 기록합니다. 다만 이 PC는 같은 세션에서 실행마다 p50이 약 8ms와 약 12.3ms 두 무리로 갈렸습니다. 같은 세션의tickBenchmark(개발용 모드만, 같은 4 GiB 힙)는 바닐라 8.38·8.92·8.82·8.59·12.30ms, Poly 모드 로더 7.70·12.47·7.43·12.53·12.29ms였습니다. 그래서 Forge 쪽 실행이 모두 느린 무리에 든 것이 기계 상태 때문인지는 이 측정으로 가릴 수 없습니다. Forge 이벤트 버스와 모드의 틱 처리 비용은 따로 재지 않았습니다. - p95·p99는 Poly 모드 로더 쪽 표준편차가 커서(한 실행의 가장 긴 틱 118ms, 같은 실행의 가장 긴 GC 정지 99.9ms) 중앙값만 비교합니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-23-startup-forge.json, docs/benchmarks/2026-10-23-ticks-forge.json에 있습니다.
측정 7 — 실제 Quilt 모드 묶음의 시작 시간과 서버 틱 (2026-10-24)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmarkQuilt, :poly-bootstrap:tickBenchmarkQuilt -PtickBenchmarkHeap=4g |
| 시나리오 | 측정 5·6과 같습니다. 시작 시간은 측정 1, 틱은 측정 3과 같습니다(유리 정육면체, 양쪽 모두 힙 4 GiB 고정) |
| 비교 | 바닐라(모드 없음) 대 Poly 모드 로더 + 26.2 Quilt 묶음(Quilt 전용 판 114개와 필수 모드 3개, corpusQuilt가 게임 테스트 서버에 넣는 jar)과 테스트용 하네스. 외부 모드 132개가 로드됩니다(Quilt 77개, Fabric 55개, 중첩 포함) |
| 묶음에서 뺀 것(벤치마크에서만) | Craftable Bucket of Sulfur Cube와 그 Easy 판: minecraft:tick에 없는 함수 sulfur:tick을 적어 게임이 minecraft:tick 태그 전체를 거부하므로 벤치마크 데이터 팩의 틱 함수도 돌지 않습니다(바닐라 TagLoader). Omni: 공용 Mixin이 클라이언트 클래스 LocalPlayer를 instanceof로 검사해, 몹이 뛰는 순간 서버가 NoClassDefFoundError로 멈춥니다(바이트코드 확인, Quilt Loader에서도 같음). 처음 실행은 이 셋을 넣은 채여서 틱 함수가 돌지 않았고(Poly 모드 로더 쪽 p50 0.06ms), 그 결과는 버렸습니다 |
| 비교하지 않은 것 | 같은 모드를 실제 Quilt Loader로 실행한 경우. 그래서 이 측정은 Poly 모드 로더가 Quilt Loader보다 빠른지 느린지 말하지 않습니다 |
| 반복·틱 측정·환경 | 측정 3과 같습니다(Poly 모드 로더 0.2.0-beta.2 + 그 작업의 변경). 같은 PC에서 다른 작업(NeoForge 게임 실행)이 함께 돌고 있었습니다. 바닐라와 Poly 모드 로더를 번갈아 실행하므로 둘 다 같은 조건이지만 절대값은 평소보다 흔들릴 수 있습니다 |
시작 시간(실행 5회, 초):
| 변형 | 중앙값 | 표준편차 | 개별 실행 |
|---|---|---|---|
| 바닐라 | 6.539 | 0.450 | 6.56, 6.28, 6.54, 7.31, 6.15 |
| Poly 모드 로더 + Quilt 묶음 | 12.873 | 0.757 | 14.26, 12.36, 13.07, 12.87, 12.49 |
서버 틱(실행 5회의 중앙값, 괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 + Quilt 묶음 |
|---|---|---|
| 실행당 측정 틱 수 | 1,006 | 1,006 |
| 틱 p50 (ms) | 8.40 (±0.35) | 7.67 (±0.05) |
| 틱 p95 (ms) | 10.05 (±0.75) | 9.27 (±0.13) |
| 틱 p99 (ms) | 13.52 (±1.28) | 12.37 (±1.27) |
| 가장 긴 틱 (ms) | 47.3 | 69.6 |
| 50ms를 넘은 틱 (%) | 0.00 | 0.30 |
| GC 직후 힙 최댓값 (MiB) | 139.4 (±1.1) | 193.3 (±1.5) |
| GC 횟수 | 12 | 15 |
| GC 정지 합계 (ms) | 198.9 (±15.7) | 291.3 (±20.8) |
| 가장 긴 GC 정지 (ms) | 38.9 | 60.7 |
결론:
- 시작 시간이 6.3초(+97%) 늘었습니다. 모든 Poly 모드 로더 실행이 모든 바닐라 실행보다 5초 이상 길어서 측정된 비용으로 기록합니다. 모드 132개의 판정·링크 검사, Mixin 설정, Fabric API 초기화, 데이터 팩 65개 이상 읽기가 모두 들어 있어 나누지 않았습니다.
- 메모리: GC 직후 힙이 54 MiB(+39%) 많고, GC는 3번, 정지 합계는 92ms 더 많습니다. 차이가 표준편차보다 훨씬 커서 Quilt 묶음의 메모리 비용으로 기록합니다.
- 틱 시간: p50이 Poly 모드 로더 쪽이 0.73ms 짧습니다. 다섯 실행 모두 Poly 모드 로더 쪽이 7.61
7.75ms, 바닐라가 7.868.80ms입니다. 그러나 개선이라고 주장하지 않습니다. 측정 6처럼 이 PC는 같은 세션 안에서도 무리가 갈리고, 이번에는 다른 게임 실행이 함께 돌았습니다. 묶음의 어떤 모드(RailOptimization 등)가 틱을 줄이는지도 따로 재지 않았습니다. 가장 긴 틱과 50ms를 넘은 틱은 Poly 모드 로더 쪽이 많고, 가장 긴 GC 정지와 함께 늘었습니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-24-startup-quilt.json, docs/benchmarks/2026-10-24-ticks-quilt.json에 있습니다.
측정 8 — 실제 NeoForge 모드 묶음의 시작 시간과 서버 틱 (2026-10-24)
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmarkNeoForge, :poly-bootstrap:tickBenchmarkNeoForge -PtickBenchmarkHeap=4g |
| 시나리오 | 측정 6과 같습니다. 시작 시간은 측정 1, 틱은 측정 3과 같습니다(유리 정육면체, 양쪽 모두 힙 4 GiB 고정) |
| 비교 | 바닐라(모드 없음) 대 Poly 모드 로더 + NeoForge 26.2.0.93 + 26.2 호환성 묶음의 NeoForge 모드. corpusNeoForge가 게임 테스트 서버에 넣는 jar 그대로입니다(묶음 36개 + 게임 테스트 하네스, FML이 49개 모드 실행). Poly 모드 로더 쪽은 설치한 프로필처럼 Poly 모드 로더 Java agent로 시작합니다 |
| 비교하지 않은 것 | 같은 모드를 NeoForge 자체 실행기로 실행한 경우. 그래서 이 측정은 Poly 모드 로더가 NeoForge보다 빠른지 느린지 말하지 않습니다. NeoForge와 모드 묶음을 Poly 모드 로더로 돌렸을 때 바닐라와 얼마나 다른지만 보여 줍니다 |
| 반복·틱 측정·환경 | 측정 3과 같습니다(커밋 2ee8d3a). 같은 PC에서 다른 작업이 함께 돌았을 수 있습니다 |
시작 시간(실행 5회, 초):
| 변형 | 중앙값 | 표준편차 | 개별 실행 |
|---|---|---|---|
| 바닐라 | 7.577 | 0.637 | 6.51, 6.48, 7.58, 7.69, 7.70 |
| Poly 모드 로더 + NeoForge 모드 | 21.288 | 1.891 | 17.92, 18.11, 21.29, 21.55, 21.54 |
서버 틱(실행 5회의 중앙값, 괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 + NeoForge 모드 |
|---|---|---|
| 실행당 측정 틱 수 | 1,007 | 1,007 |
| 틱 p50 (ms) | 9.67 (±0.28) | 4.80 (±0.86) |
| 틱 p95 (ms) | 16.36 (±2.28) | 6.36 (±1.55) |
| 틱 p99 (ms) | 18.55 (±2.26) | 8.98 (±1.86) |
| 가장 긴 틱 (ms) | 42.8 | 63.2 |
| 50ms를 넘은 틱 (%) | 0.00 | 0.10 |
| GC 직후 힙 최댓값 (MiB) | 141.3 (±3.8) | 225.3 (±1.0) |
| GC 횟수 | 12 | 14 |
| GC 정지 합계 (ms) | 173.7 (±7.1) | 282.6 (±11.9) |
| 가장 긴 GC 정지 (ms) | 30.3 | 57.0 |
결론:
- 시작 시간이 13.7초(+181%) 늘었습니다. 모든 Poly 모드 로더 실행이 모든 바닐라 실행보다 10초 이상 길어서 측정된 비용으로 기록합니다. NeoForge 파일 확인, 패치된 게임 jar 확인, FML의 모드 탐색·레이어·처리기, Mixin, 모드 49개의 생성과 설정 파일 쓰기가 모두 들어 있어 몫을 나눌 수 없습니다. 바닐라·Poly 모드 로더 양쪽이 실행 순서에 따라 두 무리(바닐라 약 6.5초·7.6초, Poly 모드 로더 약 18초·21.5초)로 갈렸습니다. 기계 상태 때문으로 보이지만 이 측정으로는 가릴 수 없습니다.
- 틱 시간: Poly 모드 로더 쪽 p50이 4.9ms(−50%) 짧습니다. 다섯 실행 모두 Poly 모드 로더 쪽이 4.51
6.62ms, 바닐라가 9.4610.10ms로 겹치지 않습니다. 이 묶음에는 서버 틱을 줄이는 Lithium과 FerriteCore가 들어 있으므로, 이 차이는 그 모드들의 효과로 보고 Poly 모드 로더나 NeoForge의 성능 우위로 주장하지 않습니다. 모드별로 나눠 재지 않았습니다. - 메모리: GC 직후 힙이 84 MiB(+59%) 많고, GC는 2번, 정지 합계는 109ms 더 많습니다. 차이가 양쪽 표준편차보다 훨씬 커서 NeoForge와 모드 묶음의 메모리 비용으로 기록합니다.
- 가장 긴 틱과 가장 긴 GC 정지는 Poly 모드 로더 쪽이 깁니다(실행마다 52~66ms). 한 번씩 있는 GC 정지로 보여 중앙값만 비교합니다.
- 첫 틱 측정 시도(같은 날)에서 Poly 모드 로더 쪽 마지막 실행 하나가 모드 생성 중 멈춰 시간 초과로 끝났습니다. 같은 멈춤이 뒤의
corpusNeoForge(26.1.2)에서 다시 나타나 스레드 덤프를 얻었습니다. Kotlin 모드 둘이 병렬로 생성될 때 게임 클래스로더와 FML 플러그인 클래스로더 사이에 생긴 교착이었고, 고쳤습니다. 위 수치는 고치기 전에 잰 것이며, 고친 코드는 클래스를 찾는 동안 잠금을 덜 쥘 뿐 같은 일을 합니다.
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-24-startup-neoforge.json, docs/benchmarks/2026-10-24-ticks-neoforge.json에 있습니다.
측정 9 — 같은 Fabric 모드로 Poly 모드 로더 대 Sinytra Connector (2026-10-24)
측정 5~8과 달리 다른 로더 구성과 직접 비교한 첫 측정입니다. 같은 Fabric 모드를 Poly 모드 로더와, 실제 NeoForge 위의 Sinytra Connector로 각각 실행했습니다.
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:startupBenchmarkConnector -PtickBenchmarkHeap=4g, :poly-bootstrap:tickBenchmarkConnector -PtickBenchmarkHeap=4g |
| 변형 | ① 바닐라(모드 없음) ② Poly 모드 로더 + 공통 Fabric 모드 + Fabric API ③ NeoForge 26.2.0.93 + Sinytra Connector + 공통 Fabric 모드 + Forgified Fabric API. 세 변형을 같은 세션에서 번갈아 실행합니다(각 1회 예열은 버림, 측정 5회, 실행마다 새 디렉터리) |
| Connector 쪽 설치 | NeoForge 설치 프로그램(neoforge-26.2.0.93-installer.jar, SHA-1 97205829a020fc9389f504a77f590b54157c8dde)의 --installServer로 만든 전용 서버. Poly 모드 로더는 관여하지 않습니다. Connector README가 필수로 적은 모드를 Modrinth에서 SHA-512로 고정해 받습니다: Connector 3.1.0-beta.3+26.2, Forgified Fabric API 0.161.0+26.2+4.0.2, Launchpad 1.10.2+26.2. 선택 모드인 Connector Extras는 넣지 않았습니다 |
| 시작 방법 | Connector 쪽은 NeoForge가 설치한 인수 파일(user_jvm_args.txt, win_args.txt)을 그대로 쓰고, 진입점만 FML의 net.neoforged.fml.startup.Server 대신 같은 jar의 net.neoforged.fml.startup.GameTestServer(Mojang 게임 테스트 서버)입니다. EULA는 묻지 않으며 동의하지 않았습니다 |
| 같게 맞춘 것 | 같은 JDK, 같은 힙(-Xms4g -Xmx4g, 시작 시간도), 같은 틱 측정 agent·JFR 설정·데이터 팩(측정 3의 유리 정육면체, 1,200틱, 처음 200틱 버림), 같은 log4j 설정(NeoForge 기본 설정은 디버그 로그 파일을 더 쓰므로 바꿨습니다). Connector는 처음 시작할 때 Fabric jar를 변환해 게임 폴더 .cache/에 둡니다. 예열 실행의 .cache/를 측정 실행마다 복사해, Poly 모드 로더가 공유 변환 캐시를 쓰는 것처럼 두 번째 시작 상태로 잽니다 |
| 각자의 대응물 | Fabric API(Poly 모드 로더) ↔ Forgified Fabric API(Connector). 게임 테스트 하네스도 각자의 것입니다. Poly 쪽은 fabric-gametest-harness(Fabric API가 레지스트리를 전용 서버 main에서만 동결하므로 같은 일을 함), Connector 쪽은 neoforge-gametest-harness(실제 NeoForge는 배포 환경에서 게임 테스트를 틱하지 않음) |
| 시작 시간 지표 | 시작GameTestRunner의 "Running test environment"가 처음 나온 때(월드 생성 뒤, 첫 테스트 전) |
| 환경 | Intel Core i9-14900HX, RAM 31.7 GiB, Windows 11 Pro 10.0.26200, JDK 25.0.3, G1, Minecraft 26.2, Poly 모드 로더 1.0.0(커밋 2f212b5), Fabric API 0.161.0+26.2. 측정 전과 측정 중 15초마다 다른 게임 JVM을 확인했고 한 번도 없었습니다(시작 14회, 틱 29회 확인, 모두 조용함) |
공통 모드 묶음. 먼저 26.2 호환성 묶음의 Fabric jar 전체(게임 테스트 서버에서 빠지는 Xaero 둘 제외)를 양쪽에서 실행하고, 양쪽에서 실제로 로드된 것만 남겼습니다. 공통 19개: AppleSkin, Cloth Config, Collective, EntityCulling, Fabric Language Kotlin, FancyMenu, FerriteCore, GeckoLib, Jade, JEI, Konkrete, Melody, MezzConfig, NotEnoughAnimations, Placeholder API, 3D Skin Layers, Veinminer, Simple Voice Chat, YetAnotherConfigLib. 매 실행에서 Poly 모드 로더는 "Foreign mods: 89 loaded"(중첩 jar 포함)를 남기고, FML은 위 모드를 모두 모드 목록에 올려야 통과합니다(작업이 검사).
| 빠진 모드 | 빠진 쪽 | 이유 |
|---|---|---|
| Architectury 21.1.12 | Connector | Mixin MixinBaseSpawner.checkSpawnerSpawn이 주입 검사에 실패(NeoForge가 패치한 메서드). 서버가 멈춤 |
| Balm 26.2.0.9 | Connector | Mixin LivingEntityMixin.clearAllEffects가 주입 검사에 실패. 서버가 멈춤 |
| Lithium 0.25.3 | Connector | Mixin block.hopper.LevelMixin이 주입 검사에 실패. 서버가 멈춤 |
| Puzzles Lib 26.2.5 | Connector | Connector의 jar 변환이 Mixin 클래스 하나에서 멈춤("Error parsing property 'slice'", ClassCastException) |
| Forge Config API Port 26.2.1 (Fabric 판) | Connector | jar 안의 net.neoforged.fml.config 클래스가 NeoForge 자신의 것과 겹쳐 LinkageError(loader constraint violation) |
| Zoomify 2.17.0 | Connector | 여러 로더용 jar의 neoforge.mods.toml을 NeoForge가 읽고, 없는 언어 공급자 klf를 요구. 클라이언트 모드 |
| LambDynamicLights 4.12.4 (+ 중첩 라이브러리) | Poly 모드 로더 | environment: client라 서버에서 로드하지 않음(POLY-COMPAT-108, Fabric Loader와 같음). Connector는 서버에서도 로드함 |
| Sodium, Iris, Continuity 등 클라이언트 전용 13개 | 양쪽 | 서버에서 로드하지 않음(Connector는 Dynamic FPS·MoreCulling 안에 든 공통 라이브러리 일부는 로드) |
위 6개는 Poly 모드 로더에서 로드되므로(측정 5) Connector가 못 돌리는 모드가 있다는 것은 호환성 차이이지 이 측정의 속도 결과가 아닙니다. 특히 Lithium이 빠졌으므로 측정 5의 틱 단축은 여기에 없습니다.
시작 시간(실행 5회, 초):
| 변형 | 종료까지 중앙값 (±표준편차) | 개별 실행 | 준비까지 중앙값 (±표준편차) | 개별 실행 |
|---|---|---|---|---|
| 바닐라 | 6.562 (±0.172) | 6.30, 6.56, 6.77, 6.56, 6.63 | 5.295 (±0.067) | 5.24, 5.28, 5.39, 5.30, 5.38 |
| Poly 모드 로더 + 공통 모드 | 12.154 (±0.147) | 12.33, 11.99, 12.15, 11.99, 12.19 | 10.722 (±0.140) | 10.96, 10.72, 10.83, 10.61, 10.66 |
| NeoForge + Connector + 공통 모드 | 11.826 (±0.653) | 10.93, 11.83, 11.17, 12.37, 12.31 | 10.232 (±0.622) | 9.46, 10.23, 9.63, 10.75, 10.81 |
서버 틱(실행 5회의 중앙값, 괄호 안은 실행 간 표준편차):
| 지표 | 바닐라 | Poly 모드 로더 + 공통 모드 | NeoForge + Connector + 공통 모드 |
|---|---|---|---|
| 실행당 측정 틱 수 | 1,007 | 1,007 | 1,007 |
| 틱 p50 (ms) | 8.63 (±0.18) | 8.47 (±0.26) | 8.48 (±0.17) |
| 틱 p95 (ms) | 10.57 (±0.45) | 10.91 (±0.34) | 9.94 (±1.02) |
| 틱 p99 (ms) | 13.16 (±0.48) | 13.97 (±0.97) | 12.51 (±2.84) |
| 가장 긴 틱 (ms) | 57.2 | 61.8 | 57.3 |
| 50ms를 넘은 틱 (%) | 0.10 | 0.20 | 0.10 |
| GC 직후 힙 최댓값 (MiB) | 138.4 (±0.9) | 173.1 (±1.1) | 203.0 (±2.7) |
| GC 횟수(시작 포함 실행 전체) | 12 | 16 | 15 |
| GC 정지 합계 (ms) | 192.9 (±17.7) | 282.3 (±34.0) | 293.5 (±36.7) |
| 가장 긴 GC 정지 (ms) | 45.2 | 49.5 | 78.9 |
결론:
- 메모리: Poly 모드 로더가 적게 씁니다. GC 직후 힙이 Connector보다 29.9 MiB(−15%) 적습니다. 다섯 실행 모두 Poly 모드 로더 171
174 MiB, Connector 201207 MiB로 겹치지 않고 차이가 표준편차보다 훨씬 커서 측정된 차이로 기록합니다. 바닐라 대비 추가분은 Poly 모드 로더 +34.7 MiB, Connector +64.6 MiB입니다. 다만 Connector 쪽에는 NeoForge 자체가 들어 있으므로, 이 차이를 "Connector 계층"과 "NeoForge"로 나누지 않았습니다. 가장 긴 GC 정지도 Connector 쪽이 깁니다(실행마다 61114ms 대 4460ms). - 틱 p50: 차이 없음. 8.47 대 8.48ms로 같고 바닐라(8.63ms)와도 표준편차 안입니다.
- 틱 p95·p99: Poly 모드 로더가 이기지 못했습니다. p95 중앙값은 Connector가 0.98ms 낮고, Connector 다섯 실행 중 넷(9.67
10.02ms)이 Poly 모드 로더의 모든 실행(10.1811.02ms)보다 낮았습니다. 그러나 Connector의 한 실행(12.12ms) 때문에 표준편차(1.02ms)가 차이보다 커서 규칙상 유의한 차이 없음입니다. p99도 같습니다(차이 1.46ms, Connector 표준편차 2.84ms). Poly 모드 로더의 p95(10.91ms)는 바닐라(10.57ms)보다도 높지만 표준편차 안입니다. 개선할 점으로 기록합니다. 이 묶음에서 Poly 모드 로더 쪽 꼬리 지연이 바닐라·Connector보다 낮지 않습니다. 원인은 여기서 재지 않았습니다(측정 10에서 조사). - GC 횟수: Poly 모드 로더가 1번 많습니다(16 대 15, 양쪽 모든 실행에서 같음, 바닐라 12). JFR 기록이 JVM 시작부터라 시작 단계의 GC가 들어 있습니다. 정지 합계는 표준편차 안이라 차이로 주장하지 않습니다.
- 시작 시간(두 번째 시작): Poly 모드 로더가 이기지 못했습니다. 준비까지 중앙값은 Connector가 0.49초, 종료까지 0.33초 짧습니다. Connector 다섯 실행 중 셋은 Poly 모드 로더의 모든 실행보다 짧고, 둘은 Poly 모드 로더 실행 범위 안입니다(종료까지·준비까지 모두). 차이가 Connector 쪽 표준편차(0.62~0.65초) 안이라 유의한 차이 없음입니다. Poly 모드 로더 쪽은 실행 간 흔들림이 훨씬 작습니다(±0.14초). 바닐라 대비 준비까지 추가분은 Poly 모드 로더 +5.4초, Connector +4.9초입니다.
- 첫 시작(예열 실행, 1회씩, 결론에 쓰지 않음): Connector는 Fabric jar를 변환하느라 15.2초(준비 13.8초), Poly 모드 로더는 12.0초(준비 10.6초)였습니다. 하지만 Poly 모드 로더의 변환 캐시는 이 작업 전 시험 실행으로 이미 데워져 있었으므로 같은 조건의 첫 시작 비교가 아닙니다.
원시 데이터(두 변형의 jar 목록 포함)는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-24-startup-connector.json, docs/benchmarks/2026-10-24-ticks-connector.json에 있습니다. 고정해 받은 Connector 쪽 파일 목록은 poly-bootstrap/compat/connector-reference-26.2.json에 있습니다.
측정 10 — 측정 9의 틱 꼬리 지연 원인 조사와 틱 다시 재기 (2026-10-24)
측정 9에서 Poly 모드 로더의 틱 p95·p99가 Connector보다 높았고 p95는 바닐라보다도 높았습니다. 그 원인을 프로파일로 찾고, Poly 모드 로더 몫이면 고친 뒤 틱만 같은 설정으로 다시 쟀습니다. 결론부터 말하면 틱 안에서 Poly 모드 로더가 더하는 비용은 찾지 못했고, 그래서 로더 코드는 바꾸지 않았습니다. p95 차이는 측정 구간 앞쪽(틱 200~399), JIT가 아직 수렴하는 동안에 몰려 있고, p99 차이의 절반가량은 측정 구간에 든 GC 한 번의 차이입니다.
프로파일 방법
- 측정 9의 원시 JFR 기록 15개(기본 설정, Java 샘플 20ms 간격)를 다시 분석했습니다. 측정 구간은 서버 스레드에서
GameTestServer.tickServer가 처음 샘플된 시각부터 잡았습니다. - 느린 틱과 보통 틱을 나누려고 측정 도구에 켜고 끄는 옵션을 더했습니다(기본값 꺼짐).
-PtickBenchmarkTickEvents=true는 틱 프로브가 틱마다 JFR 이벤트poly.TickProbe.Call(걸린 시간, 순번)을 남기게 하고,-PtickBenchmarkJfrSettings=<default|profile|.jfc>는 기록 설정을 바꿉니다. 세 변형에 똑같이 들어갑니다. - 프로파일 실행:
tickBenchmarkConnector -PtickBenchmarkHeap=4g -PtickBenchmarkRuns=2 -PtickBenchmarkTickEvents=true와profile기반.jfc(Java 샘플 5ms,jdk.ClassLoad켬,jdk.Compilation문턱 0ms, safepoint·VM 작업, 모니터 1ms·park 2ms 문턱). 첫 측정 실행은 다른 작업의 게임 JVM과 겹쳐 버렸고, 변형마다 예열 실행과 두 번째 실행만 썼습니다. 프로파일 실행의 수치는 측정 결과로 쓰지 않습니다(프로파일러 비용이 들어 있음). - 느린 틱 = 실행마다 틱 200 이후의 p95 이상, 보통 틱 = p40~p60. 두 묶음 안에 든 샘플·할당·GC·클래스 로드·역최적화·컴파일·잠금 대기를 셌습니다.
찾은 것
| 의심한 것 | 증거 | 판정 |
|---|---|---|
| Poly 모드 로더 코드(이벤트 디스패치, Fabric API 이벤트 연결, 진입점) | 측정 9 측정 구간의 서버 스레드 샘플 2,363개 중 dev.polymodloader 프레임이 있는 샘플 4개(0.2%), net.fabricmc 3개(0.1%). 프로파일 실행의 느린 틱에서는 둘 다 0개. 비교: Connector 쪽은 NeoForge 프레임(EventBus.post, EventHooks.onNeighborNotify 등)이 보통 틱 샘플의 1.8% | 아님 |
| 서버 스레드가 하는 일의 총량 | 실행 끝 스레드 덤프의 서버 스레드 CPU 시간(측정 9, 5회 중앙값): 바닐라 12.23초, Poly 모드 로더 12.28초, Connector 12.17초 | 차이 없음 |
| 틱 중 클래스 로딩·변환 | 틱 200 이후 느린 틱 하나에 서버 스레드 클래스 로드 0.9개·0.08ms(바닐라 1.3개·0.08ms, Connector 1.2개·0.10ms). 측정 9 Poly 실행의 틱 구간 변환 로그는 한 줄 | 차이 없음 |
| 로그 | 틱 중 Poly 모드 로더 로그는 위 변환 한 줄뿐 | 아님 |
| 할당과 GC 횟수 | 측정 구간 할당 샘플 합계: 바닐라 7.84GiB, Poly 모드 로더 7.86GiB, Connector 7.24GiB(실행당). 상위 할당 지점은 모두 게임 코드(BlockPos, CollectingNeighborUpdater). 첫 틱 이후 GC는 세 변형 모두 7회 실행 전부 4회(측정 10). GC 횟수 차이(12·16·15)는 전부 시작 단계입니다. Metadata GC Threshold 바닐라 8회, Poly·Connector 10회, 시작 중 Evacuation Pause 바닐라 0회, Connector 1회, Poly 2회 | 틱과 무관 |
| GC가 측정 구간 어디에 드는지 | 틱 중 GC 간격은 세 변형 모두 약 320 | 위상 차이. Poly 코드 비용 아님 |
| 잠금 경합 | 측정 구간 서버 스레드의 JavaMonitorEnter(1ms 이상)·ThreadPark(2ms 이상) 없음, 세 변형 모두 | 아님 |
| 틱 프로브 | 세 변형 모두 같은 jar, 같은 인수, 같은 대상 메서드(GameTestServer.tickServer)이고, 작업이 실행마다 [tick-probe] timing 줄을 확인합니다. Poly·NeoForge 쪽은 각자의 변환 뒤에 프로브가 감싸므로 각자 tickServer에 넣은 훅(poly:server_tick_events, NeoForge의 ServerTickEvent)이 모두 측정에 들어갑니다 | 같게 다룸 |
| JIT 컴파일 | 틱 범위별 컴파일 수(프로파일 실행, 컴파일러 스레드 15개): 틱 200 | 차이 없음 |
답은 느린 틱이 어디 있는지였습니다. 프로파일 실행의 느린 틱(p95 이상) 102개 중 틱 200399에 든 것이 Poly 모드 로더 61개, 바닐라 62개, Connector 48개입니다. GC가 든 몇 틱을 빼면 그 틱에는 클래스 로드도 로더 프레임도 없고, 보통 틱과 같은 게임 메서드가 더 느리게 돌 뿐입니다(샘플 상위 메서드가 보통 틱과 같음). 25틱 묶음 중앙값을 보면 세 변형 모두 틱 300400 근처에서야 안정됩니다. 틱 100200에서는 Connector(NeoForge)가 가장 빠르고, Poly 모드 로더는 틱 225250에서 가장 느리다가(첫 틱 중 GC가 이 무렵에 옴) 틱 275부터는 가장 빠릅니다(측정 10, 7회 중앙값, ms):
| 틱 | 0 | 100 | 150 | 200 | 225 | 250 | 275 | 300 | 400 | 475 |
|---|---|---|---|---|---|---|---|---|---|---|
| 바닐라 | 20.2 | 14.1 | 11.6 | 10.8 | 9.5 | 9.5 | 9.5 | 9.5 | 8.9 | 8.8 |
| Poly 모드 로더 | 20.6 | 13.6 | 11.9 | 10.9 | 10.8 | 9.9 | 8.9 | 8.8 | 8.3 | 8.3 |
| Connector | 20.2 | 12.4 | 10.8 | 10.0 | 9.7 | 9.5 | 9.3 | 9.0 | 8.6 | 8.4 |
벤치마크는 처음 200틱만 버리므로 이 수렴 구간이 p95·p99에 들어갑니다. 같은 원시 틱을 범위로 나누면 다음과 같습니다(실행별 값의 중앙값, ±표준편차, ms):
| 범위 | 지표 | 바닐라 | Poly 모드 로더 | Connector |
|---|---|---|---|---|
| 측정 9, 틱 200~399 | p95 / p99 | 11.55 (±0.69) / 14.66 | 12.38 (±0.28) / 16.70 | 10.38 (±3.51) / 13.06 |
| 측정 9, 틱 400~끝 | p95 / p99 | 9.79 (±0.30) / 12.50 | 9.58 (±0.41) / 11.63 | 9.61 (±0.26) / 11.85 |
| 측정 10, 틱 200~399 | p95 / p99 | 11.86 (±0.59) / 16.09 | 11.63 (±1.52) / 17.02 | 10.77 (±1.86) / 15.08 |
| 측정 10, 틱 400~끝 | p95 / p99 | 9.77 (±2.10) / 12.06 | 9.20 (±0.68) / 12.63 | 9.61 (±2.26) / 11.75 |
틱 400 이후에는 Poly 모드 로더의 p95가 Connector·바닐라와 같거나 낮습니다(표준편차 안이라 우위로 주장하지 않음). 틱 200~399의 평균은 Poly 모드 로더와 바닐라가 같고(측정 9·10을 합친 12회 중앙값 10.02 대 9.99ms) Connector가 낮습니다(9.46ms). 즉 이 구간에서 Poly 모드 로더가 바닐라보다 일을 더 하는 것이 아니라, NeoForge 쪽이 더 빨리 데워집니다. 또 Connector는 틱당 할당이 약 8% 적습니다(할당 샘플: BlockPos$MutableBlockPos.immutable 실행당 1.9GiB 대 Poly 모드 로더 2.8GiB, 바닐라 2.9GiB). 같은 게임 코드에서 할당이 적은 것은 JIT의 탈출 분석이 다르게 됐다는 뜻으로 보입니다. 그 이유는 밝히지 못했습니다. 후보는 NeoForge가 패치한 게임 코드의 모양(Connector 쪽에서만 Level.markAndNotifyBlock이 따로 보임), Forgified Fabric API와 Fabric API의 서로 다른 Mixin(같은 Fabric 모드여도 자기 시간 분포가 다름: ServerChunkCache.getChunk 자기 시간 Connector 16%, 바닐라 11%, Poly 모드 로더 6%), 그리고 실행 순서(항상 바닐라 → Poly 모드 로더 → Connector)입니다.
참고로 Poly 모드 로더 쪽에만 보인 할당이 하나 있습니다. Collective(Fabric 판)의 Mixin Level_updateNeighborsAt이 CallbackInfo를 실행당 약 35MiB(전체 할당의 0.4%) 만듭니다. 모드 자신의 비용이라 Poly 모드 로더가 바꿀 일이 아니며, 크기로 보아 꼬리 지연의 원인도 아닙니다.
고친 것
로더 코드는 고치지 않았습니다. 프로파일에서 Poly 모드 로더 자신의 비용을 찾지 못했기 때문입니다. 바뀐 것은 측정 도구뿐입니다(위 프로파일 옵션, 기본값 꺼짐, 아래 측정에는 쓰지 않음).
틱 다시 재기
| 항목 | 값 |
|---|---|
| 명령 | :poly-bootstrap:tickBenchmarkConnector -PtickBenchmarkHeap=4g -PtickBenchmarkRuns=7 |
| 설정 | 측정 9와 같음(같은 세 변형·모드·JDK·힙·데이터 팩·로그 설정·기본 JFR 설정, 1,200틱, 처음 200틱 버림, 각 1회 예열 버림). 다른 점은 측정 실행 7회(측정 9는 5회) |
| 코드 | Poly 모드 로더 1.0.0. 로더 코드는 측정 9와 같고, 측정 도구만 커밋 b7d71fd |
| 환경 | 측정 9와 같은 PC(Intel Core i9-14900HX, RAM 31.7 GiB, Windows 11 Pro 10.0.26200, JDK 25.0.3, G1, Minecraft 26.2). 측정 전과 측정 중 15초마다 다른 게임 JVM을 확인했고 38회 모두 조용했습니다 |
서버 틱(실행 7회의 중앙값, 괄호 안은 실행 간 표준편차). 마지막 열은 비교용 측정 9의 Poly 모드 로더입니다:
| 지표 | 바닐라 | Poly 모드 로더 | NeoForge + Connector | 측정 9 Poly 모드 로더 |
|---|---|---|---|---|
| 실행당 측정 틱 수 | 1,006 | 1,007 | 1,006 | 1,007 |
| 틱 p50 (ms) | 8.74 (±0.51) | 8.24 (±0.16) | 8.49 (±0.51) | 8.47 (±0.26) |
| 틱 p95 (ms) | 10.58 (±1.61) | 10.73 (±0.66) | 10.07 (±2.09) | 10.91 (±0.34) |
| 틱 p99 (ms) | 13.47 (±1.47) | 14.18 (±1.53) | 13.07 (±2.36) | 13.97 (±0.97) |
| 가장 긴 틱 (ms) | 51.3 | 61.8 | 55.2 | 61.8 |
| 50ms를 넘은 틱 (%) | 0.10 | 0.10 | 0.10 | 0.20 |
| GC 직후 힙 최댓값 (MiB) | 141.5 (±3.5) | 173.1 (±4.0) | 203.8 (±2.3) | 173.1 (±1.1) |
| GC 횟수(시작 포함 실행 전체) | 12 | 16 | 15 | 16 |
| GC 정지 합계 (ms) | 189.6 (±7.7) | 267.6 (±21.6) | 283.2 (±17.6) | 282.3 (±34.0) |
| 가장 긴 GC 정지 (ms) | 41.6 | 48.7 | 65.6 | 49.5 |
실행별 p95(ms): 바닐라 10.58, 14.77, 10.23, 10.51, 10.65, 11.53, 10.44 / Poly 모드 로더 10.61, 11.04, 12.01, 11.94, 10.73, 10.45, 10.56 / Connector 13.61, 9.88, 9.91, 10.04, 14.84, 10.07, 10.10.
결론:
- 전후 비교: 개선 없음. 로더 코드를 바꾸지 않았으므로 이것은 다시 잰 것입니다. Poly 모드 로더의 p95는 10.91 → 10.73ms, p99는 13.97 → 14.18ms로 표준편차 안이며, 측정 9를 재현했습니다.
- 틱 p95·p99: Poly 모드 로더가 여전히 이기지 못했습니다. p95 중앙값은 Connector가 0.66ms 낮고, Connector 일곱 실행 중 다섯(9.88
10.10ms)이 Poly 모드 로더의 모든 실행(10.4512.01ms)보다 낮습니다. 나머지 둘(13.61, 14.84ms)은 Poly 모드 로더의 모든 실행보다 높습니다. 표준편차(2.09ms)가 차이보다 커서 규칙상 유의한 차이 없음이지만, 보통의 실행에서 Connector가 낮다는 모양은 측정 9와 같습니다. p99도 같습니다(차이 1.11ms, Connector 표준편차 2.36ms). - 원인: 측정 구간 앞쪽의 JIT 수렴과 GC 위치. 위 범위 표대로 p95 차이는 틱 200~399에 있고, 틱 400 이후에는 없습니다. 이 구간에서 Poly 모드 로더는 바닐라와 같은 속도로 데워지고 NeoForge 쪽이 더 빨리 데워집니다. p99 차이의 절반가량은 Connector의 첫 틱 중 GC가 버리는 구간(틱 0)에 들어 측정 구간의 GC가 하나 적은 데서 옵니다. Poly 모드 로더가 틱마다 하는 일 때문이 아닙니다.
- 바닐라 대비: p95 10.73 대 10.58ms, p99 14.18 대 13.47ms로 표준편차 안이며 유의한 차이 없음입니다. p50은 Poly 모드 로더가 0.50ms 낮지만 바닐라 표준편차(0.51ms) 안이라 주장하지 않습니다.
- 흔들림: Poly 모드 로더는 세 변형 중 실행 간 흔들림이 가장 작습니다(p95 표준편차 0.66ms 대 바닐라 1.61ms, Connector 2.09ms). 바닐라 1회, Connector 2회는 실행 전체가 느렸습니다(p50 9.3~9.6ms).
- 메모리: GC 직후 힙은 측정 9와 같습니다(Poly 모드 로더 173.1MiB, Connector 203.8MiB, 다섯 실행과 일곱 실행 모두 겹치지 않음).
원시 데이터는 Poly 모드 로더 저장소의 docs/benchmarks/2026-10-24-ticks-connector-rerun.json에, 측정 9·10의 모든 실행의 틱 범위별 수치는 docs/benchmarks/2026-10-24-tick-ranges-connector.json에 있습니다.
측정하지 않은 것
- 모드 없이 Poly 모드 로더만 띄운 클라이언트 프레임 시간(로더 비용과 모드 비용 분리)
- 할당률, 청크 생성 시간
- 같은 Fabric 모드 묶음을 Fabric Loader로 실행한 비교(측정 5), Lithium을 뺀 변형
- Fabric 모드 묶음을 넣은 클라이언트 프레임 시간, Forge 모드 묶음
- 같은 Forge 모드 묶음을 Forge 자체 실행기로 실행한 비교(측정 6), Forge 이벤트 버스·모드별 틱 비용
- 같은 NeoForge 모드 묶음을 NeoForge 자체 실행기로 실행한 비교(측정 8), Lithium을 뺀 NeoForge 묶음
- 같은 Quilt 모드 묶음을 Quilt Loader로 실행한 비교(측정 7), 26.1 계열의 Quilt 묶음
- Connector 비교(측정 9)에서: 클라이언트 프레임 시간(FPS), 한쪽만 로드하는 모드(Connector가 못 돌리는 Architectury·Balm·Lithium·Puzzles Lib·Forge Config API Port·Zoomify, Poly 모드 로더가 서버에서 빼는 LambDynamicLights), 같은 조건의 첫 시작(빈 캐시), NeoForge만 띄운 변형(NeoForge 몫과 Connector 몫 분리), Connector Extras, 26.1 계열, 전용 서버(EULA 확인에서 멈추므로 게임 테스트 서버로 잼)
- 측정 10에서: NeoForge 쪽이 틱 200~399에서 더 빨리 데워지고 틱당 할당이 적은 이유(JIT 인라이닝·탈출 분석 비교,
-XX:+PrintCompilation·PrintInlining등), 실행 순서를 바꾼 변형(항상 바닐라 → Poly 모드 로더 → Connector), 처음 400틱을 버린 변형(측정 10의 틱 범위 표는 같은 원시 틱을 나눈 것이지 따로 잰 것이 아님), CPU 시간 샘플링(jdk.CPUTimeSample, Linux 전용) - JMH 마이크로벤치마크(이벤트 디스패치, 변환 계획)
- 다른 하드웨어
이 항목들을 측정하기 전까지 성능 목표(프레임 p95 5% 개선 등)는 목표일 뿐 결과가 아닙니다.