像来自glibc的strlen()
执行一个很好的位操作并且每次检查4个字节使得函数如此快速,与逐字节例程相比,正如大多数其他人所做的那样,是这样的比较汇编中的两个字符串?我正在阅读有关C语言代码实现的一些页面,对字符串处理部分非常感兴趣,但我仍然没有找到这样的。
我必须尽可能快地完成这个功能,因为它是我应用程序的核心。(不建议使用哈希表)
欢迎任何汇编。但是我对intel的汇编语法有点熟悉,如果你要提供的汇编有所不同,请对它进行评论。
答案 0 :(得分:6)
您可以逐字比较(例如,一次32位或64位)。你只需要小心不要越过字符串的末尾。如果你正在制作琴弦,那么你可以用零填充它们,这样它们就是单词大小的倍数,那么你甚至不需要检查。
答案 1 :(得分:4)
假设零终止字符串(尽管同样适用于memcmp()
);在汇编中进行字符串比较的最快方法取决于字符串的长度/ s和特定的CPU。
总的来说; SSE或AVX具有较高的设置成本,但在运行后提供更快的吞吐量,如果您要比较很长的字符串(特别是如果大多数字符匹配),则它是最佳选择。
或者,使用通用寄存器一次执行一个字节的操作通常会具有非常低的设置成本和较低的吞吐量,这使得它成为比较大量小字符串(甚至很多大字符串)的最佳选择前几个字符可能不同的字符串。)
如果您针对特定应用执行此操作,则可以确定比较的平均字符数,并找到该平均值的最佳方法。您还可以针对不同情况使用不同的功能 - 例如如果有混合物,请实施strcmp_small()
和strcmp_large()
。
尽管如此,如果字符串比较的性能很重要,那么比较字符串的最快方法很可能不是比较字符串。基本上,“我必须尽快使这个功能成为可能,因为它是我的应用程序的核心”应该让每个人都想知道为什么不可能有更好的方法来实现应用程序。
答案 2 :(得分:3)
以下是一些使用dword比较的代码:
注意,首先检查字符串的长度。这是因为在提到的库中,字符串是长度前缀的,因此StrLen是即时O(1),并且扫描终止NULL仅作为后退提供(参见本答复的第二部分)。
在实际比较之前比较长度允许为不同的字符串设置速度O(1),在搜索大数组的情况下可以显着提高性能。
然后比较继续dwords,最后,如果字符串长度不是4的乘法,则剩余的1..3字节将逐字节进行比较。
proc StrCompCase, .str1, .str2
begin
push eax ecx esi edi
mov eax, [.str1]
mov ecx, [.str2]
cmp eax, ecx
je .equal
test eax, eax
jz .noteq
test ecx, ecx
jz .noteq
stdcall StrLen, eax
push eax
stdcall StrLen, ecx
pop ecx
cmp eax, ecx
jne .noteq
stdcall StrPtr, [.str1]
mov esi,eax
stdcall StrPtr, [.str2]
mov edi,eax
mov eax, ecx
shr ecx, 2
repe cmpsd
jne .noteq
mov ecx, eax
and ecx, 3
repe cmpsb
jne .noteq
.equal:
stc
pop edi esi ecx eax
return
.noteq:
clc
pop edi esi ecx eax
return
endp
这是StrLen的实现。
你可以看到,如果可能的话,它使用长度前缀字符串,这样就可以使执行时间为O(1)。如果这是不可能的,它会回到扫描算法,每个周期检查8个字节并且它很快,但仍然是O(n)。
proc StrLen, .hString ; proc StrLen [hString]
begin
mov eax, [.hString]
cmp eax, $c0000000
jb .pointer
stdcall StrPtr, eax
jc .error
mov eax, [eax+string.len]
clc
return
.error:
xor eax, eax
stc
return
.pointer:
push ecx edx esi edi
; align on dword
.byte1:
test eax, 3
jz .scan
cmp byte [eax], 0
je .found
inc eax
jmp .byte1
.scan:
mov ecx, [eax]
mov edx, [eax+4]
lea eax, [eax+8]
lea esi, [ecx-$01010101]
lea edi, [edx-$01010101]
not ecx
not edx
and esi, ecx
and edi, edx
and esi, $80808080
and edi, $80808080
or esi, edi
jz .scan
sub eax, 9
; byte 0 was found: so search by bytes.
.byteloop:
lea eax, [eax+1]
cmp byte [eax], 0
jne .byteloop
.found:
sub eax, [.hString]
clc
pop edi esi edx ecx
return
endp
请注意,零终止字符串同时存在性能和安全问题。
最好使用大小前缀字符串。例如,提到的库使用动态字符串,其中字符串包含偏移量为-4的dword字段(上面代码中的string.len),其中包含字符串的当前长度。
答案 3 :(得分:3)
比第一个更快的字节每字节比较的规则是malloc字符串或.align 16
任何常量字符串以确保