
2025/12/16 1:42
Full Unicode Search at 50× ICU Speed with AVX‑512
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
改善された要約
StringZilla は、AVX‑512(およびその他の SIMD バックエンド)を搭載した CPU 上で Unicode/UTF‑8 の大文字小文字を区別しない部分文字列検索と関連するトークン操作を劇的に高速化する無料のヘッダーオンリ―ライブラリです。Unicode 17 の完全なケースフォールディング規則(「ß → ss」のような展開や「ffi → ffi」のような合字を含む)を実装し、25 種類の空白トークン、9 種類の改行バリアント、およびスクリプトごとのカーネル(ASCII、西ヨーロッパ語、中央ヨーロッパ語、キリル文字、ギリシャ語、アルメニア語、ベトナム語など)をサポートします。警報ロジックは不安全な文字に対して安全経路へフォールバックし、安全ウィンドウ検索戦略で SIMD スキャン用の ≤16 バイト部分文字列を見つけ、周囲のコンテキストを検証して展開を処理します。
ライプツィヒ Wikipedia データセットでのベンチマークでは、トークナイズ/小文字化に対して直列コードより 10–20 倍の高速化、フォールド・アンド・スキャンパイプラインでは 50–150 倍(PCRE2 と比較して最大約 20,000 倍)を達成しています。StringZilla はケースフォールディングに対して入力より少なくとも 3 倍大きい出力バッファを必要とし、コードポイントではなくバイトオフセット/長さを返します。また、反復検索用のイテレータ形式も提供しています。Apache 2.0 ライセンスの下でヘッダーオンリ― C/C++ として配布され、多くの CPU アーキテクチャ向けにプリコンパイル済みのホイールが用意され、GPU/CPU 並列拡張もオプションとして利用可能です。CPython、Rust、Swift、Node.js、Go などへのバインディングが存在し、ウェブサーバー、データベース、テキスト解析パイプラインに展開して CPU 使用率とレイテンシを削減しながら、多言語データの豊富な処理をサポートします。
今後の計画としては、ジョージア文字、韓国語、およびその他のスクリプト向けの専用 SIMD カーネル、ARM NEON(128‑bit)対応、および現在ベースラインに遅れを取っている言語へのさらなる最適化が含まれます。
本文
今年作った最も醜いが実用的なオープンソースライブラリ
私は StringZilla をご紹介できることをうれしく思います。これは AVX‑512 に対応した、非常に高速化された Unicode ツールキットで、一般的な UTF‑8 操作を 速く かつ 正確に 行うことができます。
以下は元の記事を整理したものです。改行や不要な記号は削除しています。
1. なぜ重要なのか
- 2024 年時点で、インターネット上の約 98 % が UTF‑8 を採用しています。
- UTF‑8 は 100 万文字以上を表現できますが、その扱いは決して簡単ではありません。
- Chrome・Chromium や OS カーネルなど多くのライブラリは ICU に Unicode サポートを委ねていますが、ICU には高速な大文字小文字不敏感部分文字列検索機能が欠けています。
StringZilla はそのギャップを埋めます。以下の機能を提供します:
| 機能 | v4.x 以降 |
|---|---|
| 行または空白文字(25 種類、9 つの改行バリエーション)でテキストをトークン化 | 他と比べて 10 倍高速 |
| 大文字小文字変換(1400+ Unicode ルール、ロケール非依存) | 10 倍高速 |
| 大文字小文字不敏感部分文字列検索(事前に折りたたむ必要なし) | 20–150 倍高速(PCRE2 の と比べ最大 20 000 倍) |
2. UTF‑8 の概要
UTF‑8 は可変長エンコーディングです:
U+0000–U+007F : 1 バイト 0xxxxxxx U+0080–U+07FF : 2 バイト 110xxxxx 10xxxxxx U+0800–U+FFFF: 3 バイト 1110xxxx 10xxxxxx 10xxxxxx U+10000–U+10FFFF: 4 バイト 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
解析には逐次アクセス(
i → i+1)が必要で、ベクトル化が難しいです。最新の CPU でもバイト単位で処理する必要がありますが、StringZilla は巧妙な SIMD カーネルでこのボトルネックを克服します。
3. 現代言語における Unicode
| 言語 | デフォルト文字列型 | 備考 |
|---|---|---|
| Rust | (有効な UTF‑8) | バイト単位でインデックス |
| Go | (バイト列) | |
| Swift | ネイティブ UTF‑8 (5 以降) | グラフェムクラスターを公開 |
| Java | UTF‑16 | BMP を超える文字はサロゲートペア |
| C#/.NET | UTF‑16 | 常に ≥ 2 バイト |
| Python | Unicode () | 内部で UCS‑4、 モジュール利用可能 |
これらの違いから、UTF‑8 を生データとして処理する必要があるライブラリは、Python の
stringzilla や Rust の stringzilla などのバインディングを提供しています。
4. 大文字小文字変換と部分文字列検索
展開例
- ドイツ語
→ “ss”ß - トルコ語
→ “i̇”(小文字 i + 組み合わせ点)İ - 合字(
)→ “fi”fi
これらの展開により、1 つのコードポイントが複数バイトになる場合があります。
安全ウィンドウ手法
- 短く安全なニードルスライス を見つける。
- 折りたたみ後で ≤ 16 バイトに収まるもの。
- 何らかの「アラーム」(展開・合字・ケルビン記号)を起こさないもの。
- このウィンドウを SIMD スキャンで検索。
- ヒットがあれば、ヘッド+テール全体をスカラーで検証。
これにより SIMD のスループットを保ちつつ正確性も保証します。
5. 性能
| データセット | 言語 | ベースライン (GB/s) | StringZilla (GB/s) | 利得 |
|---|---|---|---|---|
| Eng | C++ (AVX‑512) | 1.15 | 10.93 | 9.9× |
| Ger | Rust | 0.749 | 13.61 | 13.6× |
| Vie | Go | 0.416 | 17.97 | 17.9× |
| Rus | Python | 0.543 | 10.61 | 10.6× |
すべての数値は AVX‑512 対応の AMD Zen 5 インスタンスで測定。
ICU や PCRE2 と比較して、StringZilla は「純粋な大文字小文字不敏感検索」で 50–100 倍高速です。またすべての Unicode エッジケースに対応しています。
6. StringZilla の使い方
C / C++
#include <stringzilla/stringzilla.h> char src[] = "Straße"; char dst[64]; // ≥ 3 × source length size_t folded_len = sz_utf8_case_fold(src, strlen(src), dst); const char *haystack = "Der große Hund"; const char *needle = "GROSSE"; sz_size_t hay_len = strlen(haystack); sz_size_t nee_len = strlen(needle); sz_utf8_case_insensitive_needle_metadata_t meta = {}; sz_size_t match_len; char const *match = sz_utf8_case_insensitive_find( haystack, hay_len, needle, nee_len, &meta, &match_len );
C++ (string views)
namespace sz = ashvardanian::stringzilla; sz::string_view text = "Der große Hund"; auto [offset, length] = text.utf8_case_insensitive_find("GROSSE"); sz::utf8_case_insensitive_needle pattern("STRASSE"); auto match = sz::string_view("Straße").utf8_case_insensitive_find(pattern);
Python
import stringzilla as sz # One‑liner search offset = sz.utf8_case_insensitive_find("Der große Hund", "GROSSE") print(offset) # 4 (byte offset) # Iterator for repeated searches for match in sz.utf8_case_insensitive_find_iter( "Straße STRASSE strasse", "strasse"): print(match, match.offset_within("Straße STRASSE strasse"))
Rust
use stringzilla::stringzilla::{utf8_case_insensitive_find, Utf8CaseInsensitiveNeedle}; assert_eq!(utf8_case_insensitive_find("Straße", "STRASSE"), Some((0, 7))); let needle = Utf8CaseInsensitiveNeedle::new(b"STRASSE"); assert_eq!(utf8_case_insensitive_find("strasse", &needle), Some((0, 7)));
Swift
import StringZilla let haystack = "Der große Hund" if let range = haystack.utf8CaseInsensitiveFind(substring: "GROSSE") { print(haystack[range]) // "große" }
Node.js
const sz = require('stringzilla'); const text = Buffer.from("Straße"); const patternBytes = Buffer.from("STRASSE"); console.log(sz.utf8CaseInsensitiveFind(text, patternBytes)); // { index: 0n, length: 7n }
7. インストール
| プラットフォーム | コマンド |
|---|---|
| Python | |
| Rust | |
| Swift | SwiftPM の依存関係に追加 |
| Node.js | |
主要アーキテクチャ向けにビルド済みバイナリが用意されています。GPU/CPU 並列化パッケージ(
, stringzilla-cpus
)もあります。stringzilla-cuda
8. 今後の計画
- ジョージア語、韓国語など他スクリプト向けの専用 SIMD カーネル
- ARM 対応(NEON/SVE) – 現在は 128‑bit ベクトルに限定
- 動的ディスパッチ遅延のさらなる低減
StringZilla は Apache 2.0 ライセンスで GitHub 上で公開されています:https://github.com/ashvardanian/StringZilla
コーディングを楽しんでください!