好友
阅读权限 30
听众
最后登录 1970-1-1
本帖最后由 DEATHTOUCH 于 2023-10-10 15:33 编辑
上次在论坛看到有人求助crc32是什么指令,在我了解了该指令之后,将我目前所得的成果分享给大家。CRC32通常用于数据的校验,但是使用纯软件实现的CRC32速度较慢,于是Intel在扩展指令集SSE4.2中加入了crc32指令,用于快速的crc32计算,经过多年(大概10多年),现在支持SSE4.2的cpu基本已经普及。
但是该指令与我们平常见到的crc32校验有所不同,具体是crc32指令使用的是多项式0x1EDC6F41(反转后为0x82F63B78),我们称之为CRC32C;而一般用的crc32使用的多项式为0x04C11DB7(反转后为0xEDB88320),符合IEEE 802.3规范,也可以称为CRC32B。
软件实现的CRC32通常使用查表法,不同的多项式会产生不同的表,提前调用函数建表或使用固定表,一般的方法如下:
[C] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
unsigned
long
crc32(unsigned
long
crc,
char
* buf,
int
len)
{
crc = crc ^ 0xFFFFFFFF;
for
(
int
i=0;i<len;i++) {
crc = CRC32Table[(crc ^ *(buf++)) & 0xFF] ^ (crc >> 8);
}
return
crc ^ 0xFFFFFFFF;
}
对于上面的代码,显然效率不是很高,我们可以使用循环展开的方法进行手动优化,伪代码如下:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
while
(len>=4){
crc = CRC32Table[(crc ^ buf[0]) & 0xFF] ^ (crc >> 8);
crc = CRC32Table[(crc ^ buf[1]) & 0xFF] ^ (crc >> 8);
crc = CRC32Table[(crc ^ buf[2]) & 0xFF] ^ (crc >> 8);
crc = CRC32Table[(crc ^ buf[3]) & 0xFF] ^ (crc >> 8);
len-=4;
buf+=4;
}
while
(len>0){
crc = CRC32Table[(crc ^ *(buf++)) & 0xFF] ^ (crc >> 8);
len--;
}
基本上展开4次就够了,没必要展开太多。
下面我们来看看如何使用crc32指令,在此之前,我们要看看cpu是不是支持SSE4.2指令集,这里我们使用cpuid指令。
[Delphi] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function
SSE42Support:
Boolean
;assembler;
asm
@start:
push ebx
mov eax,
1
cpuid
test ecx,
$100000
je @
not
mov eax,
1
jmp @out
@
not
:
xor
eax, eax
@out:
pop ebx
end
;
如果该函数返回True,那么我们可以放心使用crc32指令了,下面是crc32指令的具体使用函数:
[Delphi] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function
FastCrc32c(crc:
LongWord
;
const
buf;len:
Integer
):
LongWord
;assembler;
asm
{$ifdef CPUX86}
xor
eax,
$FFFFFFFF
@@
1
:
cmp ecx,
4
jb @@
2
crc32 eax, [edx]
add edx,
4
sub ecx,
4
jmp @@
1
@@
2
:
cmp ecx,
0
je @@
end
@@
3
:
crc32 eax,
byte
[edx]
inc edx
dec ecx
jnz @@
3
@@
end
:
xor
eax,
$FFFFFFFF
{
$else
}
mov rax, rcx
xor
eax,
$FFFFFFFF
@@
1
:
cmp r8,
8
jb @@
2
crc32 rax, [rdx]
add rdx,
8
sub r8,
8
jmp @@
1
@@
2
:
cmp r8,
0
je @@
end
@@
3
:
crc32 rax,
byte
[rdx]
inc rdx
dec r8
jnz @@
3
@@
end
:
xor
eax,
$FFFFFFFF
{
$endif
}
end
;
用伪代码描述一下,只描述32位的,64位同理:
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
eax = crc;
while
(len>=4){
ebx = *(
long
*)(buf++);
crc32 eax, ebx
len-=4;
}
while
(len>0){
bl= *(buf++);
crc32 eax, bl
len--;
}
你会发现这些代码和前面循环展开后的代码是如此的相似!
使用1GB的缓冲区,每个字节都填充1,来测试计算这个缓冲区的crc32c的耗时。
在我的电脑上,32位查表法需要大约2200毫秒,用crc32指令要大约200毫秒;64位查表法需要大约2250毫秒,而用crc32指令只要大约110毫秒。
不过根据Faster CRC32-C on x86 (corsix.org) ,有一种更好的利用crc32指令提高吞吐量的方法,就是同时用crc32指令计算3段缓冲区并将结果合并,实测发现32位有大约50%多的提升,但是对于64位几乎没有提升,目前并不清楚具体原因,可能一次分段是4K太小了,导致其它指令的开销过大,可能使用纯汇编会有更好的效果。
这里给出4KB缓冲区32位的优化版本:
[Delphi] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function
hwcrc32c4k(crc:UInt32; buf:
Pointer
):UInt32;assembler;
const
K:
Array
[
0..3
]
of
UInt32 = (
$8A074012
,
0
,
$93E106A4
,
0
);
asm
push edi
push ebx
xor
ebx, ebx
xor
ecx, ecx
mov edi, edx
add edx,
1360
@@
1
:
crc32 eax, [edi]
crc32 ebx, [edi+
1360
]
crc32 ecx, [edi+
1360
*
2
]
add edi,
4
cmp edi, edx
jb @@
1
@@
2
:
movdqa xmm0, K
movd xmm1, eax
movd xmm2, ebx
pclmulqdq xmm1, xmm0,
$00
pclmulqdq xmm2, xmm0,
$10
pxor xmm1, xmm2
movd eax, xmm1
psrldq xmm1,
4
movd edx, xmm1
crc32 ecx, [edi+
1360
*
2
]
crc32 ecx, [edi+
1360
*
2
+
4
]
xor
eax, [edi+
1360
*
2
+
8
]
xor
edx, [edi+
1360
*
2
+
12
]
crc32 ecx, eax
crc32 ecx, edx
mov eax, ecx
@@
end
:
pop ebx
pop edi
end
;
以上就是本人关于crc32指令研究出的具体内容了,有问题欢迎提出,共同讨论。
免费评分
查看全部评分