23

ILSpyは、String.IsNullOrEmptyがで実装されていることを示していString.Lengthます。しかし、なぜString.IsNullOrEmpty(s)より速いのですs.Length == 0か?

たとえば、このベンチマークでは5%高速です。

var stopwatches = Enumerable.Range(0, 4).Select(_ => new Stopwatch()).ToArray();
var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] { s => s == String.Empty, s => s.Length == 0, s => String.IsNullOrEmpty(s), s => s == "" };
int count = 0;
for (int i = 0; i < 10000; ++i) {
    stopwatches[i % 4].Start();
    for (int j = 0; j < 1000; ++j)
        count += strings.Count(testers[i % 4]);
    stopwatches[i % 4].Stop();
}

(他のベンチマークも同様の結果を示しています。これは、私のコンピューターで実行されているcruftの影響を最小限に抑えました。また、余談ですが、空の文字列と比較したテストは、同じように約13%遅くなりIsNullOrEmptyました。)

さらに、なぜIsNullOrEmptyx86でのみ高速であるのに対し、x64String.Lengthでは約9%高速なのですか?

更新:テストセットアップの詳細:64ビットWindows 7、Intel Core i5プロセッサ、「コードの最適化」を有効にしてコンパイルされたコンソールプロジェクトで実行されている.NET4.0。ただし、「モジュールロードでのJIT最適化の抑制」も有効になっています(承認された回答とコメントを参照)。

最適化を完全に有効にすると、次のテストのように、デリゲートやその他のオーバーヘッドを削除したLength場合よりも約14%高速になります。IsNullOrEmpty

var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,,STU,V,,W,,X,,,Y,,Z,".Split(',');
int count = 0;
for (uint i = 0; i < 100000000; ++i)
    count += strings[i % 32].Length == 0 ? 1 : 0; // Replace Length test with String.IsNullOrEmpty
4

7 に答える 7

23

これは、Visual Studio内からベンチマークを実行したため、JITコンパイラがコードを最適化できないためです。最適化を行わないと、このコードはString.IsNullOrEmpty用に生成されます。

00000000   push        ebp 
00000001   mov         ebp,esp 
00000003   sub         esp,8 
00000006   mov         dword ptr [ebp-8],ecx 
00000009   cmp         dword ptr ds:[00153144h],0 
00000010   je          00000017 
00000012   call        64D85BDF 
00000017   mov         ecx,dword ptr [ebp-8] 
0000001a   call        63EF7C0C 
0000001f   mov         dword ptr [ebp-4],eax 
00000022   movzx       eax,byte ptr [ebp-4] 
00000026   mov         esp,ebp 
00000028   pop         ebp 
00000029   ret 

次に、長さ==0で生成されたコードと比較します。

00000000   push   ebp 
00000001   mov    ebp,esp 
00000003   sub    esp,8 
00000006   mov    dword ptr [ebp-8],ecx 
00000009   cmp    dword ptr ds:[001E3144h],0 
00000010   je     00000017 
00000012   call   64C95BDF 
00000017   mov    ecx,dword ptr [ebp-8] 
0000001a   cmp    dword ptr [ecx],ecx 
0000001c   call   64EAA65B 
00000021   mov    dword ptr [ebp-4],eax 
00000024   cmp    dword ptr [ebp-4],0 
00000028   sete   al 
0000002b   movzx  eax,al 
0000002e   mov    esp,ebp 
00000030   pop    ebp 
00000031   ret 

ご覧のとおり、 Length == 0のコードは、 String.IsNullOrEmptyのコードを実行するすべてのことを実行しますが、さらに、ブール値(長さの比較から返される)を愚かにブール値に変換しようとするため、String.IsNullOrEmptyよりも遅くなります。

最適化を有効にしてプログラムをコンパイルし(リリースモード)、Windowsから直接.exeファイルを実行する場合、JITコンパイラによって生成されたコードの方がはるかに優れています。String.IsNullOrEmptyの場合、次のようになります。

001f0650   push    ebp
001f0651   mov     ebp,esp
001f0653   test    ecx,ecx
001f0655   je      001f0663
001f0657   cmp     dword ptr [ecx+4],0
001f065b   sete    al
001f065e   movzx   eax,al
001f0661   jmp     001f0668
001f0663   mov     eax,1
001f0668   and     eax,0FFh
001f066d   pop     ebp
001f066e   ret

長さ==0の場合:

001406f0   cmp     dword ptr [ecx+4],0
001406f4   sete    al
001406f7   movzx   eax,al
001406fa   ret

このコードでは、結果は期待どおりです。つまり、Length==0はString.IsNullOrEmptyよりもわずかに高速です。

また、Linq、ラムダ式を使用し、ベンチマークでモジュロを計算することは、これらの操作が(文字列の比較に比べて)遅く、ベンチマークの結果が不正確になるため、あまり良い考えではないことにも言及する価値があります。

于 2012-04-28T09:45:56.283 に答える
4

ベンチマークは、String.IsNullOrEmptyとString.Lengthを測定するのではなく、関数に対して異なるラムダ式がどのように生成されるかを測定します。つまり、単一の関数呼び出し(IsNullOrEmpty)のみを含むデリゲートが、関数呼び出しと比較(Length == 0)を含むデリゲートよりも高速であることはそれほど驚くことではありません。

実際の呼び出しを比較するには、デリゲートなしで直接呼び出すコードを記述します。

編集:私の大まかな測定では、IsNullOrEmptyを使用したデリゲートバージョンは他のバージョンよりもわずかに高速ですが、同じ比較への直接呼び出しは逆の順序です(余分なコードの数が大幅に少ないため、約2倍高速です)。結果は、マシン間、x86 / x64モード間、およびランタイムのバージョン間で警告される可能性があります。実用的な目的で、LINQクエリで使用する必要がある場合は、4つの方法すべてがほぼ同じであると考えます。

全体として、これらの方法を選択することで実際のプログラムに測定可能な違いがあるとは思えないので、最も読みやすい方法を選択して使用してください。条件で==/!=を間違える可能性が少ないため、私は一般的にIsNullOrEmptyを好みます。

タイムクリティカルなコードから文字列操作を完全に削除すると、これらの選択肢から選択することで、より大きなメリットが得られます。また、クリティカルなコードのLINQを削除することもできます。いつものように、実際のシナリオで全体的なプログラム速度を測定するようにしてください。

于 2012-04-28T04:53:28.760 に答える
1

私はあなたのテストが正しくないと信じています:

このテストは、追加のnullチェックを実行するためstring.IsNullOrEmptyよりも常に遅いことを示しています。s.Length==0

var strings = "A,B,,C,DE,F,,G,H,,,,I,J,,K,L,MN,OP,Q,R,STU,V,W,X,Y,Z,".Split(',');
var testers = new Func<string, bool>[] { 
    s => s == String.Empty, 
    s => s.Length == 0, 
    s => String.IsNullOrEmpty(s), 
    s => s == "" ,
};
int n = testers.Length;
var stopwatches = Enumerable.Range(0, testers.Length).Select(_ => new Stopwatch()).ToArray();
int count = 0;
for(int i = 0; i < n; ++i) { // iterate testers one by one
    Stopwatch sw = stopwatches[i];
    var tester = testers[i];
    sw.Start();
    for(int j = 0; j < 10000000; ++j) // increase this count for better precision
        count += strings.Count(tester);
    sw.Stop();
}
for(int i = 0; i < testers.Length; i++)
    Console.WriteLine(stopwatches[i].ElapsedMilliseconds);

結果:

6573
5328
5488
6419

s.Length==0ターゲットデータにnull文字列が含まれていないことが確実な場合に使用できます。それ以外の場合は、を使用することをお勧めしますString.IsNullOrEmpty

于 2012-04-28T05:01:53.593 に答える
1

あなたはどこかでテストが間違っています。IsNullOrEmptyは、追加のnull比較操作を行い、長さをテストするため、定義上高速にすることはできません。

したがって、答えは次のようになります。テストのおかげで高速になります。ただし、コードでさえ、x86モードとx64モードの両方でIsNullOrEmptyが私のマシンで一貫して遅いことを示しています。

于 2012-04-28T04:52:45.563 に答える
0

IsNullOrEmpty他のすべての人が言ったように、それはnullのチェックも行うので、速くすることは不可能だと思います。しかし、高速であるかどうかにかかわらず、違いは非常に小さいのでIsNullOrEmpty、コードをより安全にするこの追加のnullチェックのために、これは使用にプラスになります。

于 2012-04-28T05:30:27.160 に答える
-2

CSharpの第10章「プロパティ」を介したCLRで、JeffRichterは次のように書いています

プロパティメソッドの実行には長い時間がかかる場合があります。フィールドアクセスは常にすぐに完了します。プロパティを使用する一般的な理由は、スレッドの同期を実行することです。これにより、スレッドが永久に停止する可能性があるため、スレッドの同期が必要な場合は、プロパティを使用しないでください。そのような状況では、方法が好まれます。また、クラスにリモートでアクセスできる場合(たとえば、クラスがから派生している場合System.MarshalByRefObject)、プロパティメソッドの呼び出しは非常に遅くなるため、プロパティよりもメソッドが優先されます。私の意見では、から派生したクラスMarshalByRefObjectはプロパティを使用しないでください。

したがって、String.LengthがプロパティでありString.IsNullOrEmpty、プロパティよりも高速に実行される可能性のあるメソッドであることがわかりますString.Length

于 2012-04-28T06:00:43.890 に答える
-4

関係する変数のタイプが原因である可能性があります。*空はブール値を使用しているようです。長さはintです(私は推測します)。

平和 !

  • : 編集
于 2012-04-28T03:44:29.920 に答える