強火で進め

このブログではプログラム関連の記事を中心に書いてます。

ベストプラクティスガイドの Strings and text の内容を日本語で紹介

文字列とテキスト

Unity ドキュメントベストプラクティスガイド( Best practice guides )の Strings and text の内容を日本語で紹介。

Unity - Manual: Strings and text
https://docs.unity3d.com/Manual/BestPracticeUnderstandingPerformanceInUnity5.html

  • 文字列連結は StringBuilder を使う

※これは Unity に限らず C# プログラムでよく言われる事ですね。 s += "abc" などとすると内部的には新しい string が生成される処理が走るのでコストが増えるのでダメってやつです。

ロケール強制と順序比較

  • String.Equals() はロケールを考慮した処理が行われるので注意

例えば以下の様に pe の部分が微妙に異なる文字列を比較していますがロケールが US-English の場合には true を返しますがヨーロッパのロケールが設定されている場合には false を返すそうです。
※つまり内部で複雑な処理が行われている。

String.Equals("encyclopedia", “encyclopædia”);

注: Unity はバージョン 5.3 と 5.4 の時点で、 Unity のスクリプティングランタイムは常に英語(US-en)ロケールで実行される。

ほとんどの文字列比較では C や C++ と同様の比較で問題無い場合が多い。その様な比較は StringComparison.Ordinal で実現可能。

myString.Equals(otherString, StringComparison.Ordinal);

【文字列検索処理のベンチマーク結果】

Method Time (ms) for 100k short strings
String.StartsWith, default culture 137
String.EndsWith, default culture 542
String.StartsWith, ordinal 115
String.EndsWith, ordinal 34
Custom StartsWith replacement 4.5
Custom EndsWith replacement 4.5

culture(カルチャー、ロケール)を使った時が一番遅く、使わなかった時がそこそこ、Custom(自前で準備した場合)が一番高速という結果。

EndsWith と StartsWith のカスタムメソッドの作例。

    public static bool CustomEndsWith(string a, string b) {
        int ap = a.Length - 1;
        int bp = b.Length - 1;

        while (ap >= 0 && bp >= 0 && a [ap] == b [bp]) {
            ap--;
            bp--;
        }
        return (bp < 0 && a.Length >= b.Length) || 

                (ap < 0 && b.Length >= a.Length);
        }

    public static bool CustomStartsWith(string a, string b) {
        int aLen = a.Length;
        int bLen = b.Length;
        int ap = 0; int bp = 0;

        while (ap < aLen && bp < bLen && a [ap] == b [bp]) {
        ap++;
        bp++;
        }

        return (bp == bLen && aLen >= bLen) || 

                (ap == aLen && bLen >= aLen);
    }

正規表現

  • シンプルな IsMatch ですら内部処理で大きなデータ割当てが走る為、初期化中を除いて許容できないと考えるべき。
  • Regex.Match や Regex.Replace などの静的メソッドはオンザフライで正規表現コンパイルし、生成されたオブジェクトもキャッシュしないので強く非推奨。

例)

Regex.Match(myString, "foo");

これは実行するたびに5KBのゴミ(ガベージコレクションの対象になるもの)が発生。

var myRegExp = new Regex("foo");
myRegExp.Match(myString);

この場合は320バイトのゴミが発生。

XMLJSON、その他の長文テキストの解析

  • 多くのサードパーティーのパーサーは、リフレクションに基づいて構築されている。リフレクションは開発中に優れた選択肢ですが(パーサがデータレイアウトの変更に迅速に対応できるため)、遅い。
  • Unity はその解決策として JSONUtility API を準備。
    • これは純粋な C# JSONパーサよりも速い。
    • 追加コードなしで辞書など多くの複雑なデータ型をシリアライズすることはできない。

代替案は以下の3つ

1:ビルド時に解析する

データを ScriptableObject に移動し、 AssetBundle を介して配布する。
この辺りの話については以下の動画(英語)を参照

この戦略は可能な限り最良のパフォーマンスを提供するが、動的に生成する必要のないデータにのみ適している。ゲームデザインのパラメータやその他のコンテンツに最適。

2:スプリットおよび遅延ロード

解析すべきデータを小さな単位に分割し、必要になった時点でロードさせる。

3:スレッド

完全に C# のオブジェクトしか使用せず、 Unity API を使用しない場合には、解析操作をワーカースレッドに移す事ができる。
スレッドを使用する場合に使用するクラスは Thread や ThreadPool 。

Thread クラス (System.Threading)
https://msdn.microsoft.com/ja-jp/library/system.threading.thread%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

ThreadPool クラス (System.Threading)
https://msdn.microsoft.com/ja-jp/library/system.threading.threadpool(v=vs.110).aspx

方法 : スレッドを作成および終了する (C# プログラミング ガイド)
https://msdn.microsoft.com/ja-jp/library/7a2f3ay4(v=vs.90).aspx