All news
ASCodeworksReleases

SYNX 3.8 はどこでも速くなり、すべてのパーサーの答えがようやくそろいました

SYNX 3.8.0 は言語そのものには一切手を入れていません。変わったのは各実装の読み込み速度で、TypeScript では最大 9.6 倍、Rust では 2〜3.6 倍速くなりました。そして同じファイルに対して、どの実装も同じ答えを返すようになりました。

by APERTURESyndicate · 2026年10月17日

SYNX 3.8 はどこでも速くなり、すべてのパーサーの答えがようやくそろいました

SYNX 3.8.0 をリリースしました。設定ファイルを書いているだけなら、ファイルの書き方に新しいところは何もありません。文法は少しも動いていないからです。動いたのはその下にあるものすべてで、Rust のコア、TypeScript のパッケージ、Python と Node のバインディング、そして 7 つのネイティブ移植を、同じマシンと同じファイル群で 3.7.4 と比べて計測し、実際に時間を食っていた箇所を作り直しました。

どれくらい速くなったか

要点をフォーマットそのもので書くと、こうなります。

# 3.7.4 -> 3.8.0, same machine, same files
rust
  config_2_5kb     38.6us -> 17.6us
  static_4mb       60.1ms -> 35.3ms
  active_1_2mb     81.0ms -> 22.5ms
typescript_node
  config_2_5kb     34.0us -> 20.4us
  active_1_2mb    297.5ms -> 31.1ms
python
  config_2_5kb     50.3us -> 17.6us
  synxl_100k      173.7ms -> 83.2ms

いちばん伸びたのはマーカー付きの大きな文書を読む TypeScript で、理由はちょっと恥ずかしいものでした。:calc のキーごとに式を文書内の他のすべてのキーと照らし合わせ直していたので、処理量がファイルの大きさの二乗で増えていたのです。Rust では、効果の大半はメモリから来ています。Windows のシステムアロケーターでは、普通の解析が時間のほぼ半分をメモリの確保に使っていましたが、3.8.0 では確保の回数が静的なファイルで約 3 分の 1、アクティブモードで約 5 分の 1 になりました。バイナリ形式の .synxb も同じように見直し、文書でよく使われる文字列ほど短いインデックスを割り当てるようにしたため、ファイルは Rust で 3〜20%、TypeScript で最大 40% 小さくなりました。さらに同じ文書からは必ず同じバイト列が生成されるようになり、これは 3.7.4 では保証されていませんでした。

実はパーサー同士の答えが合っていなかった

ここは探していたわけではありません。速くしたコードが同じ結果を返すか確かめるために、ランダムに生成した同じ文書を Rust のコアと TypeScript のパッケージに読ませて結果を比べたところ、!active の文書の 17% で結果が食い違いました。TypeScript が間違っている場合も Rust が間違っている場合もあり、さらに Rust が実行のたびに違う間違え方をする場合までありました。オブジェクトをハッシュマップの順序でたどっていたため、仕様書にそのまま載っている tax:calc / total:calc の例が、およそ 2 回に 1 回 CALC_ERR で失敗していたのです。

一度探し始めると、ほかにも出てきました。リスト要素の中のマーカーは Rust ではまったく適用されていませんでした。Synx.stringify と .synxb は書き戻すときにデータを変えてしまい、"007" は数値の 7 として戻り、|+ のコードブロックはインデントを失い、ランダムな 3,000 文書のうち 1,777 文書が往復で元に戻りませんでした。プロセスごと落とせる文書まであり、:clamp:nan:5 では Rust がパニックし、自分自身を読み込むファイルではスタックがあふれました。これら 55 件はすべて修正済みで、最終的なコードでは Rust のコアと TypeScript が、生成した 70,000 文書すべてで一致しています。

7 つの移植もすべて 3.8.0 に

SYNX には Go、Java、.NET、Dart、C++、Swift、Godot 向けのネイティブパーサーもあり、こちらは数リリース分遅れていました。今はすべて 3.8.0 になり、同じ方法で Rust のコアと、それぞれ数千の生成文書で照合して、差分はもう残っていません。変化の大きさはまちまちで、C++ と Swift の移植は 1.2 MB のアクティブ文書に 88 秒と 47 秒かかっていたのが 33 ミリ秒と 95 ミリ秒になり、Godot のアドオンはそもそも Godot 4.7 でコンパイルすら通っていませんでした。

アップデートについて

.synx ファイルを変更する必要はありません。パッケージは npm の @aperturesyndicate/synx-format、crates.io の synx-core、PyPI の synx-format で、移植はそれぞれのいつものレジストリから届きます。Rust から synx-core を直接使っている場合、オブジェクトは HashMap ではなく順序付きの IndexMap になりました。自前で HashMap を組み立てて包んでいるコードには .collect() が必要ですが、get や反復で読むだけのコードはそのままコンパイルできます。

フォーマットでまだ表現できないものがひとつあります。リスト要素の中のリストのあとに次の要素が続く場合、書き出して読み戻すと元に戻りません。SYNX が一度に開いておけるリストはひとつだけだからです。これは 4.0 で対応する予定で、あわせてコンパクト文字列も入ります。コアのラボ用コピーでは、これだけで解析がさらに 1.2〜1.5 倍速くなりました。

すべての変更と数値は 変更履歴 と GitHub の 3.8.0 リリース にあります。

Other stories

All stories
SYNX 3.8 はどこでも速くなり、すべてのパーサーの答えがようやくそろいました | APERTURESyndicate