SQLite parse_number(): パーサー書き換え後、CがRustを上回る
Simon WillisonによるSQLite parse_number()の実験では、当初はRustがCを上回っていたが、狙いを絞ったパーサーの書き換えによって性能順位が逆転した。2026年11月8日に公開されたこの調査は、書式付き数値をクリーンアップするためのデータベース関数を追加するコンパイル済みアドオンである拡張機能を比較している。報告によれば、改訂後のC実装はテストしたすべてのデータセットで首位となり、コンパイル済みファイルもより小さく維持した。
課題はささやかに聞こえる。通貨記号、カンマ、パーセント記号を含むテキストを、数値としてのソートや加算に使える値へ変換することだ。しかしこの比較は、異なるエンジニアリング上の問いに対して異なる答えを示した。Rustは最初の十進数パーサーでより高速だった一方、最終的には最適化されたCが測定上もっとも高速な実装となった。
AI生成コードを評価する開発者にとって、焦点となるのはレビューのプロセスだ。もっともらしい実装や馴染みのある言語だけでは、実行時間がどこで費やされているかをほとんど示せない。Willisonの調査が有用になったのは、測定によって特定の変換ルーチンが特定され、より限定的な実験へとつながったときだった。
SQLite parse_number()、2回目の試行で結果が変わる
実装が変われば報告された勝者も変わり、パーサー設計が比較の中心となった。
Willisonはまず、自身のアカウントでOpus 5.5とされているClaudeに、CとRustでこの関数を検討するよう依頼した。求めた比較は、実行速度、コードの複雑さ、生成されるライブラリのサイズを対象としていた。彼はこの調査をSQLite parsing experimentで説明している。
最初の結果では、ファイルサイズと速度が分かれた。Willisonによれば、C拡張機能は約14 KBで、rusqliteを使った場合は321 KB、sqlite-loadable-rsを使った場合は298 KBだった。これらのRustライブラリは、RustコードをSQLiteに接続する。それでも、より小さいファイルである初期C実装は、混合入力と小数入力で敗れた。
説明の中心となったのは、テキストを浮動小数点数、すなわち近似的な数値表現に変換するCライブラリ関数strtod()だった。Willisonは、Rustが最初に十進数で優位に立った理由を、より高速な変換ルーチンにあるとした。その後、彼はClaudeに対し、Rust実装に匹敵する、あるいは上回るCパーサーを作るよう依頼した。
改訂版のCは、報告された比較で首位に立った。混合データでは、丸めた正味コストが、初期Cの1行あたり67ナノ秒から改訂Cでは19ナノ秒へ低下した。Rust版は46ナノ秒と41ナノ秒を記録した。小数のみの入力では、C内でさらに大きな変化が見られ、1行あたり106ナノ秒から18ナノ秒になった。
この一連の流れが、この話に意義を与えている。最初の比較で止めていれば、測定した小数ワークロードにはRustを選ぶという結論を支持することになる。狙いを絞った改訂を続けた結果、求められたデータベース関数を変えずに別の候補が生まれた。
この実験は、代替案の生成と評価も切り分けている。Claudeが実装を提供したが、その相対的な価値は周辺の測定を通じて明らかになった。最初の回答は、確定したエンジニアリング判断ではなく出発点を提供した。
この結果をプログラミング言語の普遍的な順位付けとして扱う根拠はない。これは小さな関数に対する特定の実装である。違いには変換ルーチン、一時ストレージ、統合コードが含まれ、それらはいずれも大まかなC対Rustという見出しの下にある。
実務上の変化はもっと具体的だ。開発者には、遅い最初のCドラフトが測定上もっとも高速な候補になった公開事例がある。これにより、より広範な言語についての主張を裏付けないまま、実装の選択を検討する事例として役立つ。
ベンチマークはSQLite内での繰り返しパースを測定する
このワークロードでは繰り返される変換が中心的な役割を果たすため、結果にはその文脈を付す必要がある。
公開されたbenchmark harnessは、200万行を含むインメモリデータベースを作成する。各値をパースして結果を合計するクエリを評価する。Pythonがクエリを開始し、SQLiteが繰り返されるデータベース処理を実行する。
この構成は、個々の変換ごとにPythonループを置かないため重要だ。したがって実験は、データベース関数呼び出しとパース経路を組み合わせた処理をより直接的に対象としている。主に測定しているのは、Pythonアプリケーションがネイティブコードへ繰り返し移行する処理ではない。
ハーネスは15回の実行で候補を交互に実行する。最小経過時間と中央値を記録し、最小値から公表する正味の数値を算出する。結果が1行あたりのナノ秒になる前に、文字列長を合計するベースラインスキャンが差し引かれる。
この差し引きは追加作業の切り分けに役立つが、その数値はあくまで比較のための推定値である。インポート、ダッシュボードの更新、あるいは他の操作を含むクエリ全体の所要時間ではない。これをユーザー向けの応答時間として読むと、測定設計の範囲を超えることになる。
データセットも結果を形作る。整数テキスト、小数テキスト、書式付きの混合値を別々に実行する。混合ジェネレーターには、通貨記号付きの値、パーセンテージ、通常の数値文字列、無効なエントリーが含まれる。こうした変化により、小数変換の問題と一般的なパースの問題を区別できる。
インポートされた財務レポートを繰り返しソートするアプリケーションを考えてみよう。すべてのクエリでパースが発生する場合、変換処理は各レポート要求の一部になる。一方でアプリケーションがインポート時に値を正規化するなら、同じパーサーは別の段階に影響し、異なる性能要件に直面する。
どちらのアーキテクチャも、これらの測定から自動的に導かれるわけではない。この実験はパース部品に関する証拠を提供し、その部品がどの頻度で実行されるかはアプリケーションが決める。高速な単独関数がもっとも重要になるのは、実際のワークロードで意味のある割合を占める場合だ。
ベンチマークの純粋なSQL代替案は、繰り返しの文字列置換を意味するネストしたreplace()呼び出しの後に数値変換を行う。これがあることで、開発者は馴染みのある比較基準を得られる。ただし、文字の置換と完全な入力ポリシーの適用は別の仕事であるため、同等の動作には別途検証が必要となる。
したがって、短いベンチマーク要約を実装選択の正当化に使う開発者には重い責任がある。報告された順位を採用する前に、自分のアプリケーションのどこでパースが起きるかを特定する必要がある。また、自分の入力が生成されたデータセットに似ているかも判断しなければならない。
SQLite parse_number()では、ハーネスによってこうした問いを検証可能にしている。読者は、勝者を示す数値だけに頼らず、計時されたクエリとデータジェネレーターの両方を確認できる。
Cは検証と変換の統合で速度を得る
最適化されたC実装は、中間処理を減らしながら、ショートカットの制限を超える入力向けのフォールバックを維持している。
optimized decimal parserは、数値情報を蓄積しながら文字を検査する。この走査中に、有効桁を追跡し、小数点以下の桁数を数える。これにより、適切な入力ではクリーンな文字列として再構築する処理を回避できる。
そのショートカットは、Clingerの高速パスを用いる。これは中間値が正確な表現の要件を満たす場合に適用される十進数変換手法だ。実装は明示的な数値条件でこの経路を制限する。条件外の値は、より低速な変換経路を続行する。
この区別は不可欠である。単に丸め誤差を受け入れるショートカットは、別のエンジニアリング上の問いに答えることになる。ここでコードは、意図した数値結果を保持しつつ、限定された入力集合を高速化しようとしている。
実装は最大19桁の有効桁を収集する。十進数のショートカットにはさらに、累積値が2^53以下であり、小数部が22桁以下であることが求められる。これらの境界が、直接計算を適用できる条件を決める。
より広い設計が、なぜ改訂によって比較が変わったかを説明している。検証にはすでに入力文字の確認が必要だ。その確認中に有用な数値情報を蓄積すれば、別個の変換段階が繰り返すはずの作業の一部を回避できる。
整数処理も同じ手法の恩恵を受ける。値が収まる場合、関数は蓄積した整数を直接返せる。その文字列をもう一度変換する前に、一時的なクリーン文字列を構築する必要はない。
フォールバックにも相当なエンジニアリング作業が残る。検証済みの数値テキストを再構築し、可能な場合はローカルバッファを使用し、必要に応じて追加ストレージを割り当てる。ここでの割り当てとは、演算のための一時メモリーを要求することであり、算術演算そのものを超える処理を導入する。
コードはまた、ショートカットを有効にする前にコンパイラーの浮動小数点評価動作を確認する。そのガードは、中間計算が異なる精度を使用する可能性に対処している。この詳細は、小さな算術最適化が見かけ上の行数を超える責務を負いうることを示す。
Rustは、候補が異なる作業配置を用いるため、依然として意味のある比較対象である。この調査は、同程度に特化したRustの書き換えが同じ計測結果を維持するとは立証していない。最適化されたCとこれらのRust版を比較することで、これらの候補に関する結果が確立される。
妥当な解釈は、特化がテストされたワークロードで成果を上げたというものだ。Cの書き換えは、以前は分離されていた処理を統合し、適格な値について一般的な変換ルーチンを回避する。この解釈は、実装で確認できる仕組みに従っている。
これはまた、言語の評判だけでは不完全な予測しかできない理由でもある。汎用ルーチンは幅広い入力空間で動作を提供する一方、特化コードはより狭い契約を対象にできる。性能は、実際に到来する入力に対して選ばれた経路がどれだけの作業を行うかにも依存する。
保守性の問題は直接続く。高速な実装を所有する者は、その適格条件、フォールバック動作、数値上の前提も所有する。測定された優位性が役立つのは、チームが後の変更を通じてこれらの性質を維持できる場合だけだ。
拡張機能のサイズとコード所有には異なる方向の力が働く
より小さなコンパイル済み成果物と、より単純な保守経路は別の基準であり、どちらも他方の代替にはならない。
プロジェクトのimplementation comparisonは、ストリップ済みCライブラリが14,408バイトであると報告している。対応するRust成果物は、rusqliteで320,864バイト、sqlite-loadableで298,464バイトとなっている。ストリッピングは、コンパイル済みファイルから特定のビルド情報を削除する。
同じレポートでは、単純なCは空白行とコメントを除いて75行、最適化Cは104行と数えている。Rustは36行または30行の統合コードに加え、共通の41行パーサーを使用する。rusqlite版では20個、sqlite-loadable版では62個の依存関係を報告している。
これらの測定は、異なる形の複雑さを表している。ソース長は目に見える実装量を捉え、依存関係数はビルドグラフの一部を捉える。どちらも、将来のバグを診断する難しさを直接測定するものではない。
短い関数は、複数のライブラリに分散した振る舞いに依存することがあります。より長く自己完結した関数は、アプリケーションチームがレビューすべき責任をコード内に集約できます。どちらを選ぶかは、チームがその責任をどこに置きたいかを問う必要があります。
C実装では、複数の責任が可視化されています。バッファ処理、整数範囲の検査、小数向けのショートカットはいずれもプロジェクト自身のソースに現れます。レビュアーはそれらを直接確認できますが、可視性が専門知識の必要性をなくすわけではありません。
Rustの言語機能とパース用ライブラリは、一部の手作業を減らせます。すでにRustで作業しているチームにとっては、可読性の向上につながる可能性があります。ただし、コンパクトな統合レイヤーであっても、入力ポリシー、データベースの動作、エラー処理についてレビューが必要です。
コンパイル後のサイズも別の問いを生みます。厳しく制約された配布物における追加ライブラリの意味は、すでに大きなアセットを含むアプリケーションの場合とは異なります。報告された差は測定可能ですが、実際上の重要性は配布環境に左右されます。
ファイルサイズも、実行時のメモリ消費を示す指標にはなりません。ディスク上のライブラリのバイト数は、データ処理中に発生するすべての割り当てを説明しません。最小のファイルを最小メモリの実装と見なすには、この比較では提供されていない測定が必要です。
同じ注意は依存関係の数にも当てはまります。依存関係が増えれば、ビルドや更新時の検討事項も増え得ますが、その数を数えても欠陥率は立証されません。同様に、依存関係が少ないからといって、アプリケーション側で所有するコードが保守しやすいとは限りません。
既存のCアプリケーションでは、コンパクトなC拡張が確立済みのコンパイルやレビュー慣行に適合するかもしれません。既存のRustアプリケーションでは、別個のCコンポーネントを導入すると、追加の所有・管理作業が発生する可能性があります。これらはベンチマークによって確立された結果ではなく、デプロイに関する判断です。
したがって、SQLite parse_number() は複数の独立した次元を持つ判断を提示します。報告された最適化済みC候補は、測定されたパース性能とコンパイル後サイズで先行しています。それでもチームは、専用の変換パスを維持することが、自らのスキルやリリースプロセスに適合するかを判断する必要があります。
有用な比較では、こうした次元を可視化したままにします。それらを単一の勝者に集約すれば、異なるチームが合理的に異なる選択を行える理由が隠れてしまいます。
テストの通過だけではデータ契約は確定しない
正しさには、実装同士が数値結果で一致するかどうかを超え、整形されたテキストにどのような意味を与えるかが含まれます。
比較レポートによると、すべてのバージョンが40件の明示的なケースと800万件のランダム比較に合格しました。一般にファジングと呼ばれるランダムテストでは、予期しない挙動を探すために入力を生成します。報告された結果は証拠を提供しますが、この記事のために独立して再現されたものではありません。
実装間の一致は、特に算術処理を書き換えた後には価値があります。テストされた入力空間内で明白な乖離が起こる可能性を低くできます。しかし、共有された一致だけでは、選択された挙動が特定のアプリケーションの業務ルールに合致するかは判断できません。
割合はこの違いをよく示します。パーセント記号を取り除けば表示上の数値は維持されますが、パーセンテージを比率として解釈するとスケールが変わります。アプリケーションは、どちらの挙動を正しいものとして扱う前にも、必要とする意味を決めなければなりません。
通貨表記も関連する問題を提起します。記号を取り除けばテキストを数値として処理しやすくなりますが、値が同じ単位を共有していることは保証されません。異なる通貨の金額をまとめるには、文字の削除だけでは得られない情報が必要です。
書式の慣習にも注意が必要です。カンマは、ある慣習では桁区切りを示し、別の慣習では小数点の境界を示します。固定された慣習を前提に設計されたパーサーでは、その期待を受け取るデータに付随させる必要があります。
これらは、Willisonの実装で確認された欠陥ではなく、仮説上の導入時の懸念です。パーサーが設計どおりに正確に動作していても、未知のデータセットには不適切となり得る理由を説明しています。正しさは、選択された契約から始まります。
数値精度もまた別の境界です。SQLiteの浮動小数点ドキュメントでは、REAL値がバイナリ64表現を使用し、近似値であることが説明されています。変換を行う言語にかかわらず、多くの10進小数はこの形式で正確に表現できません。
正しく丸められたパーサーは、表現可能な適切な値を選択します。しかし、それによって後続のすべての算術処理が正確になるわけではありません。正確な10進計算を必要とするアプリケーションは、表現形式と算術要件を別途評価する必要があります。
失敗時の処理にも同じ注意が必要です。存在しない、または不明な値を示すSQLiteのマーカーであるNULLを返すことで、テキストが無効な場合でも処理を継続できます。それでもアプリケーションは、拒否されたデータと有効な数値入力を区別する手段を保持すべきです。
そうしなければ、見た目には整った結果がデータ品質の問題を隠す可能性があります。合計を確認するユーザーは、期待されたすべてのレコードが意味のある形で寄与したかを知る必要があります。その懸念は、パーサーが契約に従っている場合でも、周辺アプリケーションに属します。
ベンチマーク自体も別の不確実性を導入します。ベースラインを差し引いた後の最小計測時間は、好条件下で測定された実行を表します。本番環境の競合、多様なデータセット、異なるコンピューティング環境における典型的な性能を確立するものではありません。
共有されたテスト結果から、普遍的な性能主張も導けません。異常に長い入力をより多く含むワークロードでは、フォールバックがより頻繁に実行される可能性があります。その場合、テストされた一般的な書式の値と比べ、作業量のバランスが変化します。
そのため、SQLite parse_number() を懐疑的に読むことには建設的な意義があります。結果は、特定の実装を検討し、その挙動を再現することを支持します。しかし、受け入れる書式を定義し、拒否されたレコードを保持し、想定するホストアプリケーションで結果を検証する必要性まではなくしません。
再現性、Rustの改訂、フォールバックの挙動を注視する
次に有用となる証拠は、挙動と測定手法を明示したまま比較の範囲を広げるものです。
最初に注視すべきシグナルは、独立した再現です。別のシステムで実行すれば、報告された順位がハードウェアやビルド環境の違いを越えてどの程度維持されるかを検証できます。完全なクエリ時間と算出されたパースコストの両方を報告すれば、その証拠は解釈しやすくなります。
結果が一致すれば、最適化済みCの優位性が元のセットアップを超えて広がるという確信が強まります。順位が異なれば結論は限定され、調査に値する別の要因が特定されます。環境と入力分布が文書化されていれば、どちらの結果でも比較は改善されます。
再現では、数値結果と計測時間の区別も維持すべきです。受け入れる入力を変更する高速な候補は、もはや同じ問いに答えていません。正しさの結果を性能とともに公開すれば、この境界を可視化できます。
2つ目のシグナルは、比較で明らかになった特定の作業に対処する改訂版Rust実装です。スキャンと変換戦略をより近づければ、実装構造とプログラミング言語の選択を分離しやすくなります。関連する問いは、同程度の特化によって測定上の差が縮まるかどうかです。
改訂版Rustが統合上の利点を保ちながら差を縮めるなら、当初の結果は、言語を選ぶ前にアルゴリズムを検討すべきだという、より強い根拠になります。最適化済みCが優位を維持するなら、開発者はこの特定コンポーネントについて、より確かな証拠を得られます。
これは提案された比較であり、発表済みのリリースや約束された結果ではありません。更新版が固定されたスケジュールで登場すると想定する理由はありません。その価値は、競合する実装をより直接的に比較可能にすることから生まれます。
3つ目のシグナルは、フォールバックの挙動に関する証拠です。最適化済みCのショートカットは適格な入力を素早く処理しますが、より広い入力分布では低速な経路が実行される頻度が変化する可能性があります。そうしたケースをテストすれば、この実装の実用的な動作範囲についてより多くが明らかになります。
この作業では、速度と同じくらい厳密に正しさを検証すべきです。長い数値文字列、不正なレコード、異なるホスト設定は、一般的な書式の値では表れない前提を露呈させる可能性があります。文書化された結果は、別の単独の見出し用計測値よりも強い確信をもたらします。
エンジニアリングチームにとっては、ソースと同じくらい周辺の証拠を保持することが重要です。ビルド設定、入力例、却下した代替案は、特定の実装が選ばれた理由を説明します。それらを検索可能なナレッジベースに保存すれば、将来のレビュアーはその判断を見直せます。
この調査の直近の価値は、すでに明確です。測定されたボトルネックが、具体的な改訂と結果の変化につながりました。まだ未解決なのは、その結果がどこまで一般化できるかです。今後の比較では、焦点を絞った実験を普遍的な言語の優劣論に変えることなく、証拠を拡張すべきです。
SQLite parse_number() を採用する前に、アプリケーションが実際に直面している判断を特定してください。代表的な入力を保持し、パーセンテージと無効なレコードの扱いを定義し、実行先となるホスト内で候補を比較します。完全なクエリ時間を、算出されたパース指標とともに保持し、好ましいコンポーネント結果がユーザー体験とのつながりを失わないようにしてください。専用の数値処理パスはフォールバックと同じ注意深さでレビューし、両方を再現するために必要なビルド詳細を保持します。有用な次の一歩は、この実装の利点があなたの要件でも維持されるかをテストすることです。あなたのアプリケーションで選択を決める制約は何でしょうか。繰り返し変換にかかる時間、配布サイズ、それともパーサーを維持するための労力でしょうか。



