9月26日、Daniel Lemireが「How fast can you fix a UTF-16 string in C#?」と題した記事を公開した。C#の標準的なUTF-16修復処理は、絵文字だらけの文字列を与えると0.4 GB/sまでスループットが落ちる——その原因を解明し、SIMD命令で53 GB/sまで引き上げる実装を紹介する内容だ。最大で約130倍の差が生じるというのは驚くべき数字だが、原因を知ると「なるほど」と腑に落ちる。
「不正なUTF-16文字列」とは何か
C#の文字列はUTF-16でエンコードされている。ほとんどの文字は16ビット1ワードで表現できるが、絵文字などBMP(基本多言語面、U+0000〜U+FFFF)外の文字はサロゲートペアと呼ばれる2ワードの組み合わせを使う。高サロゲート(U+D800〜U+DBFF)の直後に低サロゲート(U+DC00〜U+DFFF)が続く形が正しく、片方だけが現れた「孤立サロゲート」は不正(ill-formed)とみなされる。そのままディスクやネットワークに送り出すと、文字化けやパース失敗の原因になる。
Webフロントエンドでも同じ問題が存在し、JavaScriptにはES2024でString.prototype.toWellFormed()とisWellFormed()が追加されている(TC39プロポーザルとして2023年にStage 4到達)。孤立サロゲートをU+FFFD(置換文字)に置き換えたり、文字列が正形式かどうかを確認したりできるAPIだ。Lemireはこれと同等の機能を、C#ライブラリSimdUnicodeにPull Request #54として追加した。
SimdUnicodeはLemireらが開発するオープンソースライブラリで、UTF-8・UTF-16処理をSIMD命令で高速化することを目的としている。.NETランタイムへの採用も視野に入れて開発が進められており、今回のアルゴリズムはV8(ChromeのJavaScriptエンジン)に提供したものと同一だ。
string s = UTF16.ToWellFormed(input); // 既に正形式なら同じインスタンスを返す(アロケーションなし)
bool ok = UTF16.IsWellFormed(span);
入力が既に正形式であればToWellFormedはコピーせずそのまま返す設計になっており、ホットパスでの不要なアロケーションを避けられる。
標準実装のどこが遅いのか
C#で素直に修復処理を実装すると、以下のようなコードになる。
static void Repair(ReadOnlySpan<char> input, Span<char> output)
{
input.CopyTo(output);
int i = NextError(output, 0);
while (i >= 0)
{
output[i] = '\uFFFD';
i = NextError(output, i + 1);
}
}
static int NextError(ReadOnlySpan<char> s, int start)
{
int i = start;
while (true)
{
int k = s.Slice(i).IndexOfAnyInRange('\uD800', '\uDFFF');
if (k < 0) return -1;
i += k;
if (char.IsHighSurrogate(s[i]) && i + 1 < s.Length && char.IsLowSurrogate(s[i + 1]))
i += 2;
else
return i;
}
}
IndexOfAnyInRange自体はランタイムが最適化しているが、サロゲートペアのみで構成された文字列(絵文字だらけの場合)では致命的に遅くなる。正しいサロゲートペアを検出するたびに2文字スキップして再検索を繰り返すため、ループのたびにSIMD最適化済みの関数を呼び出し直すオーバーヘッドが積み重なり、スループットが激減する。いわば「速い道具を毎回しまって取り出す」を繰り返しているようなイメージだ。
SimdUnicodeの実装はIntel AVX-512やARM NEONのSIMD命令を使い、一度に複数の16ビット値を並列比較することでこの問題を回避している。サロゲート範囲の検出・ペア判定・U+FFFD置換の一連の処理を、1回のベクトル演算でまとめて処理できるため、関数呼び出しのオーバーヘッドが生じない。
ベンチマーク結果
検証(IsWellFormed)

Intel Xeon Gold 6548N(Emerald Rapids、AVX-512搭載) での測定結果:
- ラテン文字(サロゲートなし):SimdUnicode 69 GB/s vs
IndexOfAnyInRange33 GB/s(約2倍) - 絵文字のみの文字列(全てサロゲートペア):ランタイム実装が 0.4 GB/s まで落ちる一方、SimdUnicodeは 53 GB/s を維持(約130倍)

Apple M4 Max でも傾向は同じだ。AVX-512を持たないARM環境ではIntelほど差は大きくないが、SimdUnicodeが優位を保っている。
修復(ToWellFormed)


修復処理は全コードユニットを出力バッファに書き出す(正しければそのまま、不正ならU+FFDに置き換え)。入力が正形式であれば実質的にメモリコピーと同等の処理になる。ベンチマークでもコピーとほぼ同速度という結果が出ており、修復の有無によるオーバーヘッドは極めて小さい。
査読済み論文としても公開
このアルゴリズムはLemireとClauseckerによる査読済み論文「Fixing ill-formed UTF-16 strings with SIMD instructions」(Software: Practice and Experience、2026年)として発表されており、arXiv版も公開されている。arXivへの投稿は2026年1月で、その後SimdUnicodeへの実装、今回の記事という流れだ。実装からアルゴリズムの理論的裏付けまでを一貫して確認できる点も、このプロジェクトの強みといえる。
測定環境は.NET SDK 10.0.400(Linux)/ 10.0.103(macOS)、Intel Xeon Gold 6548N(Emerald Rapids)、Apple M4 Max。
詳細はHow fast can you fix a UTF-16 string in C#?を参照していただきたい。