Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!

.net 4 client
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
524288 11108 1 : 1.0 1 : 1.5 1 : 5.8 1 : 1.5 1 : 1.5
690001 13547 1 : 1.0 1 : 1.6 1 : 6.2 1 : 1.6 1 : 1.6
908093 18188 1 : 1.0 1 : 1.6 1 : 6.1 1 : 1.6 1 : 1.6
1195118 23089 1 : 1.0 1 : 1.7 1 : 6.3 1 : 1.7 1 : 1.7
1572863 29947 1 : 1.0 1 : 1.7 1 : 6.5 1 : 1.7 1 : 1.7
.net 4.5.1
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
524288 11048 1 : 1.0 1 : 1.6 1 : 5.9 1 : 1.6 1 : 1.6
690001 14056 1 : 1.0 1 : 1.6 1 : 6.1 1 : 1.6 1 : 1.6
908093 17823 1 : 1.0 1 : 1.6 1 : 6.3 1 : 1.6 1 : 1.6
1195118 23763 1 : 1.0 1 : 1.6 1 : 6.2 1 : 1.6 1 : 1.6
1572863 30210 1 : 1.0 1 : 1.7 1 : 6.3 1 : 1.7 1 : 1.7 Компиляция изначально 32-битного приложения в 64 бита умножает необходимую для него память и снижает его производительностьРади интереса прогнал ваш код на x86-64:
.net 4.5.1
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
524288 8392 1 : 1.5 1 : 1.0 1 : 8.9 1 : 1.0 1 : 1.0
690001 11161 1 : 1.5 1 : 1.0 1 : 8.8 1 : 1.0 1 : 1.0
908093 14559 1 : 1.5 1 : 1.0 1 : 9.0 1 : 1.0 1 : 1.0
1195118 18972 1 : 1.5 1 : 1.0 1 : 9.0 1 : 1.0 1 : 1.0
1572863 24114 1 : 1.6 1 : 1.0 1 : 9.3 1 : 1.0 1 : 1.0 IStructuralEquatable) можно объяснить только серьёзным изъяном в тестировании. Это я не говорю о том, что методика изначально неправильная — ни квантилей, ни прогрева кэша, ни сборки мусора…SequenceEqual/IStructuralEquatable в варианте x86-64 стали показывать совершенно другие результаты. Сходу не могу это объяснить :)Console.ReadKey()) приаттачиться отладчиком к внешнему процессу.В что если в способе «PInvoke, для CRT функции memcmp()» тоже использовать fixed + unsafe и уже передавать в memcmp непосредственно указатели?Ничего особенного не случится. Для массива байт маршаллер и так фиксирует (pin) массив в памяти и передаёт в unmanaged-код обычный указатель.
memcmp() и RtlCompareMemory()Размер массива Минимальное время Unsafe MemCMP RtlCompareMemory Простейший метод
1 279 1 : 001,7 1 : 003,2 1 : 008,8 1 : 001,0
1 278 1 : 001,8 1 : 003,2 1 : 008,9 1 : 001,0
2 325 1 : 001,3 1 : 002,9 1 : 006,1 1 : 001,0
4 374 1 : 001,1 1 : 002,6 1 : 009,0 1 : 001,0
7 422 1 : 001,0 1 : 002,5 1 : 007,0 1 : 001,1
12 426 1 : 001,0 1 : 002,5 1 : 006,9 1 : 001,7
19 490 1 : 001,0 1 : 002,2 1 : 006,0 1 : 001,9
32 560 1 : 001,0 1 : 002,0 1 : 005,3 1 : 002,7
54 622 1 : 001,0 1 : 002,0 1 : 005,4 1 : 003,8
89 802 1 : 001,0 1 : 001,7 1 : 004,3 1 : 004,7
147 1092 1 : 001,0 1 : 001,5 1 : 003,7 1 : 004,0
242 1571 1 : 001,0 1 : 001,4 1 : 003,0 1 : 004,2
398 2328 1 : 001,0 1 : 001,4 1 : 002,7 1 : 004,5
657 3664 1 : 001,0 1 : 001,2 1 : 002,2 1 : 004,6
1082 5519 1 : 001,0 1 : 001,2 1 : 002,1 1 : 005,0
1782 8554 1 : 001,0 1 : 001,2 1 : 002,1 1 : 005,3
2936 13520 1 : 001,0 1 : 001,2 1 : 002,0 1 : 005,5
4837 21771 1 : 001,0 1 : 001,2 1 : 002,0 1 : 005,6
7967 35464 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7
13124 58073 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7
21618 96457 1 : 001,0 1 : 001,2 1 : 001,9 1 : 005,7
35610 167952 1 : 001,0 1 : 001,1 1 : 001,8 1 : 005,4
58656 285282 1 : 001,0 1 : 001,1 1 : 001,8 1 : 005,3
96617 534712 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,7
159146 924569 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,5
262143 1520968 1 : 001,0 1 : 001,1 1 : 001,6 1 : 004,5

cmps*). while
(
( 0 != ecx )
& ( 0 == Compare( (DWORD) RAM[esi], (DWORD) RAM[edi] ) )
)
{
--ecx;
edi += 4;
esi += 4;
}
77A52780 /$ 56 push esi ; сохранить значения регистров в стеке
77A52781 |. 57 push edi
77A52782 |. FC cld ; DF <-- 0
77A52783 |. 8B7424 0C mov esi, [esp+0C] ; esi <-- Source1
77A52787 |. 8B7C24 10 mov edi, [esp+10] ; edi <-- Source2
77A5278B |. 8B4C24 14 mov ecx, [esp+14] ; ecx <-- SIZE_T
77A5278F |. C1E9 02 shr ecx, 2 ; ecx <-- (ecx >> 2) ( делим счётчик на 4)
77A52792 |.- 74 04 jz short smaller_4 ;
77A52794 |. F3:A7 repe cmpsd ; В цикле по ecx сравниваем DWORD'ы;
77A52796 |.- 75 16 jne short not_zero ; if ( !ZF ) goto not_zero;
smaller_4:
77A52798 |> 8B4C24 14 mov ecx, [esp+14] ; ecx <-- SIZE_T
77A5279C |. 83E1 03 and ecx, 00000003 ; ecx &= 3
77A5279F |.- 74 04 jz short return_SIZE_T ; if ( ZF ) goto return_SIZE_T;
77A527A1 |. F3:A6 repe cmpsb ; в цикле сравниваем байты
77A527A3 |.- 75 16 jne short found_difference
return_SIZE_T:
77A527A5 |> 8B4424 14 mov eax, [esp+14] ; eax <-- SIZE_T
77A527A9 |. 5F pop edi ; восстанавливаем значения регистров
77A527AA |. 5E pop esi
77A527AB |. C2 0C00 ret 0C ; return eax
not_zero:
77A527AE |> 83EE 04 sub esi, 4 ; esi -= 4
77A527B1 |. 83EF 04 sub edi, 4 ; edi -= 4
77A527B4 |. B9 04000000 mov ecx, 4 ; ecx -= 4
77A527B9 |. F3:A6 repe cmpsb ; в цикле сравниваем байты
found_difference:
77A527BB |> 4E dec esi ; --esi
77A527BC |. 2B7424 0C sub esi, [esp+0C] ; esi <-- (esi - Source1)
77A527C0 |. 8BC6 mov eax, esi ; eax <-- esi
77A527C2 |. 5F pop edi ; восстанавливаем значения регистров
77A527C3 |. 5E pop esi
77A527C4 \. C2 0C00 ret 0C ; return eax
The good news is that we do eliminate bounds checks for what we believe to be the most common form of array accesses: accesses that occur within a loop over all the indices of the loop. So, there is no dynamic range check for the array access in this program:
static void Test_SimpleAscend(int[] a) {
for (int i = 0; i < a.Length; i++)
a[i] = i; // We get this.
}
Unfortunately, we do not eliminate the range check for the descending version of this loop:
static void Test_SimpleDescend(int[] a) {
for (int i = a.Length — 1; i >= 0; i--)
a[i] = i; // We DO NOT get this.
}
буквально любым усложнением можно нарушить тонкую грань
public static unsafe bool CompareByteArrays(byte[] x, byte[] y)
{
if (x.Length != y.Length)
return false;
fixed (byte* xp = x, yp = y)
{
int* xip = (int*)xp, yip = (int*)yp;
int b = 0;
int s = sizeof(int);
var il = x.Length / s;
for (int i = 0; i < il; i++, b += s)
{
if (xip[i] != yip[i])
return false;
}
for (; b < x.Length; b++)
{
if (x[b] != y[b])
return false;
}
}
return true;
}
64 бита умножает необходимую для него память и снижает его производительность
result.SequenceEqualTime = result.IStructuralEquatableTime = result.PInvokeTime;Конечно же, это всё существенно меняет..net 4.5.1 x86
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
32768 17168 1 : 1.0 1 : 1.8 1 : 7.5 1 : 128.2 1 : 2267.8 .net 4.5.1 x86-64
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
32768 10878 1 : 2.2 1 : 1.0 1 : 13.6 1 : 286.5 1 : 2732.5 Simplest: if (p_BytesLeft.Length != p_BytesRight.Length)
00000000 push ebp
00000001 mov ebp,esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 mov edi,edx
00000008 mov ebx,dword ptr [ecx+4]
0000000b cmp ebx,dword ptr [edi+4]
0000000e je 00000017
{
return false;
00000010 xor eax,eax
00000012 pop ebx
00000013 pop esi
00000014 pop edi
00000015 pop ebp
00000016 ret
}
for (int i = 0; i < p_BytesLeft.Length; ++i) //вот так, без всяких хитростей мы байты сравниваем
00000017 xor esi,esi
00000019 test ebx,ebx
0000001b jle 00000039
{
if (p_BytesLeft[i] != p_BytesRight[i])
0000001d movzx eax,byte ptr [ecx+esi+8]
00000022 cmp esi,dword ptr [edi+4]
00000025 jae 00000043
00000027 cmp al,byte ptr [edi+esi+8]
0000002b je 00000034
return false;
0000002d xor eax,eax
0000002f pop ebx
00000030 pop esi
00000031 pop edi
00000032 pop ebp
00000033 ret
for (int i = 0; i < p_BytesLeft.Length; ++i) //вот так, без всяких хитростей мы байты сравниваем
00000034 inc esi
00000035 cmp ebx,esi
00000037 jg 0000001D
}
return true;
00000039 mov eax,1
0000003e pop ebx
0000003f pop esi
00000040 pop edi
00000041 pop ebp
00000042 ret
00000043 call 73039937
00000048 int 3for ограничивает индекс границами p_BytesLeft, но этот же самый индекс используется для доступа к p_BytesRight. Таким образом, jit-компилятор не может убрать проверку. Да, мы выше проверяем длины массивов на равенство, но jit-компилятор сильно ограничен в возможностях оптимизации, поскольку его главная задача — быстро компилировать msil.for, закэшировав длину массива, вы гарантированно ломаете оптимизации;Так там ведь проверяются длины массивов на равенство.Это не помогает :) А вообще, вот, советую к прочтению: Array Bounds Check Elimination in the CLR.
DoMeasurements() присутствует строка: result.SequenceEqualTime = result.IStructuralEquatableTime = result.PInvokeTime;
Из-за этого результаты и неверны. x1 += 2;
x2 += 2;x1, x2 — это int*, => вы передвинетесь на 8 байтов.E:\GitHub\Best-Way-to-Compare-Byte-Arrays-in-.Net\Benchmarks\bin\Release>Bench
rks.exe 1204000
BenchmarkCompetition: start
***** Simplest: start *****
WarmUp:
Ticks: 7706 ms: 2
Ticks: 4806 ms: 1
Ticks: 4700 ms: 1
Ticks: 4739 ms: 1
Stats: MedianTicks=4772, MedianMs=1, Error=63.96%
Result:
Ticks: 4726 ms: 1
Ticks: 4784 ms: 1
Ticks: 4775 ms: 1
Ticks: 4736 ms: 1
Ticks: 4749 ms: 1
Ticks: 4798 ms: 1
Ticks: 4865 ms: 1
Ticks: 4716 ms: 1
Ticks: 4785 ms: 1
Ticks: 4797 ms: 1
Stats: MedianTicks=4779, MedianMs=1, Error=03.16%
***** Simplest: end *****
***** SequenceEqual: start *****
WarmUp:
Ticks: 47214 ms: 15
Ticks: 41995 ms: 13
Ticks: 40726 ms: 13
Ticks: 41666 ms: 13
Stats: MedianTicks=41830, MedianMs=13, Error=15.93%
Result:
Ticks: 40865 ms: 13
Ticks: 40682 ms: 13
Ticks: 40700 ms: 13
Ticks: 41486 ms: 13
Ticks: 43097 ms: 14
Ticks: 41157 ms: 13
Ticks: 41609 ms: 13
Ticks: 43426 ms: 14
Ticks: 41876 ms: 13
Ticks: 40337 ms: 13
Stats: MedianTicks=41321, MedianMs=13, Error=07.66%
***** SequenceEqual: end *****
***** IStructuralEquatable: start *****
WarmUp:
Ticks: 700334 ms: 231
Ticks: 706967 ms: 234
Ticks: 703792 ms: 233
Stats: MedianTicks=703792, MedianMs=233, Error=00.95%
Result:
Ticks: 700858 ms: 232
Ticks: 706920 ms: 234
Ticks: 699373 ms: 231
Ticks: 698729 ms: 231
Ticks: 697655 ms: 230
Ticks: 699388 ms: 231
Ticks: 699028 ms: 231
Ticks: 696725 ms: 230
Ticks: 702334 ms: 232
Ticks: 695562 ms: 230
Stats: MedianTicks=699200, MedianMs=231, Error=01.63%
***** IStructuralEquatable: end *****
***** Unsafe: start *****
WarmUp:
Ticks: 5533 ms: 1
Ticks: 619 ms: 0
Ticks: 588 ms: 0
Ticks: 485 ms: 0
Ticks: 481 ms: 0
Ticks: 479 ms: 0
Stats: MedianTicks=536, MedianMs=0, Error=1055.11%
Result:
Ticks: 553 ms: 0
Ticks: 474 ms: 0
Ticks: 622 ms: 0
Ticks: 475 ms: 0
Ticks: 551 ms: 0
Ticks: 683 ms: 0
Ticks: 479 ms: 0
Ticks: 480 ms: 0
Ticks: 476 ms: 0
Ticks: 480 ms: 0
Stats: MedianTicks=480, MedianMs=0, Error=44.09%
***** Unsafe: end *****
***** PInvoke: start *****
WarmUp:
Ticks: 2076 ms: 0
Ticks: 619 ms: 0
Ticks: 568 ms: 0
Ticks: 557 ms: 0
Ticks: 564 ms: 0
Stats: MedianTicks=568, MedianMs=0, Error=272.71%
Result:
Ticks: 567 ms: 0
Ticks: 628 ms: 0
Ticks: 558 ms: 0
Ticks: 364 ms: 0
Ticks: 362 ms: 0
Ticks: 454 ms: 0
Ticks: 364 ms: 0
Ticks: 362 ms: 0
Ticks: 438 ms: 0
Ticks: 571 ms: 0
Stats: MedianTicks=446, MedianMs=0, Error=73.48%
***** PInvoke: end *****
BenchmarkCompetition: finish
Competition results:
Simplest : 1ms
SequenceEqual : 13ms
IStructuralEquatable : 231ms
Unsafe : 0ms
PInvoke : 0ms
Error=1055.11%, это warm up, можно в общем-то не обращать внимания.Error=73.48% PInvoke::Result (или 44.09% Unsafe::Result) — это звоночек.P.S. не весь код из ваших примеров компилируется, не молги бы вы поправить его?
public void AddTask(string name, Action initialize, Action action, Action clean)
competition.AddTask("For",
() => { array = new int[ArraySize]; },
() => For(),
() => { array = null; });
competition.AddTask("Linq",
() => { array = new int[ArraySize]; },
() => Linq(),
() => { array = null; });
// Type: BenchmarkDotNet.BenchmarkCompetition
// Assembly: BenchmarkDotNet, Version=0.5.1.0, Culture=neutral, PublicKeyToken=null
// MVID: A1FDCA59-3DB1-4AC6-9C3F-8307BAA3104A
// Assembly location: E:\GitHub\Best-Way-to-Compare-Byte-Arrays-in-.Net\packages\BenchmarkDotNet.0.5.1\lib\net35\BenchmarkDotNet.dll
using System;
namespace BenchmarkDotNet
{
public class BenchmarkCompetition
{
public void AddTask(string name, Action initialize, Action action);
public void AddTask(string name, Action action);
public void Run();
public void PrintResults();
}
}
Размер массива Минимальное время Unsafe PInvoke Простейший метод SequenceEqual IStructuralEquatable
1 273 1 : 001,8 1 : 003,5 1 : 001,0 1 : 010,5 1 : 055,3
1 271 1 : 001,8 1 : 003,5 1 : 001,0 1 : 010,3 1 : 055,6
2 325 1 : 002,4 1 : 003,0 1 : 001,0 1 : 010,9 1 : 092,2
3 326 1 : 002,4 1 : 003,0 1 : 001,0 1 : 010,7 1 : 092,3
4 377 1 : 001,1 1 : 002,7 1 : 001,0 1 : 011,4 1 : 118,7
6 425 1 : 001,0 1 : 002,7 1 : 001,0 1 : 011,6 1 : 138,8
9 426 1 : 001,0 1 : 002,6 1 : 001,2 1 : 013,5 1 : 174,0
13 426 1 : 001,0 1 : 002,7 1 : 001,6 1 : 016,7 1 : 241,4
20 490 1 : 001,0 1 : 002,4 1 : 002,1 1 : 020,5 1 : 330,4
29 490 1 : 001,0 1 : 002,5 1 : 002,7 1 : 026,5 1 : 448,2
43 560 1 : 001,0 1 : 002,2 1 : 003,4 1 : 032,3 1 : 572,4
63 638 1 : 001,0 1 : 002,1 1 : 004,2 1 : 039,8 1 : 731,8
91 806 1 : 001,0 1 : 001,8 1 : 004,7 1 : 044,2 1 : 832,3
133 1012 1 : 001,0 1 : 001,7 1 : 003,9 1 : 052,5 1 : 960,5
195 1335 1 : 001,0 1 : 001,5 1 : 004,1 1 : 057,2 1 : 1066,8
284 1769 1 : 001,0 1 : 001,4 1 : 004,4 1 : 061,7 1 : 1172,5
414 2414 1 : 001,0 1 : 001,3 1 : 004,5 1 : 064,8 1 : 1247,9
603 3380 1 : 001,0 1 : 001,2 1 : 004,6 1 : 066,9 1 : 1292,7
879 4750 1 : 001,0 1 : 001,2 1 : 004,7 1 : 068,6 1 : 1343,9
1282 6423 1 : 001,0 1 : 001,2 1 : 005,0 1 : 073,8 1 : 1449,2
1868 8893 1 : 001,0 1 : 001,2 1 : 005,2 1 : 077,5 1 : 1526,0
2723 12667 1 : 001,0 1 : 001,2 1 : 005,4 1 : 079,0 1 : 1560,1
3969 18042 1 : 001,0 1 : 001,2 1 : 005,4 1 : 080,9 1 : 1598,8
5785 25872 1 : 001,0 1 : 001,2 1 : 005,5 1 : 082,1 1 : 1640,5
8431 37341 1 : 001,0 1 : 001,2 1 : 005,6 1 : 083,2 1 : 1645,5
12288 54115 1 : 001,0 1 : 001,2 1 : 005,6 1 : 083,6 1 : 1653,2
main 00540F14 call 001DC2F4 ESP=001AEE14
main 001DC2F4 mov eax, 1D38D8 EAX=001D38D8
main 001DC2F9 mov ebp, ebp
main 001DC2FB jmp 00540F40
main 00540F40 push ebp ESP=001AEE10
main 00540F41 mov ebp, esp EBP=001AEE10
main 00540F43 push edi ESP=001AEE0C
main 00540F44 push esi ESP=001AEE08
main 00540F45 push ebx ESP=001AEE04
main 00540F46 sub esp, 28 ESP=001AEDDC
main 00540F49 mov [ebp-18], eax
main 00540F4C xor ebx, ebx EBX=00000000
main 00540F4E mov [ebp-10], ebx
main 00540F51 mov [ebp-14], ebx
main 00540F54 mov esi, fs:[0E38] ESI=002F2AE0
main 00540F5B mov dword ptr [ebp-30], 645CABC8
main 00540F62 mov dword ptr [ebp-34], C28DAB21
main 00540F69 mov eax, [esi+0C] EAX=001AF09C
main 00540F6C mov [ebp-2C], eax
main 00540F6F mov [ebp-1C], ebp
main 00540F72 mov dword ptr [ebp-20], 0
main 00540F79 lea eax, [ebp-30] EAX=001AEDE0
main 00540F7C mov [esi+0C], eax
main 00540F7F xor ebx, ebx
main 00540F81 test ecx, ecx
main 00540F83 je short 00540F8F
main 00540F85 mov [ebp-10], ecx
main 00540F88 mov eax, ecx EAX=023B42E4
main 00540F8A add eax, 8 EAX=023B42EC
main 00540F8D mov ebx, eax EBX=023B42EC
main 00540F8F xor edi, edi EDI=00000000
main 00540F91 test edx, edx
main 00540F93 je short 00540F9F
main 00540F95 mov [ebp-14], edx
main 00540F98 mov eax, edx EAX=023B42E4
main 00540F9A add eax, 8 EAX=023B42EC
main 00540F9D mov edi, eax EDI=023B42EC
main 00540F9F mov eax, [ebp-18] EAX=001D38D8
main 00540FA2 mov eax, [eax+14] EAX=001D397C
main 00540FA5 mov ecx, [eax] ECX=75A97975
main 00540FA7 push dword ptr [ebp+0C] ESP=001AEDD8
main 00540FAA push dword ptr [ebp+8] ESP=001AEDD4
main 00540FAD push edi ESP=001AEDD0
main 00540FAE push ebx ESP=001AEDCC
main 00540FAF mov dword ptr [ebp-28], 0
main 00540FB6 mov [ebp-24], esp
main 00540FB9 mov dword ptr [ebp-20], 540FC6
main 00540FC0 mov byte ptr [esi+8], 0
main 00540FC4 call ecx ESP=001AEDC8
5 способов сравнить два байтовых массива. Сравнительное тестирование