Turbolog: 깜빡임 제거
프레임마다 콘솔을 지우면 DevTools 페인트의 70~81%에서 화면이 비어 보였어요. 프레임을 덧붙이자 1%로 줄었고, 모두 첫 프레임에서 발생했어요.
Text.js의 각 프레임은 console.log 호출 1번이에요. 0.4 이전에는 모든 프레임 앞에서 console.clear()도 호출했어요. 부하가 걸리면 프레임 사이에 콘솔이 비어 깜빡였어요.
깜빡임 측정
깜빡임을 측정하려고 bench/devtools를 만들었어요. Chrome for Testing을 실행하고, 실제 DevTools 프런트엔드를 페이지로 열어 다음 항목을 측정해요:
- DevTools 프런트엔드의
requestAnimationFrame마다 콘솔 뷰포트에 완전한 32줄 프레임이 있는지 확인해요. 없으면 빈 페인트로 분류해요. - 나중에
Page.startScreencast프레임을 디코딩해 픽셀 단위로 결과를 확인해요. - 모든 프레임의 0번 줄에
FRAME:<id> · T:<epochMs>가 있어서, 프런트엔드가 어떤 프레임을 표시하고 있는지, 얼마나 늦었는지 알 수 있어요.
각 프레임 앞에서 console.clear()를 호출하면, 15fps로 실행한 100×32 대시보드는 DevTools 페인트의 70.6%에서 비어 있었어요. 60fps에서는 81.1%였어요.
원인
DevTools의 ConsoleView는 두 호출을 다르게 처리해요:
console.clear()는consoleCleared()를 호출해 뷰포트를 동기적으로 무효화해요. 모든 메시지가 같은 태스크 안에서 사라져요.console.log()는addConsoleMessage()를 호출해요. 이 함수는 제한 시간이 50ms인Throttler,viewportThrottler로 새로고침을 예약하기만 해요.
그래서 각 프레임은 이전 프레임을 즉시 지우고 새 프레임은 나중에 그려요. 15fps에서는 66ms마다 약 50ms 동안 아무것도 표시되지 않아요.
프레임 덧붙이기
0.4부터 Turbolog는 프레임을 덧붙여요. DevTools가 콘솔 하단을 따라가기 때문에 한 번의 페인트 안에서 새 프레임이 이전 프레임을 대체해요. 맨 앞에 있는 너비 0의 스팬(커튼)은 margin-top:100vh로 이전 프레임을 화면 밖으로 밀어내고, 자체 줄을 끝내므로 DevTools 소스 링크가 0번 줄의 너비를 줄이지 않아요.
그래도 언젠가는 콘솔을 비워야 해요. 콘솔 지우기는 첫 프레임, invalidate() 이후, 프레임이 100개 쌓인 뒤 유휴 상태일 때, 그리고 상한인 300개에 도달했을 때만 실행해요.
스로틀러에 맞춘 속도 조절
DevTools가 그릴 수 있는 속도보다 빠르게 프레임을 보내도 빨라지지 않아요. 스로틀러가 대기 중일 때 로그가 도착하면 스로틀러가 다시 시작되어 프레임이 누락돼요: 60fps에서 DevTools는 출력된 295개 프레임 중 67개를 그렸어요. Turbolog는 프레임 간격을 최소 66ms, 약 15fps로 조절하고, 간격이 허용하면 개별 입력은 여전히 동기적으로 렌더링해요.
위상 고정 해제
콘솔 지우기는 이후 모든 프레임에 약 50ms의 지연도 더했어요. 콘솔을 지운 뒤 DevTools는 Console was cleared라는 합성 행을 삽입해요. 이 행이 스로틀러의 즉시 실행 슬롯을 차지하고 다음 프레임을 50ms 타이머로 밀어내며, 66ms의 일정한 주기에서는 이후 모든 프레임이 그 타이머에 고정돼요.
이제 렌더러는 콘솔을 지울 때마다 한 번씩 110ms를 기다려요. 그 뒤에는 각 프레임이 이전 새로고침 완료 후 50ms가 넘어서 도착해 DevTools의 즉시 실행 경로로 처리돼요. 표시 지연 중앙값은 63ms에서 10.4ms로 줄었어요.
결과
bun bench/devtools/run.ts --strategy=native,clear --fps=15,60 --seconds=5, 100×32 대시보드, 실제 DevTools 프런트엔드:
| 구동 방식 | FPS | 빈 페인트 | 그린 프레임 / 출력한 프레임 | 지연 p50 |
|---|---|---|---|---|
| 프레임마다 지우기 | 15 | 70.6% | 70 / 75 | 60.4ms |
| 덧붙이기 (1.0) | 15 | 1.0% | 74 / 75 | 10.4ms |
| 프레임마다 지우기 | 60 | 81.1% | 45 / 300 | 9.8ms |
| 덧붙이기, 속도 조절 없음 | 60 | 0.8% | 67 / 295 | 31.2ms |
남은 1%는 의도적으로 콘솔을 지우는 첫 프레임이에요. 이때 빈 페인트로 분류된 화면에는 빈 콘솔 대신 Console was cleared 행이 있어요.
스팬당 속성 수 축소
DevTools 소스에서 비용이 하나 더 드러났어요. %c 스타일의 모든 개별 속성을 각 줄 래퍼에 복사하기 때문에, font:와 margin: 단축 속성이 스팬당 약 33개 속성으로 확장됐어요. 이제 스팬은 16진수 색상과 함께 개별 속성만 약 7개씩 사용해요.
덧붙이기 모드는 Text.js 0.4에서 출시됐고, 위상 고정 수정은 1.0에서 출시돼요. 둘 다 애플리케이션 코드를 바꿀 필요가 없어요.