我找到了这段代码
int strlen_my(const char *s)
{
int len = 0;
for(;;)
{
unsigned x = *(unsigned*)s;
if((x & 0xFF) == 0) return len;
if((x & 0xFF00) == 0) return len + 1;
if((x & 0xFF0000) == 0) return len + 2;
if((x & 0xFF000000) == 0) return len + 3;
s += 4, len += 4;
}
}
我很想知道它是如何工作的。 ¿任何人都可以解释它的工作原理吗?
答案 0 :(得分:3)
它处理未定义的行为(未对齐的访问,75%的访问超出数组末尾的概率)以获得非常可疑的加速(很可能甚至更慢)。并且不符合标准,因为它返回int
而不是size_t
。即使平台上允许未对齐的访问,它们也可能比对齐访问慢得多。
它也不适用于大端系统,或unsigned
不是32位。更不用说多重掩码和条件操作了。
那说:
它通过加载unsigned
(甚至不保证超过16位)一次测试4个8位字节。一旦任何字节包含'\0'
- 终止符,它将返回当前长度加上该字节位置的总和。否则,它将当前长度增加并行测试的字节数(4)并获得下一个unsigned
。
我的建议:优化的坏例子加上太多的不确定性/陷阱。它可能不会更快 - 只需根据标准版本进行分析:
size_t strlen(restrict const char *s)
{
size_t l = 0;
while ( *s++ )
l++;
return l;
}
可能有一种方法可以使用特殊的向量指令,但除非你能证明这是一个关键函数,否则你应该把它留给编译器 - 有些人可能会更好地展开/加速这样的循环。
答案 1 :(得分:3)
按位AND与1将从另一个操作数中检索位模式。含义,10101 & 11111 = 10101
。如果该按位AND的结果为0,那么我们就知道另一个操作数为0.当使用0xFF
(1)与单个字节进行AND运算时,结果为0将表示空字节。
代码本身在四字节分区中检查char数组的每个字节。 注意:此代码不可移植;在另一台机器或编译器上,unsigned int可能超过4个字节。使用uint32_t
数据类型来确保32位无符号整数可能会更好。
首先要注意的是,在小端机器上,构成字符数组的字节将以相反的顺序读入无符号数据类型;也就是说,如果当前地址的四个字节是与abcd
对应的位模式,则无符号变量将包含与dcba
对应的位模式。
第二个是C中的十六进制数常量导致一个int大小的数字,在位模式的小端有指定的字节。这意味着,当使用4字节整数进行编译时,0xFF
实际上是0x000000FF
。 0xFF00
是0x0000FF00
。等等。
所以程序基本上是在四个可能的位置寻找NULL字符。如果当前分区中没有NULL字符,它将前进到下一个四字节插槽。
以char数组abcdef
为例。在C中,字符串常量在结尾处始终具有空终止符,因此在该字符串的末尾有一个0x00
字节。
它的工作原理如下:
将“abcd”读入unsigned int x:
x: 0x64636261 [ASCII representations for "dcba"]
检查每个字节是否为空终止符:
0x64636261
& 0x000000FF
0x00000061 != 0,
0x64636261
& 0x0000FF00
0x00006200 != 0,
检查其他两个位置;这个4字节分区中没有空终止符,所以前进到下一个分区。
将“ef”读入unsigned int x:
x: 0xBF006665 [ASCII representations for "fe"]
注意0xBF字节;这超过了字符串的长度,所以我们从运行时堆栈中读取垃圾。它可能是任何东西。在不允许未对齐访问的计算机上,如果字符串后面的内存不是1字节对齐,则会崩溃。如果字符串中只剩下一个字符,我们将读取两个额外的字节,因此与char数组相邻的内存对齐必须是2字节对齐。
检查每个字节是否为空终止符:
0xBF006665
& 0x000000FF
0x00000065 != 0,
0xBF006665
& 0x0000FF00
0x00006600 != 0,
0xBF006665
& 0x00FF0000
0x00000000 == 0 !!!
所以我们返回len + 2
; len
为4,因为我们将它一次递增4,所以我们返回6,这确实是字符串的长度。
答案 2 :(得分:3)
Code" works"通过假设字符串布局并且像int
数组一样可访问,尝试一次读取4个字节。代码依次读取第一个int
然后读取每个字节,测试它是否为空字符。理论上,使用char
的代码运行速度会快于4个*(unsigned*)s
个操作。
但有问题:
对齐是一个问题:例如if((x & 0xFF) == 0)
可能会出错。
Endian是一个问题s
可能无法获取地址s += 4
处的字节
sizeof(int)
是一个问题,因为int
可能与4不同。
数组类型可能会超出size_t
范围,最好使用#include <stddef.h>
#include <stdio.h>
static inline aligned_as_int(const char *s) {
max_align_t mat; // C11
uintptr_t i = (uintptr_t) s;
return i % sizeof mat == 0;
}
size_t strlen_my(const char *s) {
size_t len = 0;
// align
while (!aligned_as_int(s)) {
if (*s == 0) return len;
s++;
len++;
}
for (;;) {
unsigned x = *(unsigned*) s;
#if UINT_MAX >> CHAR_BIT == UCHAR_MAX
if(!(x & 0xFF) || !(x & 0xFF00)) break;
s += 2, len += 2;
#elif UINT_MAX >> CHAR_BIT*3 == UCHAR_MAX
if (!(x & 0xFF) || !(x & 0xFF00) || !(x & 0xFF0000) || !(x & 0xFF000000)) break;
s += 4, len += 4;
#elif UINT_MAX >> CHAR_BIT*7 == UCHAR_MAX
if ( !(x & 0xFF) || !(x & 0xFF00)
|| !(x & 0xFF0000) || !(x & 0xFF000000)
|| !(x & 0xFF00000000) || !(x & 0xFF0000000000)
|| !(x & 0xFF000000000000) || !(x & 0xFF00000000000000)) break;
s += 8, len += 8;
#else
#error TBD code
#endif
}
while (*s++) {
len++;
}
return len;
}
。
试图解决这些困难。
{{1}}
答案 3 :(得分:2)
所有提案都比简单的strlen()慢。
原因是他们没有减少比较次数,只有一次涉及对齐。
检查来自Torbjorn Granlund(tege@sics.se)和Dan Sahlin(dan@sics.se)的strlen()提案。如果您使用的是64位平台,这确实有助于加快速度。
答案 4 :(得分:1)
它检测是否在little-endian机器上的特定字节设置了任何位。因为我们只检查一个字节(因为所有半字节,0或0xF都加倍)并且恰好是最后一个字节位置(因为机器是小端的,因此数字的字节模式是相反的)我们可以立即知道哪个字节包含NUL。
答案 5 :(得分:1)
循环每次迭代需要4个字节的char数组。四个if语句用于确定字符串是否结束,使用带有AND运算符的位掩码来读取所选子字符串的第i个元素的状态。