- 2021/04/18 【Unity】【C#】StartsWith, EndsWith が遅いのなら、Equals も遅いのか?
- 2021/04/04 【Unity】【C#】文字列の内容をシャッフルする
« prev next »
【Unity】【C#】StartsWith, EndsWith が遅いのなら、Equals も遅いのか? 
2021/04/18 Sun [edit]
Unity の公式マニュアルにも掲載されているのだが(ただし、情報が少し古い[Unity5])、文字列比較の StartsWith, EndsWith は遅いというのは以前から言われていた。実際に、そのページにある「CustomStartsWith」と「CustomEndsWith」と、標準ライブラリの StartsWith, EndsWith の速度比較をしてみると、かなり遅い。
・文字列とテキスト - 非効率的なビルトイン String API
ただ、その理由を読んでいたら「比較にはローカライズ用のデフォルトカルチャが効いているから」という事らしい(解説には Equals では "encyclopedia", "encyclopædia" のような表記違いは等価とみなすとある)。
「あれ?それなら Equals も CustomEquals を作った方が良いのでは?」と思ったので、試しに作ってみた。といっても CustomStartsWith とほぼ同じなんだけどね。そしてその速度比較の結果を残しておこう。
(※) Unity 2019.4.21f1 [.NET 4.x] / Windows10(x64) で確認
●公式の CustomStartsWith を真似した単純な Equals
using System;
public static class Extentions //※名前は任意
{
/// <summary>
/// 公式の CustomStartsWith を真似した単純な Equals.
/// https://docs.unity3d.com/ja/current/Manual/BestPracticeUnderstandingPerformanceInUnity5.html
/// </summary>
public static bool CustomEquals(this string a, string b)
{
int aLen = a.Length;
if (aLen != b.Length)
return false;
int ap = 0;
while (ap < aLen && a [ap] == b [ap])
{
ap++;
}
return (ap == aLen);
}
}
また比較には、公式の解説にある「ローカライズ用の調整を必要としない文字列比較に StringComparison.Ordinal を推奨」ともあったので、ついでに加えてみた(また Equals ではオペレータの "==" も加えてある)。以下に比較結果を載せておこう。
●StartsWith, EndsWith, Equlas の標準と Ordinal オプションと Custom~ を 100000 回×10回実行して、平均値を計測
[StandardStartsWith] ave : 0.1930909 [sec]
[StandardStartsWithOrdinal] ave : 0.08576998 [sec]
[CustomStartsWith] ave : 0.08573665 [sec]
●EndsWith
[StandardEndsWith] ave : 1.135096 [sec]
[StandardEndsWithOrdinal] ave : 0.08583274 [sec]
[CustomEndsWith] ave : 0.0840721 [sec]
●Equals
[StandardEquals] ave : 0.09371759 [sec]
[StandardEqualsOrdinal] ave : 0.09588816 [sec]
[OperatorEquals] ave : 0.09638711 [sec]
[CustomEquals] ave : 0.09519003 [sec]
コンパイルは「.NET 4.x」でやってるからか、意外なことに、特に Equals では誤差範囲くらいしか変わらなかった(ただし、半角英数や日本語だけの場合。他の言語では違いは出るのかも?)。
Custom~ のメソッドには null チェックも付いてないので、それを追加したとしても大して変化は無い。結果を見てみると、どうやら .NET 4.x で日本語や英字ぐらいの範囲なら、StringComparison.Ordinal オプションを付ければ、実行速度は速いらしい。
もちろん文字列の内容や長さにもよるだろうが、私が試した限りでは、10~100文字程度で日本語・英語混合くらいなら、ほとんど誤差範囲でしか変わらない。なので「a.StartsWith(b, StringComparison.Ordinal)」「a.EndsWith(b, StringComparison.Ordinal)」「a.Equals(b, StringComparison.Ordinal) (※a.Equlas(b) でもなぜか変わらない)」を機械的に使っても、実質問題無いようだ。
まぁ、標準ライブラリ自体も改修されているからね。Unity の公式マニュアルは Unity5 時代で .NET 3.5 だったので、多少は変わったのかも知れない(実際に .NET 4.x になって GC 発生やコルーチンなども改修されてるとか何とか…)。
・GCの発生要因を減らす
特に古い情報の場合、一度現在のバージョン・環境で試してみるのも良いかもね。ちなみに Android でやってみても、同じような結果だった。現バージョンでは普段使いなら、Ordinal オプションを付けておけば、Custom~ は必要ないようだ。
(関連記事)
【Unity】【C#】文字列の内容をシャッフルする
【Unity】【C#】文字列の暗号化・復号化を簡単に行う
【C#】クラスのフィールド名を文字列の配列で取得する
【Unity】【C#】文字列の内容をシャッフルする 
2021/04/04 Sun [edit]
どちらかというと、デバッグ・テスト用になるかも知れないが、文字列の内容をランダムにシャッフルする拡張メソッドを作ってみよう。
実際私はよく実行速度の計測などをするのだが、同じデータ・同じ文字列などでテストするとメモリのキャッシュが効いてしまう可能性もあるので、なるべくランダムなデータを与えるようにしてる。
そういった用途のため簡単に作ったものなので、パフォーマンスはそこそこかも知れないが、以前に紹介した配列のシャッフルの拡張メソッドを使えばすぐにできるものなので、応用みたいな感じで紹介だけしておこう。
(※) Unity 2019.4.21f1 / Windows10(x64) で確認
●文字列の内容をシャッフルする 拡張メソッド
using System;
public static class Extentions //※名前は任意
{
/// <summary>
/// 文字列をシャッフルしたものを返す
/// 2021/04/04 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-395.html
/// </summary>
/// <param name="text">元の文字列</param>
/// <returns></returns>
public static string Shuffle(this string text)
{
var array = text.ToCharArray();
array.Shuffle(); //※以前の記事を参照
return new string(array);
}
}
●メインコード例
using System;
using UnityEngine;
var text = "abcあいう漢表示";
int length = 10;
for (int i = 0; i < length; i++)
{
var res = text.Shuffle();
Debug.Log($"[{i}] {res}");
}
[1] いあうb示c表a漢
[2] ab表あ示いcう漢
[3] うcあ漢ba示表い
[4] 示うbca表漢あい
[5] c示aいあ漢う表b
[6] 表ういabc漢あ示
[7] あい表うb漢示ac
[8] b示漢ac表ういあ
[9] baいう示c漢表あ
配列をシャッフルするコードは以前の記事をコピペして欲しい。
・配列・リストのシャッフル
配列・リストのシャッフルのランダム関数(Random)は Unity 用になっているので、標準関数で使いたいなら System の方の Random に書き換えて欲しい。
もし、UnityEngine と System で Random の曖昧エラーが出てるようなら、以下のように using で指定してやると簡単だ。
using Random = UnityEngine.Random;
ちなみにこの「using」の使い方の左辺の名前は何でも良いので、単に長いクラス名を短く表記したいときにも役立つ(using Rnd = UnityEngine.Random; みたいにも書ける)。
拡張メソッドの内容はなんてことはない、一度 string 型を char[] で取り出して、配列をシャッフルして、文字列に戻す、ということをしているだけだ。
テスト用ならこれでも十分だろう。私は Unityエディタ上でよく使っているが、十分なパフォーマンスは出るようだ。
まぁ、文字列が完全に決まっているなら、StringBuilder でシャッフル作るのも良いかも知れない。ランタイムでの文字列処理は GC を発生しやすいので、new をなるべく少なくするに越したことは無いからね。配列も使いまわしにすれば、更に効率的にもなる。ただ、その辺は別問題なので、自分で色々実装してみて欲しい(笑)。
(関連記事)
【Unity】【C#】配列・リストのシャッフル
【C#】2次元配列(ジャグ配列・多次元配列)のソート
【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など
【C#】配列やListなどの中身(要素)を見る拡張メソッド Dump
| h o m e |