Nagi 0.1 性能・検証報告
この版は、Rust製コンパイラと実ランタイムを持つ、二層バックエンド言語の試作です。Highから編集可能なLowテキストを生成し、それを再解析・検査してRust経由でネイティブへコンパイルします。HTTP body → 型付きclass → SQLite → class → JSONのCRUD経路、Lowの通常関数呼び出し・関数置換、async/scope、actor間通信、worker再起動が実際に動きます。
汎用言語、本番用独自runtime、BEAM相当の障害隔離は未完成です。actor・Supervisor・queueは実ランタイムを呼ぶ標準試験関数で、Highの汎用宣言構文はまだありません。以下の数値は、記載した測定環境で採取したログに基づきます。サンプル名は公開用に匿名化し、測定時と同じASCIIのバイト長を維持しています。測定値は変更していません。
このページの目次
測定環境と再現条件
| 項目 | 今回の条件 |
|---|---|
| OS | Linux-6.18.44-x86_64-with-glibc2.39 |
| CPU | INTEL(R) XEON(R) PLATINUM 8573C |
| CPU割当 | affinity 0–8、cgroup quota 8 CPU。物理ホストの共有状況と周波数は制御していない |
| メモリ上限 | 8 GiB cgroup |
| Rust / Cargo | 1.98.1 (48a229cea 2026-09-01) / 1.98.1 |
| ビルド | release、opt-level=3、LTO=false、codegen-units=1、panic=unwind。target-cpu=nativeは使用しない |
| 主要依存 | Tokio 1.53.1、Axum 0.8.9、serde 1.0.229、serde_json 1.0.151、rusqlite 0.40.2(bundled SQLite)。Cargo.lockを同梱 |
| CPython / Node | 3.12.14 / v24.19.0 |
| HTTP Python | aiohttp 3.13.5 + slots dataclass |
| load generator | native wrk 4.2.0、commit a211dd5a7050b1f9e8a9870b95513060e72ac4a0 |
| 時刻 | 2026-09-30 UTC。詳細は environment.json |
CPU/microbenchはserver負荷・ビルドと重ねず、CPU 0へ固定して個別に実行しました。各Rust測定は5回warmup、pilotから1反復のloop数を決め、7反復のmedianです。Pythonは5回warmupと7×25回、NodeはJIT用50回warmupと7×100回です。準備済みの同じ100,000要素を使用し、配列生成は計測外です。整数入力は (i*17+13)%997-498、floatはその値を7で割ります。Node Numberでも今回の整数入力・結果は正確ですが、i64全範囲の意味は共有しません。
試行内にも揺れがあり、CPU周波数、共有ホストの競合、cache状態の完全な制御はしていません。単一環境での探索的計測です。別実行の数%の差から言語の優劣を断定しません。raw_nsを保存し、良い値だけの選択はしていません。
allocation counterは呼び出しthreadのRust GlobalAlloc要求を数えます。SQLiteのC allocator・他threadのjob/task allocationは含みません。reallocはalloc回数にも含め、byteは要求量の累計でlive/peak memoryではありません。戻り値のdropはcount取得後なので、そのdeallocationは表に入りません。時間計測中はcounterを無効にしますが、allocator転送時のTLS分岐は残ります。静的cost reportは発生箇所であり、動的allocation回数ではありません。
CPU:同じ入力・同じ結果
kernel全体の時間はµs/opです。ns/itemは100,000要素で割ったamortized値であり、一つの加算の単独latencyではありません。
| kernel | Nagi µs/op | Rust µs/op | Python µs/op | Node µs/op | Nagi ns/item | Python/Nagi |
|---|---|---|---|---|---|---|
| integer_sum | 14.667 | 13.895 | 2568.563 | 95.678 | 0.147 | 175.1× |
| map_reduce | 33.973 | 35.908 | 5622.608 | 136.259 | 0.340 | 165.5× |
| filter_reduce | 65.030 | 101.582 | 2317.947 | 139.674 | 0.650 | 35.6× |
| branch | 69.915 | 74.350 | 4514.531 | 271.324 | 0.699 | 64.6× |
| float_sum | 75.123 | 80.055 | 1637.475 | 79.255 | 0.751 | 21.8× |
| loop | 118.099 | 134.439 | 5173.244 | 193.316 | 1.181 | 43.8× |
checksumは4実装で一致しました(floatは許容誤差1e-7)。{"integer_sum": -3184, "map_reduce": 90448, "filter_reduce": 12461527, "branch": 2106, "float_sum": -454.85714285709753, "loop": 299995}。Nagi/Rustのkernel内Rust allocationは全て0回です。Python/Nodeのallocationは未計測で、この0と比較しません。
CPythonのbuiltin sumは別の参考値として 686.172 µs/opでした。表のPythonは明示ループであり、NumPy等のnative kernelとの比較ではありません。データは反復中cacheへ温まるため、DRAM帯域や巨大datasetの処理速度も表していません。
Nagiはnative整数・連続配列を使い、動的な数値boxing・型判定をループ中に置きません。generated Rustと手書きRustの整数sumの逆アセンブルには、どちらもpacked i64加算の paddq が4箇所あります(integer-sum.asm / rust-integer-sum.asm)。LLVMのvector化・unrollがns/itemを下げています。手書きRustとの時間差には生成コード・配置・実行時の揺れがあり、独自backendがRustを上回ったという結果ではありません。
JSON・view・文字列のコスト
以下のJSONは id=42,name=alice,age=18 の小さい同一入力です。ns/opはparse/encode等の操作全体、allocationは1回の操作の動的測定です。
| 操作 | ns/op | alloc回数 | realloc回数 | 要求byte累計 |
|---|---|---|---|---|
| json_parse_tree | 281.35 | 5 | 0 | 646 |
| json_tree_to_class | 325.72 | 5 | 0 | 646 |
| json_direct_class | 142.31 | 1 | 0 | 5 |
| json_borrowed_class | 178.68 | 0 | 0 | 0 |
| json_class_encode | 113.07 | 1 | 0 | 128 |
| json_encode_reused_buffer | 67.14 | 0 | 0 | 0 |
| json_parse_modify_encode | 289.75 | 2 | 0 | 133 |
| string_parse_i64_1000 | 10486.23 | 0 | 0 | 0 |
| view_1mib | 7.74 | 0 | 0 | 0 |
| copy_1mib | 57511.23 | 1 | 0 | 1048575 |
中間Value treeからUserへ変換する経路に対し、直接Userへdecodeする経路は今回 2.29×、allocationは5→1回、646→5 byteでした。Rust structへ直接deserializeしており、dict/map/汎用JSON treeを経由しません。残る1回は所有Stringフィールドです。
borrowed classは入力を指す&strを使う手書きRustの実験です。0 allocationでも、この試行ではowned classより速くはありませんでした。escapeを含むJSON文字列や入力寿命を越える保持には所有化が必要です。Highのclassにborrowed fieldを許す機能は未実装です。
encode時には128 byte capacityのVecを一つ作ります。reused buffer実験は0 allocationですが、HTTPへ一般適用していません。HTTP responseはnative class → Vec<u8> → Bodyであり、直接socketへserializeする完全streamingではありません。parse-modify-encodeはStringと出力Vecの2 allocationです。
view試験は1 MiB bufferの範囲参照を作るだけです。入力pointer+1とview pointerの一致、0 allocationを記録しました。copy試験は1,048,575 byteを実際に所有コピーします。処理内容が違うため両者の時間比を同じ処理の高速化率とは扱いません。HTTP受信bodyの借用が追加コピーを避けても、network/kernel/HTTP入力buffer全体がzero-copyという意味にはなりません。
SQLite → class:変換と所有化
同じin-memory SQLiteへ10,000件を事前投入し、cached SELECTから1/100/1,000/10,000件を取得します。id i64、name TEXT「alice」、age i32です。SQL実行・row stepping・class生成・Vecへの格納を含み、接続生成・seed insertは含みません。
| row数 | 列名毎row µs | 列番号 µs | 列番号+reserve µs | idのみscan µs | 列番号alloc | 列番号byte累計 |
|---|---|---|---|---|---|---|
| 1 | 0.790 | 0.767 | 0.853 | 0.598 | 3 | 229 |
| 100 | 30.197 | 23.004 | 22.772 | 10.039 | 107 | 10644 |
| 1000 | 327.006 | 184.723 | 209.405 | 94.325 | 1010 | 86824 |
| 10000 | 3398.379 | 2197.449 | 2384.866 | 980.799 | 10014 | 1360624 |
列名をquery開始時に一度だけ番号へ解決する経路は、10,000件で列名を毎row解決する経路に対し 1.55×でした。FromRowは直接native fieldを読み、tuple/dict/ORM中間objectを作りません。TEXTはSQLite stepの寿命を越えて返すため、各rowに所有Stringが必要です。row変換全体が0 allocationという主張はできません。
id-only scanとの時間差にはTEXT取得・String allocation・3field変換・Vec格納が混ざり、純粋な変換時間を厳密に分離した値ではありません。reserveは要求byteとreallocを減らしますが、この10,000件試行では通常Vec成長より時間が増えました。全queryで件数を事前に知るためのCOUNTや推測reserveは導入していません。
1,000 insertを一つのtransactionで実行してrollbackする操作は 488.667 µs/op、1 insert+rollbackは 3.463 µs/opでした。Rust allocation counterが0でも、SQLite C allocator、ページ処理、disk I/Oが0という意味ではありません。今回はmemory DBで、永続化/fsync性能は測っていません。
HTTPからのDB jobは専用thread、bounded channel 64、oneshot replyを通ります。その往復・job boxing・async schedulingはこの同期microbenchには含みません。後述のHTTP db_single_rowは実際にその経路を含みます。型付きcolumn検査はruntimeで、コンパイル時schema/SQL検証はありません。
実測後の変更
列番号を毎query Vecで保持していた実装を、16列以内はinline配列へ変更しました。列数overflow時のみVecへ退避します。列順入れ替え・missing columnの既存テストを通し、同じベンチを再測定しました。
| 操作 | 変更前 ns/op | 変更後 ns/op | 前 alloc | 後 alloc | 前 byte | 後 byte |
|---|---|---|---|---|---|---|
| db_indexed_1 | 819.70 | 767.44 | 4 | 3 | 261 | 229 |
| db_indexed_1000 | 213607.44 | 184723.43 | 1011 | 1010 | 86856 | 86824 |
| db_indexed_10000 | 2219904.20 | 2197449.04 | 10015 | 10014 | 1360656 | 1360624 |
確実な効果はqueryごとの1 allocation / 32 byte削減です。10,000件の時間差は小さく、速度改善が統計的に確立したとは扱いません。初期ログはmicro-before.jsonl、変更後はmicro-after.jsonlです。
actorには最大32件のpipeline送信を追加しました。個々のmessageは同じmailboxを通り、全oneshot replyを受けて結果20,000を確認します。一つの「+20,000」にまとめた測定ではありません。結果は下に示します。JSON buffer再利用とDB Vec reserveは実験のみで、現在のHTTP/DB標準経路には入れていません。
HTTP:同じ成功payloadでの比較
全serverをCPU 0、1 executor/event-loop workerへ固定し、wrkはCPU 7/8、2 thread、64 keep-alive接続です。各case 1秒warmup→3秒測定、各framework/caseを3試行しました。framework順はseed固定で試行ごとに並べ替えます。Nagi/Axumは共通runtimeのtimeout/body-limit middlewareを使います。NagiのみSQLite threadが一つ常駐します。Node/aiohttpにこのmiddlewareを完全に移植したわけではなく、framework全体の同一内部処理を保証する比較ではありません。
| case | workload |
|---|---|
| plaintext | GET /health → ok |
| small_json | GET /small → User JSONを毎回生成 |
| post_small | POST /echo、5文字name+age、型とunknown-field検査、同じJSONを生成 |
| post_medium | 同じPOST、4,096文字name |
| query_parameter | Nagiのみ:typed Query limit/name → User JSON |
| db_single_row | Nagiのみ:typed Path id → DB worker → cached SELECT → User → JSON |
Nagi/Axumは同じnative structを使用します。NodeはJSON.parse→field検査→JSON.stringify、aiohttpはjson.loads→slots dataclass→型検査→json.dumpsです。成功status/bodyをそろえています。Nagiだけschema検証を省く測定にはしていません。small_jsonは定数JSON byteの再送ではありません。
rpsとp50/p95/p99は3試行それぞれの値のmedian、maxは3試行の最大値です。複数試行のhistogramをmergeしたpercentileではありません。latencyの単位はµs。errorsはsocket/status/timeoutの合計です。
| framework | case | req/s | p50 | p95 | p99 | max | errors |
|---|---|---|---|---|---|---|---|
| nagi | plaintext | 82,701 | 662 | 2318 | 4344 | 14501 | 0 |
| nagi | small_json | 74,344 | 754 | 2355 | 4321 | 109324 | 0 |
| nagi | post_small | 39,860 | 1154 | 9128 | 34048 | 121675 | 0 |
| nagi | post_medium | 36,641 | 1527 | 4072 | 6883 | 89114 | 0 |
| nagi | query_parameter | 68,076 | 814 | 2680 | 4930 | 25215 | 0 |
| nagi | db_single_row | 42,544 | 1410 | 3487 | 6039 | 181247 | 0 |
| axum | plaintext | 87,040 | 632 | 2557 | 6019 | 125302 | 0 |
| axum | small_json | 77,406 | 709 | 2336 | 4224 | 124887 | 0 |
| axum | post_small | 60,842 | 917 | 2568 | 5211 | 16715 | 0 |
| axum | post_medium | 36,366 | 1535 | 3979 | 6783 | 85825 | 0 |
| node | plaintext | 28,606 | 1725 | 6893 | 17529 | 110900 | 0 |
| node | small_json | 35,402 | 1618 | 3741 | 6269 | 25919 | 0 |
| node | post_small | 25,713 | 2203 | 5335 | 10869 | 79994 | 0 |
| node | post_medium | 17,537 | 3275 | 6992 | 15715 | 139972 | 0 |
| aiohttp | plaintext | 17,076 | 3442 | 7607 | 13375 | 90396 | 0 |
| aiohttp | small_json | 14,098 | 3999 | 8526 | 13581 | 47104 | 0 |
| aiohttp | post_small | 11,137 | 5154 | 10084 | 14567 | 27593 | 0 |
| aiohttp | post_medium | 6,518 | 8163 | 21121 | 40894 | 108190 | 0 |
closed-loop wrkの値であり、一定到着率のopen-loopや過負荷時のtail latencyを評価したものではありません。同じruntimeを使う手書きAxumが主要なnative基準です。生成wrapperやhandlerの違い、body処理、allocation、SQLite workerを含む構成差を見て、CPU kernelの差とHTTPの差を分けて評価します。
小さいPOSTはNagi約39.9k req/s、手書きAxum約60.8k req/sで、今回Nagiが低い結果でした。大きいPOSTは約36k req/sで同じ桁です。小POSTの差を特定の関数のコストへ結び付けるprofileは取得できていません。生成wrapperとrouter/stateの扱いを分離する追加測定が必要で、native化だけでHTTPの全経路が最適になるとは判断しません。
次は測定区間のserver CPU使用率、終了時RSSの3試行最大、context switch/s(全server threadの合計差)、thread/FDの最大です。CPU使用率は1論理CPUを100%として、/procのCPU tick差をwall timeで割っています。load generator側のCPU/RSSは含めていません。
| framework | case | CPU % | 終了RSS MiB | switch/s | threads | FD |
|---|---|---|---|---|---|---|
| nagi | plaintext | 97.8 | 6.35 | 95 | 3 | 10 |
| nagi | small_json | 99.0 | 6.45 | 83 | 3 | 10 |
| nagi | post_small | 98.0 | 6.49 | 66 | 3 | 10 |
| nagi | post_medium | 98.0 | 6.52 | 37 | 3 | 36 |
| nagi | query_parameter | 97.9 | 6.52 | 73 | 3 | 50 |
| nagi | db_single_row | 98.6 | 6.67 | 65,710 | 3 | 10 |
| axum | plaintext | 98.2 | 4.28 | 100 | 2 | 10 |
| axum | small_json | 97.3 | 4.35 | 87 | 2 | 31 |
| axum | post_small | 98.4 | 4.36 | 60 | 2 | 13 |
| axum | post_medium | 99.1 | 4.38 | 32 | 2 | 10 |
| node | plaintext | 99.9 | 51.29 | 856 | 7 | 82 |
| node | small_json | 99.4 | 55.79 | 817 | 7 | 65 |
| node | post_small | 99.6 | 58.84 | 1,072 | 7 | 85 |
| node | post_medium | 99.5 | 68.02 | 1,659 | 7 | 86 |
| aiohttp | plaintext | 99.9 | 28.58 | 14 | 1 | 71 |
| aiohttp | small_json | 99.8 | 28.61 | 13 | 1 | 71 |
| aiohttp | post_small | 99.9 | 28.91 | 13 | 1 | 71 |
| aiohttp | post_medium | 99.0 | 29.83 | 11 | 1 | 71 |
合計54試行でsocket/status/timeout errorは0件でした。正常応答の負荷比較であり、認証、TLS、HTTP/2、slow-client/DoS耐性、大容量streamを含む本番SLO試験ではありません。
120秒の継続負荷
Nagi /echo、4,096文字name、128接続、wrk 2 thread、server 1 workerで120秒実行しました。35,238 req/s、p50 3222 µs、p95 7209 µs、p99 16531 µs、max 176174 µs、socket/status/timeout error 0件です。
1秒ごとのRSSは 6.05–8.06 MiB、最終負荷sample 8.06 MiB、停止後5sampleは 7.16–7.16 MiBでした。FDは負荷中 10–138、停止後 10–10でした。
この有限時間で異常や未回収FDが観測されたかを確認する試験です。メモリリーク不在・数日安定性・allocator fragmentation・GC pauseを証明するものではありません。RSSが高水位から下がらない場合も、所有値のdrop、allocatorの保持、fragmentationを分ける追加観測が必要です。全processのallocation/free追跡やheap profilerは未実施です。
同時task・TCP接続
task試験はTokioの4 workerです。1,000/10,000/50,000/100,000 taskを生成し、各taskが最初のpollへ到達したことをatomic counterで確認します。Semaphoreを解放するまでは1件も完了できません。その時点のRSSを測り、全taskを解放・joinします。3試行のmedian時間と最大RSSです。gate、Arc、ready counter、spawn/joinのコストを含むテストharnessで、実アプリの1task単独コストではありません。
| 同時待機task | 生成→全join ms | 全待機時RSS MiB | 終了RSS MiB | 未完了 |
|---|---|---|---|---|
| 1,000 | 4.812 | 2.34 | 2.34 | 0 |
| 10,000 | 18.389 | 5.62 | 5.62 | 0 |
| 50,000 | 93.424 | 20.28 | 20.28 | 0 |
| 100,000 | 162.528 | 38.64 | 38.64 | 0 |
以前のconcurrency-before.jsonlは「spawnした総数」の試験で、全数が同時に待機していた保証とpeak観測がありません。gateを追加した後の時間とは直接比較しません。完了後のRSSはallocator保持を含み、未完了taskの数ではありません。CPU処理は別のspawn_blocking+semaphore 4へ分けていますが、開始済みのblocking仕事の強制中断は未実装です。
TCPは実際に1,000/10,000接続を同時保持し、それぞれからhealth応答を受けます。open時のclient同時進行は128、server workerは1です。50,000/100,000 TCPは今回のFD上限16,384とIPv4 ephemeral port 32768–60999の条件で実施していません。100,000 taskの結果を100,000 TCP接続へ読み替えません。
| 要求TCP | 保持TCP | open+応答 秒 | 保持RSS MiB | 切断後RSS MiB | 開始FD | 保持FD | 切断後FD | errors |
|---|---|---|---|---|---|---|---|---|
| 1000 | 1000 | 0.387 | 28.35 | 28.35 | 10 | 1010 | 10 | 0 |
| 10000 | 10000 | 4.484 | 249.58 | 249.59 | 10 | 10010 | 10 | 0 |
FDは両試行で開始値へ戻りました。RSSは切断直後も高水位を保持しています。約250 MiBで10,000接続を保持できた観測はありますが、約25 KiB/接続の差分はHTTP buffer・runtime・allocator等を含むprocess全体の目安です。kernel socket memoryはRSSに含まれません。
actor・Supervisor・queue
同じCounterActor、mailbox 64、Tokio 4 worker、20,000 messageを5試行します。serial RPCは1件のreplyを待って次を送り、pipelineは最大32件を進行させます。下のµs/messageはelapsed/messagesのmedian相当で、個々のmessageのp50/p99 latencyではありません。
| 経路 | message/s | amortized µs/message | mailbox | 最大送信batch |
|---|---|---|---|---|
| actor_rpc | 22,204 | 45.038 | 64 | 1 |
| actor_pipelined | 554,022 | 1.805 | 64 | 32 |
pipelineはserialに対しこの試行で 24.95×のthroughputでした。待機とscheduler往復を重ねて隠す改善で、1件のRPC latencyが同じ倍率で下がったという意味ではありません。mailbox message/replyのallocationと所有化は残ります。Highのsharedは明示Arcですが、task/channel/DB stateの内部Arcまで消す設計ではありません。
Supervisorはworker panicを3回検出し3回再起動、再起動latencyは 1.951–2.137 msでした。latencyはpanic検出から新しい子taskの最初のpollまでで、1 ms backoffを含みます。独立workerは12回継続しました。crash-loop試験は1秒windowで上限2に達した後、再起動停止を確認しました。SIGSEGV/abort/process killの隔離、request replay/loss、任意Supervisor treeは試験対象外です。
queueは1,000 job、8 worker、channel 64、最大3 attempt、retry backoff 1/2 msで、完了909、dead-letter 91、retry 312、peak in-flight 8でした。失敗はidから決めて注入します。DLQ件数を数える実験であり、永続DLQ、process再起動後のreplay、exactly-once APIはありません。
DB workerを100回生成・終了した後、live worker数は0→0でした。scope子taskのerror/panicでは他の子taskをcancelしてjoinし、guardが解放されたことをunit testで確認しています。外側futureのdropやbody panicではJoinSet Dropによるabort要求までで、その場で非同期cleanup完了を待つ保証はありません。
安全性・正しさの確認
| 検証 | 結果と範囲 |
|---|---|
| Rust unit test | runtime 23 + compiler 39 = 62件成功。型/range/move/view escape、Low roundtrip、置換signature、実SQLite、scope、actor、Supervisor、queue |
| sample build | 11 High + 1 standalone Low、12件成功。Low call/replaceは42、手書きLowは再生成後も同じbytes |
| real HTTP | 29項目成功。CRUD、型/unknown field/malformed JSON/UTF-8/範囲、body limit、SQL bound param、keep-alive、stream、WS text/binary、timeout後の生存 |
| seeded mutation | compiler parser/check 10,000 + malformed JSON 10,000、seed 305419896、panic 0。coverage-guided fuzzではない |
| code checks | cargo fmt、clippy all-targets -D warnings、locked build/test成功。final-checks.txt |
| 配布検証 | zipを別directoryへ展開し、空のcompiler/native targetからsource build。High valuesは10/16、Low standaloneは4。relative runtime pathを確認。distribution-checks.txt |
| negative ownership | owned/localのview escape、move後使用、借用中move・mutation、taskへのview escapeを拒否。部分move/複雑なflowは最終Rust checkerに依存 |
| pointer/zero-copy | slice pointerのoffsetと0 allocationを確認。安全sliceとbackend borrow checkを使用 |
| resource/fault | 100k同時待機taskの全join、10k TCPの全応答とFD復帰、worker再起動・crash intensity、DB worker終了を確認 |
| 未実施 | ASan/TSan/Miri/Valgrind、coverage-guided fuzz、hardware perf counters、flamegraph、cache/branch miss、全allocator fragmentation、数日soak |
High checkerの成功だけでsoundnessを保証していません。safe Rustを生成し、Rustのborrow/Send検査まで通ったものだけnative binaryにします。runtime側unsafeはSystem GlobalAlloc転送等の限られた観測実装にあり、Lowに任意unsafe/FFIを許す段階ではありません。生成コードとruntimeのdependencyに対する独立したsecurity auditはしていません。
実装状況と判断
| 分野 | 今回動くもの | 今後必要なもの |
|---|---|---|
| 二層compiler | indentation High、text Low、各parse/check、native call、signatureを保つ関数置換、Rust native build | crate分割、module/import、source span、汎用generic/trait/function型、self-host |
| native値 | primitive、Copy class、Vec連続格納、nullable、Result、UUID/timestamp | 完全なMap/owned API、method/interface、算術overflow仕様の統一 |
| memory | move、保守的lexical borrow、view、明示copy/shared、Rust drop | High request arena、borrowed class、精密なescape/partial move解析 |
| async/concurrency | Tokio task、scope、bounded channel actor、restart/intensity、retry/DLQ実験 | 汎用actor/queue/Supervisor構文、独自scheduler、durable delivery、cancel可能なCPU job |
| HTTP/JSON | HTTP/1、typed route/query/body、typed response、keep-alive、limit、timeout、WS/stream sample | TLS/auth、HTTP/2、汎用routing、streaming JSON、buffer pool |
| DB | SQLite実CRUD、専用worker、prepared cache、indexed FromRow、typed native fields | 汎用params/pool/transaction、compile-time SQL/schema、PostgreSQL binary protocol |
| Low native制御 | 同じ値型/関数/viewを手書き・生成の両方で利用 | pointer/layout/alignment/alloc/free/unsafe/C ABI/SIMD命令 |
有効だった方向は、native primitive+連続配列、直接typed JSON、DB列番号の一回解決、不要な小Vecの除去、actor pipelineです。JSON借用やreserveはallocation削減と速度向上を分けて評価すべき結果でした。HTTPはhandler/runtime/serialization/schedulingを含むため、CPU kernelの倍率から予測できません。
次の実装は、算術・borrow仕様の確定、module/generic、任意state actorのlowering、typed DB paramの一般化、request arenaとbuffer再利用の実測の順が妥当です。完成に必要な工数を今回の試作速度だけから推定しません。Low self-hostはString/Map/module/allocator APIとbootstrap一致試験をそろえた後の段階です。
再実行と生ログ
ビルド・機能試験はREADMEの手順を使用します。数値の再測定時は同時にビルドや別のCPU benchmarkを走らせず、CPU affinityを環境に合わせて変更してください。
taskset -c 0 ./native-target/release/nagi-cpu > benchmarks/results/cpu-nagi.jsonl
taskset -c 0 ./target/release/examples/microbench > benchmarks/results/micro-after.jsonl
taskset -c 0 python3 benchmarks/python_cpu.py > benchmarks/results/cpu-python.jsonl
taskset -c 0 node benchmarks/node_cpu.js > benchmarks/results/cpu-node.jsonl
./target/release/examples/concurrency_bench > benchmarks/results/concurrency-after.jsonl 2> benchmarks/results/fault-after.log
python3 tests/connections.py > benchmarks/results/connections.json
python3 scripts/http_bench.py --wrk /absolute/path/to/wrk --soak 120
python3 scripts/summarize_results.py
results/にはCPU・JSON・SQLite・allocation・concurrencyのJSONL、wrk各試行のtext、HTTP集約JSON、1秒resource sample、障害stderr、環境情報、checksum検証を同梱します。wrkのソース全体は同梱せず、commitとLua scriptを記録しています。Linux専用のtaskset/proc計測部分は他OSでは置き換えが必要です。CI定義は同梱していますが、remote CI上では今回実行していません。
設計・未実装の詳細はdocs/の22ページ、再現用ソースはcompiler/runtime/examples/tests/benchmarks/scripts/にあります。