# tsrs vs tsgo: 공식 발표는 8배, 벤치마크는 16.87배? 실제로 네 가지를 비교했다

> Canonical: https://vxi.me/ko/posts/tsrs-vs-tsgo-typescript-rust-compiler/
> Locale: ko-kr
> Published: 2026-10-22
> Updated: 2026-10-22
> Markdown: https://vxi.me/ko/posts/tsrs-vs-tsgo-typescript-rust-compiler.md

<figure class="tsrs-parity-hero not-prose">
<picture><source media="(prefers-reduced-motion: reduce) and (max-width: 640px)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.mobile.still.ko-kr.svg" type="image/svg+xml"><source media="(prefers-reduced-motion: reduce)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.still.ko-kr.svg" type="image/svg+xml"><source media="(max-width: 640px)" srcset="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.mobile.ko-kr.svg" type="image/svg+xml"><img src="/images/posts/tsrs-vs-tsgo-typescript-rust-compiler/tsrs-diagnostic-parity.ko-kr.svg" alt="동일한 TypeScript 소스 리비전으로 tsgo와 tsrs에서 합성 픽스처 4개를 실행했으며 진단과 종료 코드가 모두 일치한다." width="1200" height="700" loading="eager" fetchpriority="high" decoding="async" style="display:block;width:100%;height:auto"></picture>
<figcaption>그림: 단일 파일 합성 픽스처 4개에서 tsgo와 tsrs의 진단을 비교한다. 네 결과가 일치하지만 프로젝트 호환성이나 성능 벤치마크를 뜻하지는 않는다.</figcaption>
</figure>

Cloudflare는 tsrs를 소개하며 TypeScript 7.0보다 최대 8배 빠르고 메모리 사용량이 약 70% 적다고 밝혔습니다. 그런데 tsrs의 특정 커밋에 고정된 옛 벤치마크를 확인해 보니 mui-docs에는 16.87배, vscode 기본 설정에는 13.55배라고 적혀 있었습니다. 공식 발표의 8배와 이 숫자들은 어떻게 맞는 걸까요?

대형 프로젝트 성능 실험을 제가 다시 수행한 것은 아닙니다. 이번에는 tsrs와 tsgo(TypeScript의 Go 구현)를 서로 대응하는 버전으로 고정해 GitHub의 일시적인 격리 환경에서 TypeScript 예제 네 개의 진단을 비교했습니다. 네 경우 모두 같았습니다. 더 살펴볼 근거는 생겼지만 이것만으로 운영 CI의 타입 검사기를 교체할 생각은 없습니다.

## 벤치마크를 읽을 때 checker 수가 중요한 이유

Cloudflare [공식 발표](https://blog.cloudflare.com/typescript-rust-port/)는 초기 결과를 최대 8배 속도 향상, 메모리 약 70% 절감으로 요약합니다. 별도로 고정한 tsrs [과거 측정 보고서](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/bench/RESULTS.md)에는 다음 값이 나옵니다.

| 과거 업스트림 벤치마크 | tsgo | tsrs | checker 수 | 보고된 배수 |
| --- | ---: | ---: | --- | ---: |
| mui-docs, 기본값 | 12.66 초 | 0.75 초 | 4 vs 16 | 16.87배 |
| mui-docs, 양쪽 `--checkers 4` | 12.69 초 | 1.07 초 | 4 vs 4 | 11.81배 |
| mui-docs, 양쪽 `--checkers 16` | 12.36 초 | 0.77 초 | 16 vs 16 | 16.07배 |
| vscode, 기본값 | 11.10 초 | 0.82 초 | 4 vs 16 | 13.55배 |
| vscode, 양쪽 `--checkers 16` | 6.73 초 | 0.81 초 | 16 vs 16 | 8.32배 |

기본 설정부터 조건이 다릅니다. tsgo에는 checker 4개, tsrs에는 16개가 쓰였습니다. 하지만 두 도구 모두 4개나 16개로 맞춘 행도 8배를 넘습니다. 발표는 초기 측정 결과의 요약이고 `RESULTS.md`는 특정 버전과 빌드 환경에서 얻은 별도의 옛 스냅샷입니다. 8배는 그 표의 최대값도 아니고, 표의 16배가 모든 프로젝트에서 나온다는 약속도 아닙니다.

차이의 정확한 원인을 표만 보고 단정하지는 않겠습니다. 보고서가 밝힌 환경은 Depot CI의 `depot-ubuntu-24.04-16`(16 vCPU, 메모리 63 GB, Linux x86_64)입니다. 실행 순서를 교차해 측정한 시간과 최대 메모리의 중앙값을 사용했습니다. tsgo 기준은 `typescript@7.0.2`인 반면 tsrs는 더 새로운 TypeScript 7.1-dev 커밋을 이식했고 PGO 최적화 릴리스 빌드를 썼습니다. checker 개수를 맞춰도 소스 버전과 빌드 방식은 같지 않습니다.

메모리도 상황에 따라 달라집니다. mui-docs의 기본값에서는 4.61배, 양쪽 checker를 16개로 설정하면 8.23배 적은 메모리가 보고됐습니다. 발표의 '약 70% 감소'는 초기 비교의 요약이지 보장값이 아닙니다. 이 부분은 업스트림 기록을 확인했을 뿐 대형 프로젝트 벤치마크를 재실행하지 않았습니다.

## 실제로 실행한 네 가지 진단 샘플

고정한 버전은 `tsrs@0.18.0-ts7.1.0-dev.20260929`와 `typescript@7.1.0-dev.20260930.4`의 Go 구현입니다. 두 도구는 같은 [TypeScript 소스 커밋 b85298b6](https://github.com/microsoft/TypeScript/commit/b85298b6a81f772d080b0455de0ca9d744cd6fd6)을 기준으로 합니다.

의존성 준비에서 제가 먼저 실수했습니다. TypeScript 소스 저장소의 workspace 이름 `@typescript/typescript`를 npm에서 설치할 수 있는 공개 패키지로 오해해 E404를 받았습니다. 그때는 컴파일러가 실행되지도 않았습니다. 공개 루트 패키지는 `typescript`입니다. 이 nightly의 명령은 여전히 `tsc`이며 Linux x64 네이티브 Go 구현은 `@typescript/typescript-linux-x64`에서 제공됩니다. 패키지 이름을 수정한 뒤 컴파일을 시작할 수 있었습니다.

네 샘플 모두 같은 `tsconfig.json`을 사용했습니다. `types: []`는 환경에 설치된 전역 `@types`가 자동으로 추가되는 문제를 막고, `skipLibCheck: false`는 선언 파일 검사를 유지합니다. 단지 'strict 모드'라고 말하는 대신 실제 설정을 남깁니다.

~~~json
{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "module": "esnext",
    "noEmit": true,
    "skipLibCheck": false,
    "types": []
  },
  "include": ["src/*.ts"]
}
~~~

각 디렉터리에 `src/test.ts` 파일 하나만 놓았습니다.

| 픽스처 | `src/test.ts` 전체 내용 | 실측 tsgo / tsrs |
| --- | --- | --- |
| `valid` (정상 타입) | `export const answer: number = 42;` | 진단 0개, 종료 코드 0 / 0 |
| `type-error` (타입 할당 오류) | `export const answer: number = "wrong";` | TS2322 · 종료 코드 2 / 2 |
| `module-missing` (없는 모듈) | `import {missing} from "./unavailable.js"; export const answer = missing;` | TS2307 · 종료 코드 2 / 2 |
| `generic-constraint` (제네릭 제약 오류) | `type OnlyText<T extends string> = {value:T}; export type Bad = OnlyText<number>;` | TS2344 · 종료 코드 2 / 2 |

두 도구 모두 `--noEmit --pretty false --checkers 4`로 실행했습니다. 파일명, 행과 열, 오류 코드, 메시지, 출력 순서, 종료 코드를 비교했으며 네 경우가 모두 일치했습니다. 다만 사례마다 소스 파일이 하나라 `--checkers 4`가 실제 4개 병렬 검사기 성능을 측정한 것은 아닙니다.

## 오류 개수가 같아도 안심하기 어려운 이유

고정 버전의 [호환성 문서](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/COMPATIBILITY.md)는 오류 기준선 13,462개 중 13,458개가 일치하고, `.types` 및 `.symbols` 기준선 12,779개는 모두 일치한다고 보고합니다. 그러나 합계가 같다고 진단이 같다고 할 수는 없습니다.

`drizzle-orm`이 좋은 반례입니다. 업스트림 보고서에서는 양쪽에 checker 16개를 사용했을 때 10,846개의 오류가 나왔지만 진단 위치는 달랐고, tsrs 버그로 분류했습니다. 이 사례는 제가 직접 재현한 것이 아니라 고정된 보고서에서 읽은 내용입니다. 실제 코드 리뷰와 CI에서는 오류가 정확한 파일과 줄을 가리키는지 확인해야 합니다.

버전별 제한도 살펴봐야 합니다. 이 버전은 `--init`과 `--showConfig`를 지원하지 않고, `--locale`로 진단 메시지를 번역하지 못합니다. `.astro`, `.vue`, `.svelte` content mapper는 CLI/build에서는 동작하지만 언어 서버에서는 지원되지 않습니다. 배포 바이너리도 Windows와 Intel macOS는 포함하지 않습니다. 저는 GitHub 격리 Linux 환경의 CLI만 확인했으며 IDE 플러그인, LSP나 이 운영체제들은 테스트하지 않았습니다. `astro check`나 실시간 편집기 진단이 필요하면 [이 버전의 호환성 표](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/COMPATIBILITY.md)부터 확인하세요. Astro 업그레이드의 타입 검사 문제를 다룬 [기존 글](/posts/astro-check-typescript-7-compatibility/)도 참고할 수 있습니다.

## 내 프로젝트에서는 무엇부터 비교할까

평소 사용하는 작업 디렉터리를 바로 바꾸지 말고 일회용 worktree나 별도 브랜치에서 시작하는 편이 안전합니다. 실패하면 실험을 그대로 폐기할 수 있기 때문입니다.

아래 스크립트가 확인하는 것은 종료 코드는 같아도 상세 진단이 다를 수 있다는 점입니다. `set +e`는 의도적입니다. 오류를 넣은 샘플은 0이 아닌 종료 코드를 반환하므로 두 컴파일러를 모두 끝까지 실행하고 상태를 기록한 후에 출력을 비교해야 합니다. 여기의 `tsc`는 TypeScript nightly의 Go 구현 명령이고 `/tmp/tsgo.out`는 출력 파일 이름일 뿐입니다.

~~~bash
pnpm add -D --save-exact tsrs@0.18.0-ts7.1.0-dev.20260929 typescript@7.1.0-dev.20260930.4

set +e
pnpm exec tsc -p . --noEmit --pretty false --checkers 4 > /tmp/tsgo.out 2>&1
tsgo_status=$?
pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 > /tmp/tsrs.out 2>&1
tsrs_status=$?
diff -u /tmp/tsgo.out /tmp/tsrs.out
diff_status=$?
set -e
printf 'tsgo_exit=%s tsrs_exit=%s diff_exit=%s\n' "$tsgo_status" "$tsrs_status" "$diff_status"
if [ "$tsgo_status" -ne "$tsrs_status" ] || [ "$diff_status" -ne 0 ]; then
  echo 'DIAGNOSTIC_DIFF_OR_EXIT_MISMATCH: inspect /tmp/tsgo.out and /tmp/tsrs.out'
  exit 1
fi
~~~

처음부터 차이가 나면 원본 로그를 남기고 `--checkerAssignment go`(tsgo의 파일 분배 및 checker 기록)나 `--noLazyMembers`(tsrs의 lazy-member 최적화 비활성화)를 별도 실험으로 시도하세요. 첫 비교 전에 켜면 기본 동작의 차이를 가릴 수 있습니다. [사용 문서](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/USAGE.md)와 [기본 설정 차이](https://github.com/oxc-project/tsrs/blob/357d1d3ec23180eae94a8485b8b7444487a7ea5f/docs/DEFAULTS.md)에 상세한 설명이 있습니다.

진단 비교 스크립트는 소요 시간이나 메모리를 측정하지 않습니다. 이번에는 저도 성능 측정 전 과정을 실행하지 않았습니다. 우선 한 번 기록하려면 Linux의 GNU `time`을 이용해 실행 시간과 최대 RSS를 수집할 수 있습니다. macOS에서는 `gnu-time`을 설치하고 첫 줄을 `TIME_BIN=gtime`으로 바꿉니다.

~~~bash
TIME_BIN=/usr/bin/time

set +e
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsgo-bench.metrics \
  pnpm exec tsc -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsgo-bench.out 2>&1
tsgo_status=$?
"$TIME_BIN" -f 'elapsed_s=%e max_rss=%M' -o /tmp/tsrs-bench.metrics \
  pnpm exec tsrs -p . --noEmit --pretty false --checkers 4 \
  > /tmp/tsrs-bench.out 2>&1
tsrs_status=$?
set -e

printf 'tsgo_exit=%s ' "$tsgo_status"; cat /tmp/tsgo-bench.metrics
printf 'tsrs_exit=%s ' "$tsrs_status"; cat /tmp/tsrs-bench.metrics
~~~

이 명령은 결과 한 쌍만 만듭니다. 컴퓨터의 백그라운드 부하만 변해도 값이 흔들립니다. 제대로 비교하려면 같은 runner에서 순서를 바꿔 가며 최소 5쌍을 실행하고 의존성 lockfile, 버전, `tsconfig`, checker 수를 고정해 시간 중앙값과 최대 RSS를 비교하겠습니다. 콜드 스타트와 웜 캐시도 따로 기록합니다. 저는 운영체제 page cache를 비우지 않았으므로 완전한 시스템 콜드 캐시 측정이라고 주장하지 않습니다. GNU `time`의 `%M`은 보통 최대 RSS를 KiB 단위로 표시하므로 플랫폼 간 단위를 확인해야 합니다.

실제 CI에서는 tsrs를 먼저 합병을 차단하지 않는 추가 작업으로 돌리고 기존 `tsc`와 프레임워크 검사를 필수 게이트로 유지하겠습니다. 설명되지 않은 진단 차이가 없고, 성능 수치가 안정되며, 실제 PR·배포 주기와 롤백 절차까지 확인한 뒤에야 교체를 검토할 수 있습니다. 네 가지 작은 샘플이 입증한 것은 그 입력에서 두 도구의 진단이 같다는 사실뿐입니다.

실측 날짜: 2026년 10월 22일
