测试结果分析
除了 Michael 之前在此处所做的分析(请参阅上面的帖子)之外,我将进一步分析内置 PHP 哈希的性能,因为这个主题非常有趣并且有意想不到的结果。
结果并不那么明显,甚至令人惊讶。一种简单的算法——CRC32 或 CRC16,比复杂的算法——MD5 慢。似乎现代 CPU 不喜欢特定的旧算法并且执行它们非常缓慢,至少在算法没有以新方式实现时,利用现代 CPU 架构。CRC16 CCITT 算法在过去有 300 BPS 拨号调制解调器的情况下是相对快速和高效的。现在有专门为新硬件设计的现代算法,它在相同硬件上的运行速度可能比旧算法快得多,旧算法本质上不适合新硬件,即使你尝试优化它们,它们无论如何都会相对较慢。例如,
您可能会从其他加密库中看到,这些库证实了我们在 PHP 中看到的内容 - CRC32 IEEE 的整个速度几乎与 MD5 相同。这是另一个库结果的链接:https ://www.cryptopp.com/benchmarks.html
OpenSSL 显示了类似的结果。乍一看,这似乎很不合理,因为 CRC32 的算法比 MD5 的算法简单得多,但实际情况恰恰相反。
我只是想展示一下 CRC32 函数是多么简单。
这是使用下一个传入字节(Delphi)更新 CRCR32 计数器的代码:
// Returns an updated CRC32
function UpdateCrc32(CurByte: Byte; CurCrc: Cardinal): Cardinal; inline;
begin
UpdateCrc32 := Crc32Table[Byte(CurCrc xor CurByte)] xor (CurCrc shr 8);
end;
这是组装的这段代码:
@calc_crc32:
xor dl,[esi]
mov al,dl
shr edx,8
xor edx,dword ptr [edi+eax*4]
inc esi
loop @calc_crc32
你也可以展开这段代码,这样你每字节只会得到 5 条 CPU 指令:
xor dl,bl
shr rbx,8
mov al,dl
shr edx,8
xor edx,dword ptr [r8+rax*4]
您只需将rbx
接下来的 8 个字节数据加载到寄存器中,然后重复此代码 8 次,直到您需要将接下来的 8 个字节加载到rbx
64 位寄存器。
这是计算整个字符串的 CRC32 的例程:
function CalcCRC32(const B; Size: NativeUINT;
const
InitialValue: Cardinal = CRC32_INIT): Cardinal;
var
C: Cardinal;
P: PAnsiChar;
i: NativeUINT;
begin
C := InitialValue;
if Size > 0 then
begin
P := @B;
for i := 0 to Size - 1 do
C := UpdateCrc32(Byte(P[i]), C);
end;
Result := C;
end;
下面是 Delphi 将它编译成机器代码的方式——不是非常优化,但非常简单——每个字节只有 11 条汇编指令,令人惊讶的是,在 Intel Core i5-6600 上,即使在循环展开。如您所见,实现 CRC32 IEEE 的所有这些指令都很简单,没有循环或比较,每个字节末尾只有一个比较。这只是编译的 Delphi 代码的调试器输出,而不是人工编写的汇编代码。
CRC32.pas.78: begin
push esi
push edi
CRC32.pas.80: if Size > 0 then
test edx,edx
jbe $00500601
CRC32.pas.82: P := @B;
mov edi,eax
CRC32.pas.83: for i := 0 to Size - 1 do
mov eax,edx
dec eax
test eax,eax
jb $00500601
inc eax
xor esi,esi
CRC32.pas.84: C := UpdateCrc32(Byte(P[i]), C);
movzx edx,[edi+esi]
xor dl,cl
movzx edx,dl
mov edx,[edx*4+$517dec]
shr ecx,$08
xor edx,ecx
mov ecx,edx
inc esi
CRC32.pas.83: for i := 0 to Size - 1 do
dec eax
jnz $005005e6
CRC32.pas.86: Result := C;
mov eax,ecx
CRC32.pas.87: end;
pop edi
pop esi
ret
这是CRC32汇编代码的另一种变体,每个字节只有5个处理器命令,而不是11个,但它与上述汇编代码基本相同,只是使用不同的寄存器并避免再次出现在i5上的“循环”命令-比两条不同的指令快6600。您可以在从 C 控制台应用程序调用的 CRC32 汇编程序函数中找到整个代码
586
.model flat, stdcall
.xmm
.data
.code
CRC32 proc sizeOfFile:DWORD, file:DWORD
push esi
push ecx
push edx
mov esi, file
xor edx, edx
or eax, -1
mov ecx, sizeOfFile
CRC32_loop:
mov dl, byte ptr [esi]
xor dl, al
shr eax, 8
xor eax, dword ptr [crc32_table + 4*edx]
inc esi
dec ecx
jnz CRC32_loop
not eax
pop edx
pop ecx
pop esi
ret
现在将它与 MD5 与 Peter Sawatzki 的高度优化的汇编代码进行比较:
; MD5_386.Asm - 386 optimized helper routine for calculating
; MD Message-Digest values
; written 2/2/94 by
;
; Peter Sawatzki
; Buchenhof 3
; D58091 Hagen, Germany Fed Rep
;
; EMail: Peter@Sawatzki.de
; EMail: 100031.3002@compuserve.com
; WWW: http://www.sawatzki.de
;
;
; original C Source was found in Dr. Dobbs Journal Sep 91
; MD5 algorithm from RSA Data Security, Inc.
.386
.MODEL FLAT
.CODE
R1 = ESi
R2 = EDi
FF Macro a,b,c,d,x,s,ac
; a:= ROL (a+x+ac + (b And c Or Not b And d), s) + b
Add a, [EBp+(4*x)]
Add a, ac
Mov R1, b
Not R1
And R1, d
Mov R2, c
And R2, b
Or R1, R2
Add a, R1
Rol a, s
Add a, b
EndM
GG Macro a,b,c,d,x,s,ac
; a:= ROL (a+x+ac + (b And d Or c And Not d), s) + b
Add a, [EBp+(4*x)]
Add a, ac
Mov R1, d
Not R1
And R1, c
Mov R2, d
And R2, b
Or R1, R2
Add a, R1
Rol a, s
Add a, b
EndM
HH Macro a,b,c,d,x,s,ac
; a:= ROL (a+x+ac + (b Xor c Xor d), s) + b
Add a, [EBp+(4*x)]
Add a, ac
Mov R1, d
Xor R1, c
Xor R1, b
Add a, R1
Rol a, s
Add a, b
EndM
II Macro a,b,c,d,x,s,ac
; a:= ROL (a+x+ac + (c Xor (b Or Not d)), s) + b
Add a, [EBp+(4*x)]
Add a, ac
Mov R1, d
Not R1
Or R1, b
Xor R1, c
Add a, R1
Rol a, s
Add a, b
EndM
Transform Proc
Public Transform
;Procedure Transform (Var Accu; Const Buf); Register;
; save registers that Delphi requires to be restored
Push EBx
Push ESi
Push EDi
Push EBp
Mov EBp, EDx ; Buf -> EBp
Push EAx ; Accu -> Stack
Mov EDx, [EAx+12]
Mov ECx, [EAx+8]
Mov EBx, [EAx+4]
Mov EAx, [EAx]
FF EAx,EBx,ECx,EDx, 0, 7, 0d76aa478h ; 1
FF EDx,EAx,EBx,ECx, 1, 12, 0e8c7b756h ; 2
FF ECx,EDx,EAx,EBx, 2, 17, 0242070dbh ; 3
FF EBx,ECx,EDx,EAx, 3, 22, 0c1bdceeeh ; 4
FF EAx,EBx,ECx,EDx, 4, 7, 0f57c0fafh ; 5
FF EDx,EAx,EBx,ECx, 5, 12, 04787c62ah ; 6
FF ECx,EDx,EAx,EBx, 6, 17, 0a8304613h ; 7
FF EBx,ECx,EDx,EAx, 7, 22, 0fd469501h ; 8
FF EAx,EBx,ECx,EDx, 8, 7, 0698098d8h ; 9
FF EDx,EAx,EBx,ECx, 9, 12, 08b44f7afh ; 10
FF ECx,EDx,EAx,EBx, 10, 17, 0ffff5bb1h ; 11
FF EBx,ECx,EDx,EAx, 11, 22, 0895cd7beh ; 12
FF EAx,EBx,ECx,EDx, 12, 7, 06b901122h ; 13
FF EDx,EAx,EBx,ECx, 13, 12, 0fd987193h ; 14
FF ECx,EDx,EAx,EBx, 14, 17, 0a679438eh ; 15
FF EBx,ECx,EDx,EAx, 15, 22, 049b40821h ; 16
GG EAx,EBx,ECx,EDx, 1, 5, 0f61e2562h ; 17
GG EDx,EAx,EBx,ECx, 6, 9, 0c040b340h ; 18
GG ECx,EDx,EAx,EBx, 11, 14, 0265e5a51h ; 19
GG EBx,ECx,EDx,EAx, 0, 20, 0e9b6c7aah ; 20
GG EAx,EBx,ECx,EDx, 5, 5, 0d62f105dh ; 21
GG EDx,EAx,EBx,ECx, 10, 9, 002441453h ; 22
GG ECx,EDx,EAx,EBx, 15, 14, 0d8a1e681h ; 23
GG EBx,ECx,EDx,EAx, 4, 20, 0e7d3fbc8h ; 24
GG EAx,EBx,ECx,EDx, 9, 5, 021e1cde6h ; 25
GG EDx,EAx,EBx,ECx, 14, 9, 0c33707d6h ; 26
GG ECx,EDx,EAx,EBx, 3, 14, 0f4d50d87h ; 27
GG EBx,ECx,EDx,EAx, 8, 20, 0455a14edh ; 28
GG EAx,EBx,ECx,EDx, 13, 5, 0a9e3e905h ; 29
GG EDx,EAx,EBx,ECx, 2, 9, 0fcefa3f8h ; 30
GG ECx,EDx,EAx,EBx, 7, 14, 0676f02d9h ; 31
GG EBx,ECx,EDx,EAx, 12, 20, 08d2a4c8ah ; 32
HH EAx,EBx,ECx,EDx, 5, 4, 0fffa3942h ; 33
HH EDx,EAx,EBx,ECx, 8, 11, 08771f681h ; 34
HH ECx,EDx,EAx,EBx, 11, 16, 06d9d6122h ; 35
HH EBx,ECx,EDx,EAx, 14, 23, 0fde5380ch ; 36
HH EAx,EBx,ECx,EDx, 1, 4, 0a4beea44h ; 37
HH EDx,EAx,EBx,ECx, 4, 11, 04bdecfa9h ; 38
HH ECx,EDx,EAx,EBx, 7, 16, 0f6bb4b60h ; 39
HH EBx,ECx,EDx,EAx, 10, 23, 0bebfbc70h ; 40
HH EAx,EBx,ECx,EDx, 13, 4, 0289b7ec6h ; 41
HH EDx,EAx,EBx,ECx, 0, 11, 0eaa127fah ; 42
HH ECx,EDx,EAx,EBx, 3, 16, 0d4ef3085h ; 43
HH EBx,ECx,EDx,EAx, 6, 23, 004881d05h ; 44
HH EAx,EBx,ECx,EDx, 9, 4, 0d9d4d039h ; 45
HH EDx,EAx,EBx,ECx, 12, 11, 0e6db99e5h ; 46
HH ECx,EDx,EAx,EBx, 15, 16, 01fa27cf8h ; 47
HH EBx,ECx,EDx,EAx, 2, 23, 0c4ac5665h ; 48
II EAx,EBx,ECx,EDx, 0, 6, 0f4292244h ; 49
II EDx,EAx,EBx,ECx, 7, 10, 0432aff97h ; 50
II ECx,EDx,EAx,EBx, 14, 15, 0ab9423a7h ; 51
II EBx,ECx,EDx,EAx, 5, 21, 0fc93a039h ; 52
II EAx,EBx,ECx,EDx, 12, 6, 0655b59c3h ; 53
II EDx,EAx,EBx,ECx, 3, 10, 08f0ccc92h ; 54
II ECx,EDx,EAx,EBx, 10, 15, 0ffeff47dh ; 55
II EBx,ECx,EDx,EAx, 1, 21, 085845dd1h ; 56
II EAx,EBx,ECx,EDx, 8, 6, 06fa87e4fh ; 57
II EDx,EAx,EBx,ECx, 15, 10, 0fe2ce6e0h ; 58
II ECx,EDx,EAx,EBx, 6, 15, 0a3014314h ; 59
II EBx,ECx,EDx,EAx, 13, 21, 04e0811a1h ; 60
II EAx,EBx,ECx,EDx, 4, 6, 0f7537e82h ; 61
II EDx,EAx,EBx,ECx, 11, 10, 0bd3af235h ; 62
II ECx,EDx,EAx,EBx, 2, 15, 02ad7d2bbh ; 63
II EBx,ECx,EDx,EAx, 9, 21, 0eb86d391h ; 64
Pop ESi ; get Accu from stack
Add [ESi], EAx
Add [ESi+4], EBx
Add [ESi+8], ECx
Add [ESi+12], EDx
; restore registers for Delphi
Pop EBp
Pop EDi
Pop ESi
Pop EBx
Ret
Transform EndP
End
您可以在https://github.com/maximmasiutin/MD5_Transform-x64找到此代码的 32 位和 64 位版本
此代码在 IA-32 或 x86-64 下的性能是每字节(在 Skylake 上)数据 4.94 个 CPU 周期来计算 MD5。
上述代码一次性处理 64 字节的传入数据。它从执行准备步骤的主例程中调用:
procedure CiphersMD5Update(var Context: TMD5Ctx; const ChkBuf; len: UInt32);
var
BufPtr: ^Byte;
Left: UInt32;
begin
If Context.Count[0] + UInt32(len) shl 3 < Context.Count[0] then
Inc(Context.Count[1]);
Inc(Context.Count[0], UInt32(len) shl 3);
Inc(Context.Count[1], UInt32(len) shr 29);
BufPtr := @ChkBuf;
if Context.BLen > 0 then
begin
Left := 64 - Context.BLen;
if Left > len then
Left := len;
Move(BufPtr^, Context.Buffer[Context.BLen], Left);
Inc(Context.BLen, Left);
Inc(BufPtr, Left);
If Context.BLen < 64 then
Exit;
Transform(Context.State, @Context.Buffer);
Context.BLen := 0;
Dec(len, Left)
end;
while len >= 64 do
begin
Transform(Context.State, BufPtr);
Inc(BufPtr, 64);
Dec(len, 64)
end;
if len > 0 then
begin
Context.BLen := len;
Move(BufPtr^, Context.Buffer[0], Context.BLen)
end
end;
如果您的处理器支持 CRC32 操作码 (SSE 4.2),您可以使用以下代码将校验和的计算速度提高 10 倍:
function crc32csse42(crc: cardinal; buf: Pointer; len: NativeUInt): cardinal;
asm // ecx=crc, rdx=buf, r8=len
.NOFRAME
mov eax,ecx
not eax
test r8,r8; jz @0
test rdx,rdx; jz @0
@7: test rdx,7; jz @8 // align to 8 bytes boundary
crc32 eax,byte ptr [rdx]
inc rdx
dec r8; jz @0
test rdx,7; jnz @7
@8: mov rcx,r8
shr r8,3
jz @2
@1: crc32 eax,qword ptr [rdx] // calculate CRC of 8 bytes, aligned
dec r8
lea rdx,rdx+8
jnz @1
@2: // less than 8 bytes remaining
and rcx,7; jz @0
cmp rcx,4; jb @4
crc32 eax,dword ptr [rdx] // calculate CRC of 4 bytes
sub rcx,4
lea rdx,rdx+4
jz @0
@4: // less than 4 bytes remaining
crc32 eax,byte ptr [rdx]
dec rcx; jz @0
crc32 eax,byte ptr [rdx+1]
dec rcx; jz @0
crc32 eax,byte ptr [rdx+2]
@0: not eax
end;
请注意,在我的示例中,我使用了一个只有 5KB 的缓冲区,以适应处理器的缓存并排除较慢 RAM 对摘要计算速度的影响。
CRC32 算法的快速实现适用于不使用 CRC32 操作码的现代处理器,但利用了乱序执行,包括通过寄存器重命名的推测执行。这种实现的一个例子是 CRC32 Slicing-By-8。IA-32 或 x86-64 汇编代码每字节数据有 1,20 个 CPU 时钟周期(在 Skylake 上)。您可以在https://github.com/maximmasiutin/CRC32-Slicing-x64-Asm-Pas找到这样的实现
在 PHP 中,即使在版本 7 中,似乎也不支持 CRC32 的硬件加速,尽管 Intel 和 AMD 处理器自古以来就支持这些指令。英特尔从 2008 年 11 月开始支持 CRC32(Nehalem(微架构)),而 AMD 似乎从 2013 年开始支持它。
我自己的测试确认迈克尔的结果
我已经在不同的配置下测试了各种 PHP 哈希函数:(1)AMD FX-8320(2012 年发布)在 Ubuntu 下使用 PHP 5,以及(2)Intel Core i5-6600 在 2015 年在 Windows 下使用 PHP 7 发布。我也有在此 Intel Core i5-6600 上运行 OpenSSL 测试。除此之外,我还对我们在软件“The Bat!”中使用的加密程序进行了测试。用德尔福写的。虽然主要软件是用 Delphi 编写的,但我们使用的加密例程是用 Intel 处理器(32 位或 64 位)的 Assembler 或 C 语言编写的。
我发现我们的 Delphi 代码在各种散列函数和数据大小之间显示出非常大的速度差异。这与 PHP 形成对比,在某种程度上,除了极少数例外,从最简单的 CRC32 到曾经加密的强大 MD5 的所有哈希函数都具有几乎相同的直通输出速度。
因此,这是我在 AMD FX-8320、PHP5、Ubuntu 上所做的测量。我做了两个测试用例。首先,我运行 5000 次迭代来散列仅包含 5 个字节的消息。通过这个小消息大小,我打算测试各种算法的初始化/完成步骤的持续时间,以及它如何影响整体性能。对于某些算法,如 CRC32,三个实际上没有最终确定步骤——摘要总是在每个字节之后准备好。像 SHA1 或 MD5 或其他加密强大的函数具有将较大的上下文压缩为较小的最终摘要的最终步骤。其次,我运行 5000 次迭代来散列 5000 字节长的消息。两条消息都预先填充了伪随机字节(每次迭代后它们都不会重新填充;它们只在程序启动时填充一次)。
我的 PHP 哈希速度测试结果
我已经修改了您的 PHP 代码,使其现在适用于 PHP5 和 PHP7,在不同版本的 PHP 中生成随机数据的函数不同。我刚刚测量了哈希 5000 次 5 字节消息迭代和 5000 次 5000 字节消息迭代所需的时间。结果如下:
Legend:
(1) 5b x 5000, AMD FX-8320, PHP5
(2) 5000b x 5000, AMD FX-8320, PHP5
PHP hash (1) (2)
-------- ------------ ------------
md2 0.021267 sec 2.602651 sec
md4 0.002684 sec 0.035243 sec
md5 0.002570 sec 0.055548 sec
sha1 0.003346 sec 0.106432 sec
sha224 0.004945 sec 0.210954 sec
sha256 0.004735 sec 0.238030 sec
sha384 0.005848 sec 0.144015 sec
sha512 0.006085 sec 0.142884 sec
ripemd128 0.003385 sec 0.120959 sec
ripemd160 0.004164 sec 0.174045 sec
ripemd256 0.003487 sec 0.121477 sec
ripemd320 0.004206 sec 0.177473 sec
whirlpool 0.009713 sec 0.509682 sec
tiger128,3 0.003414 sec 0.059028 sec
tiger160,3 0.004354 sec 0.059335 sec
tiger192,3 0.003379 sec 0.058891 sec
tiger128,4 0.003514 sec 0.073468 sec
tiger160,4 0.003602 sec 0.072329 sec
tiger192,4 0.003507 sec 0.071856 sec
snefru 0.022101 sec 1.190888 sec
snefru256 0.021972 sec 1.217704 sec
gost 0.013961 sec 0.653600 sec
adler32 0.001459 sec 0.038849 sec
crc32 0.001429 sec 0.068742 sec
crc32b 0.001553 sec 0.063308 sec
fnv132 0.001431 sec 0.038256 sec
fnv164 0.001586 sec 0.060622 sec
joaat 0.001569 sec 0.062947 sec
haval128,3 0.006747 sec 0.174759 sec
haval160,3 0.005810 sec 0.166154 sec
haval192,3 0.006129 sec 0.168382 sec
haval224,3 0.005918 sec 0.166792 sec
haval256,3 0.006119 sec 0.173360 sec
haval128,4 0.007364 sec 0.233829 sec
haval160,4 0.007917 sec 0.240273 sec
haval192,4 0.007676 sec 0.245864 sec
haval224,4 0.007580 sec 0.245249 sec
haval256,4 0.007442 sec 0.241091 sec
haval128,5 0.008651 sec 0.281248 sec
haval160,5 0.009304 sec 0.278619 sec
haval192,5 0.008972 sec 0.281235 sec
haval224,5 0.008917 sec 0.274923 sec
haval256,5 0.008853 sec 0.282171 sec
然后我在 Intel Core i5-6600 下运行相同的 PHP 脚本,在 Windows 10 下使用 64 位版本的 PHP7。结果如下:
Legend:
(1) 5b x 5000, Intel Core i5-6600, PHP7
(2) 5000b x 5000, Intel Core i5-6600, PHP7
PHP hash (1) (2)
--------- ------------ ------------
md2 0.016131 sec 2.308100 sec
md4 0.001218 sec 0.040803 sec
md5 0.001284 sec 0.046208 sec
sha1 0.001499 sec 0.050259 sec
sha224 0.002683 sec 0.120510 sec
sha256 0.002297 sec 0.119602 sec
sha384 0.002792 sec 0.080670 sec
ripemd128 0.001984 sec 0.094280 sec
ripemd160 0.002514 sec 0.128295 sec
ripemd256 0.002015 sec 0.093887 sec
ripemd320 0.002748 sec 0.128955 sec
whirlpool 0.003402 sec 0.271102 sec
tiger128,3 0.001282 sec 0.038638 sec
tiger160,3 0.001305 sec 0.037155 sec
tiger192,3 0.001309 sec 0.037684 sec
tiger128,4 0.001618 sec 0.050690 sec
tiger160,4 0.001571 sec 0.049656 sec
tiger192,4 0.001711 sec 0.050682 sec
snefru 0.010949 sec 0.865108 sec
snefru256 0.011587 sec 0.867685 sec
gost 0.008968 sec 0.449647 sec
adler32 0.000588 sec 0.014345 sec
crc32 0.000609 sec 0.079202 sec
crc32b 0.000636 sec 0.074408 sec
fnv132 0.000570 sec 0.028157 sec
fnv164 0.000566 sec 0.028776 sec
joaat 0.000623 sec 0.042127 sec
haval128,3 0.002972 sec 0.084010 sec
haval160,3 0.002968 sec 0.083213 sec
haval192,3 0.002943 sec 0.082217 sec
haval224,3 0.002798 sec 0.084726 sec
haval256,3 0.002995 sec 0.082568 sec
haval128,4 0.003659 sec 0.112680 sec
haval160,4 0.003858 sec 0.111462 sec
haval192,4 0.003526 sec 0.112510 sec
haval224,4 0.003671 sec 0.111656 sec
haval256,4 0.003636 sec 0.111236 sec
haval128,5 0.004488 sec 0.140130 sec
haval160,5 0.005095 sec 0.137777 sec
haval192,5 0.004117 sec 0.140711 sec
haval224,5 0.004311 sec 0.139564 sec
haval256,5 0.004382 sec 0.138345 sec
如您所见,要在 PHP 中计算消息的 CRC32,在我几乎所有的测试中,只需要计算相同消息的 MD5 所需时间的一半左右。唯一的例外是在英特尔酷睿 i5-6600 上使用 PHP7 测试 5000 条 5000 字节的消息时,CRC32 甚至比 MD5 花费的时间更长(!)。这个奇怪的结果在我的案例中总是可以重复的。我找不到合理的解释。
此外,在 PHP 上,MD5 和 SHA1 之间几乎没有明显的速度差异,除了在带有 PHP5 的 Ubuntu 上,在测试 5000 条 5000 字节的消息时,MD5 快了两倍。
我的 OpenSSL 哈希性能测试结果
以下是英特尔 i5-660 上的 OpenSSL 测试。它们以不同的方式显示数据。它们没有显示消化某些数据集花费了多少时间,但反之亦然:它们显示了 OpenSSL 在 3 秒内设法散列的数据量。因此,值越高越好:
Legend:
(1) OpenSSL 1.1.0 on Intel Core i5-6600, number of 16-bytes messages processed in 3 seconds
(2) OpenSSL 1.1.0 on Intel Core i5-6600, number of 8192-bytes messages processed in 3 seconds
Algorighm (1) (2)
--------- --------- ----------
md4 50390.16k 817875.48k
md5 115875.35k 680700.59k
sha1 118158.30k 995986.09k
ripemd160 30308.79k 213224.11k
whirlpool 39605.02k 182072.66k
同样,md5 和 sha1 之间几乎没有区别,这很奇怪,需要进一步调查 MD5 和 SHA-1 算法在时间消耗方面是否本质上相同。
我们的 Delphi 哈希函数性能结果
以下是我们在英特尔酷睿 i5-6600 上的 Delphi 库在 Windows 10 64 位下的结果,测试的代码是 32 位 Win32 应用程序。
Legend:
(1) Delphi, 5b x 5000 iterations
(2) Delphi, 5000b x 5000 iterations
Algorighm (1) (2)
--------------- -------------- --------------
md2 0.0381010 secs 5.8495807 secs
md5 0.0005015 secs 0.0376252 secs
sha1 0.0050118 secs 0.1830871 secs
crc32 >0.0000001 secs 0.0581535 secs
crc32c (intel hw) >0.0000001 secs 0.0055349 secs
正如你所看到的,MD2 也比其他哈希值要多得多——与 PHP 代码的结果相同,但 MD5 比 SHA-1 快得多,总体而言,在同一台机器上在 Delphi 中执行相同操作所需的时间更少以 PHP 为例,PHP7 用 MD5 消化 5000 条 5 字节消息需要 0.001284 秒,用 SHA1 需要 0.001499 秒。大约 5000 字节的消息——使用 MD5 需要 PHP7 0.046208 秒,使用 SHA-1 需要 0.050259 秒。
与 Delphi 一样,使用 MD5 消化 5000 条 5 字节消息需要 0.0005015 秒,而使用 SHA1 需要 0.0050118 秒。大约 5000 字节的消息——使用 MD5 需要 Delphi 0.0376252 秒,使用 SHA-1 需要 0.1830871 秒。如您所见,MD5 在 Delphi 中运行得更快,但 SHA-1 几乎相同。此外,Delphi 在 5 字节消息上的速度大约快 10 倍,但对于大约 5000 字节的消息,它与 SHA-1 大致相同甚至更慢。
但是在 CRC32 和 CRC32C 方面,Delphi 是无与伦比的,比 PHP 快 10 到 1000 倍。
结论
PHP 在循环和小型操作上天生就很慢。因此,如果您需要计算一条小消息的哈希值,那么在 PHP 上调用哪个哈希函数并不重要。但如果你需要消化一条大消息,算法速度的差异就开始显现出来:例如,MD2 的性能大约是 MD5 的十倍。无论如何,今天绝对没有理由使用 MD2。对于使用 MD2 的 PHP 用户而言,相比 MD5 没有任何优势。至于 MD5,最初设计为加密哈希函数,然后在 RFC-1991 中被 PGP 使用,现在不能再用于密码学,但可以在可信环境中用作校验和,例如用于 ETag 或其他方式。这个函数在正确实现时非常快(并且在 PHP 上,它至少不慢),与其他函数相比,MD5 产生了非常紧凑的摘要。这是我进行这些基准测试的 PHP 代码。我是根据 Michael 的原始代码示例制作的(见上文)。
<?
define (TRAILING_ZEROS, 6);
$strlens = array(5, 30, 90, 1000, 5000);
$hashes = hash_algos();
function generate_bytes($len)
{
if (function_exists('random_bytes')) {$fn='random_bytes';$str = random_bytes($len);} else // for php 5
if (function_exists('openssl_random_pseudo_bytes')) {$fn='openssl_random_pseudo_bytes';$str = openssl_random_pseudo_bytes($strlen);} else // for php 7
{
flush();
ob_start () ;
phpinfo () ;
$str = str_pad(substr(ob_get_contents (), 0, $len), $len) ;
ob_end_clean () ;
$fn = 'phpinfo';
}
return array(0=>$str, 1=>$fn);
}
foreach ($strlens as $strlen)
{
$loops = 5000;
echo "<h1>$loops iterations on $strlen bytes message</h1>".PHP_EOL;
echo '<p>';
$r = generate_bytes($strlen);
$str = $r[0];
$gotlen = strlen($str);
while ($gotlen < $strlen)
{
// for some uncodumented reason, the openssl_random_pseudo_bytes returned less bytes than needed
$left = $strlen-$gotlen;
echo "The ".$r[1]."() function returned $left byes less, trying again to get these remaining bytes only<br>";
$r = generate_bytes($left);
$str.= $r[0];
$gotlen = strlen($str);
};
echo "Got the whole string of ".strlen($str)." bytes!";
echo '</p>';
echo PHP_EOL;
echo "<pre>";
foreach ($hashes as $hash)
{
$tss = microtime(true);
for($i=0; $i<$loops; $i++)
{
$x = hash($hash, $str, true);
}
$tse = microtime(true);
echo "\n".str_pad($hash, 15, ' ')."\t" . str_pad(round($tse-$tss, TRAILING_ZEROS), TRAILING_ZEROS+2, '0') . " sec \t" . bin2hex($x);
}
echo PHP_EOL."</pre>".PHP_EOL;
flush();
}
?>
帖子继续……