TOON과 JSON, 균일 레코드 실측
2026-10-06 기준 균일한 hikes 레코드 1,000개 실험에서 toon-format의 UTF-8 출력 중앙값은 27562 bytes, 공백 없는 JSON은 65643 bytes였어요. 다만 직렬화 중앙값은 TOON 8.619585ms, JSON 0.967026ms로 갈렸고, 크기와 생성 시간을 한 지표로 바꾸지 않아야 해요.
2026-10-06 기준 균일한 hikes 레코드 1,000개에서 TOON은 JSON보다 UTF-8 출력이 작았지만, 직렬화 시간은 더 길었어요.
이 결과는 전송하거나 프롬프트에 넣을 텍스트의 크기와, 그 텍스트를 만드는 비용을 같은 판단으로 묶으면 안 된다는 뜻이에요.
가설과 측정 범위
이번 측정의 가설은 균일한 객체 배열 1,000개에서 toon-format이 압축 JSON보다 더 작은 UTF-8 직렬화 결과를 만든다는 것이었어요. 입력은 중첩 context, friends, hikes로 구성했고 hikes의 각 레코드는 id, name, distanceKm, wasSunny 필드를 같은 형태로 가졌어요.
비교 대상 JSON은 Python 표준 라이브러리의 json.dumps였고 공백이 들어가지 않도록 separators=(",", ":") 조건을 썼어요. 각 구현은 같은 입력으로 워밍업한 뒤 직렬화 결과의 UTF-8 바이트 수와 직렬화 시간을 기록했어요.
원시 결과는 원시 데이터(JSON)에서 확인할 수 있어요. 도구의 공개 위치는 toon-format 저장소예요. 수치와 표는 그 기록만 사용했어요.
환경과 실행 조건
측정 환경은 aarch64, Neoverse-N1 x2, CPUQuota 150%, 2048MB, Ubuntu 24.04.4 LTS, Python 3.12.3이었어요. 이 환경 표기는 결과를 다른 장비의 일반 성능으로 읽지 않기 위해 함께 남겨야 해요.
각 측정 묶음은 7회 반복했어요. 출력 크기는 마지막 직렬화 결과의 UTF-8 바이트 수로 기록했고, 시간은 구현별 연속 실행에서 얻은 회당 평균값을 사용했어요.
결과: 더 작은 출력, 더 긴 직렬화
출력 크기에서는 TOON이 일관되게 작았어요. 7회 모두 TOON은 27562 bytes, JSON은 65643 bytes였고 최소·중앙·최댓값이 같았어요. 입력이 고정돼 있고 출력 문자열도 결정적이었다는 관측이에요.
| 지표 | 구현 | 중앙값 | 최소 | 최대 | 단위 |
|---|---|---|---|---|---|
| 직렬화 시간 | TOON | 8.619585 | 8.604987 | 8.807773 | ms |
| 직렬화 시간 | JSON | 0.967026 | 0.948264 | 0.995039 | ms |
| 출력 크기 | TOON | 27562 | 27562 | 27562 | bytes |
| 출력 크기 | JSON | 65643 | 65643 | 65643 | bytes |
시간 축은 반대였어요. TOON의 직렬화 시간 중앙값은 8.619585ms였고 JSON은 0.967026ms였어요. 따라서 이 입력에서 TOON을 선택하는 근거는 생성 속도가 아니라 출력 크기예요.
여기서 크기와 시간을 하나의 승패로 합치면 측정의 의미가 사라져요. 저장·전송·컨텍스트 길이가 병목이라면 bytes 열이 관련 있고, 요청마다 즉시 직렬화해야 하는 경로라면 ms 열이 관련 있어요. 같은 입력에서도 두 요구가 서로 다른 선택을 가리킨다는 점이 이번 결과의 핵심이에요.
재현에 사용한 스크립트
아래 코드는 원시 결과를 만든 스크립트예요. 코드가 생성하는 데이터 구조와 측정 순서를 바꾸지 않은 채 기록했어요.
import json
import time
import toon_format
records = [
{
"id": i,
"name": f"trail-{i:04d}",
"distanceKm": round(1.25 + i * 0.05, 2),
"wasSunny": i % 3 != 0,
}
for i in range(1000)
]
data = {
"context": {"task": "benchmark", "location": "Boulder"},
"friends": ["ana", "luis", "sam"],
"hikes": records,
}
json_dumps = lambda value: json.dumps(value, separators=(",", ":"), ensure_ascii=False)
toon_format.dumps(data)
json_dumps(data)
iterations = 30
start = time.perf_counter()
for _ in range(iterations):
toon_text = toon_format.dumps(data)
toon_serialize_ms = (time.perf_counter() - start) * 1000 / iterations
start = time.perf_counter()
for _ in range(iterations):
json_text = json_dumps(data)
json_serialize_ms = (time.perf_counter() - start) * 1000 / iterations
print(json.dumps({
"toonSerializeMs": toon_serialize_ms,
"jsonSerializeMs": json_serialize_ms,
"toonBytes": len(toon_text.encode("utf-8")),
"jsonBytes": len(json_text.encode("utf-8")),
}))
해석과 한계
이 결과는 균일한 hikes 레코드와 이 입력 구조에서만 성립해요. 필드 구성이 달라지거나 값의 반복성이 달라지면 출력 크기의 차이도 달라질 수 있고, 다른 런타임이나 하드웨어에서는 시간 범위를 그대로 옮길 수 없어요.
또한 이 측정은 직렬화만 다뤘어요. 역직렬화, 네트워크 왕복, 저장 공간, 소비하는 쪽의 호환성은 이 기록에 없으므로 이 결과만으로 판단할 수 없어요. 이번 실험에서 확실한 사실은 고정된 입력에서 TOON의 출력은 더 작았고, 그 출력을 만드는 시간은 JSON보다 길었다는 점이에요.
관련 글
LibPDF Core와 pdf-lib 생성 시간 비교
40쪽 벡터 도형 PDF를 7회 생성·저장한 실험에서 LibPDF Core의 중앙값은 23.444065ms, pdf-lib는 69.286347ms였어요. 원시 결과의 speedup은 2.95539이지만 출력 바이트 수가 달라 파일 크기 요구도 함께 판단해야 해요.
자동화 레인 편입 전 확인할 것
새 자동화 레인은 한 번의 그림자 통과만으로 즉시 정식 흐름에 넣으면 안 돼요. 실행 순서, 미편입 상태의 종료 방식, 관측 지표를 분리해 두면 일정 변경이 기존 작업을 멈추게 하는 문제를 줄일 수 있어요.
자동 저작 전 Git 사본 동기화
자동 저작이 오래된 규칙을 읽지 않게 하려면 모델 호출보다 먼저 작업 사본을 원격 기준으로 fast-forward해야 해요. 갱신 불가 상태를 실패로 끝내는 이유와 분기된 사본을 안전하게 막는 테스트 기준을 다뤄요.
공개 자료를 바탕으로 AI가 쓰고, 발행 전에 수치와 주장을 원문과 대조합니다.