假设您正在使用x86 32位系统。你的任务是尽快实现strlen。
你需要注意两个问题: 地址对齐。 2.读取机器字长(4字节)的存储器。
在给定的字符串中找到第一个对齐地址并不难。
然后我们可以用4个字节读取一次内存,并计算总长度。但是,一旦在4个字节中有一个零字节,我们应该停止,并在零字节之前计算左字节。为了快速检查零字节,有一个来自glibc的代码片段:
unsigned long int longword, himagic, lomagic;
himagic = 0x80808080L;
lomagic = 0x01010101L;
// There's zero byte in 4 bytes.
if (((longword - lomagic) & ~longword & himagic) != 0) {
// do left thing...
}
我在Visual C ++中使用它,与CRT的实现进行比较。 CRT比上面的快得多。
我不熟悉CRT的实现,他们是否使用更快的方法来检查零字节?
答案 0 :(得分:8)
您可以在创建字符串时将字符串的长度与字符串一起保存,就像在Pascal中一样。
答案 1 :(得分:7)
第一个CRT的第一个是汇编程序中的直接。你可以在这里看到它的源代码C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\intel\strlen.asm
(这是针对VS 2008)
答案 2 :(得分:4)
这取决于。微软的库真的有两个不同版本的strlen。一个是C语言中的可移植版本,它是关于strlen最简单的版本,非常接近(可能等同于):
size_t strlen(char const *str) {
for (char const *pos=str; *pos; ++pos)
;
return pos-str;
}
另一种是汇编语言(仅用于Intel x86),与上面的内容非常相似,至少就加载4个字节而言,检查其中一个是零,并做出适当的反应。唯一明显的区别是,它们不是减去,而是基本上添加预先否定字节并添加。即而不是word-0x0101010101
,他们使用word + 0x7efefeff
。
答案 3 :(得分:2)
还有使用REPNE SCAS指令对的编译器内部版本,虽然它们通常在较旧的编译器上,但它们仍然可以非常快。还有SSE2版本的strlen,例如Dr Agner Fog's performance library's implementation,或类似this
答案 4 :(得分:1)
删除那些' L'后缀并看到... 您正在推广所有计算到#34; long"!在我的32位测试中,仅此一项费用就会增加一倍。
我还做了两次微观优化:
由于我们使用扫描的大多数字符串都包含0到127范围内的ASCII字符,高位(几乎)从不设置,所以只在第二次测试中检查它。
增加索引而不是指针,这在某些体系结构(尤其是x86)上更便宜,并为您提供免费' ... 的长度p>
uint32_t gatopeich_strlen32(const char* str)
{
uint32_t *u32 = (uint32_t*)str, u, abcd, i=0;
while(1)
{
u = u32[i++];
abcd = (u-0x01010101) & 0x80808080;
if (abcd && // If abcd is not 0, we have NUL or a non-ASCII char > 127...
(abcd &= ~u)) // ... Discard non-ASCII chars
{
#if BYTE_ORDER == BIG_ENDIAN
return 4*i - (abcd&0xffff0000 ? (abcd&0xff000000?4:3) : abcd&0xff00?2:1);
#else
return 4*i - (abcd&0xffff ? (abcd&0xff?4:3) : abcd&0xff0000?2:1);
#endif
}
}
}
答案 5 :(得分:0)
假设您知道最大可能的长度,并且在使用之前已经将内存启动到\ 0,您可以进行二进制拆分并根据值进行左/右(\ 0,左侧拆分,否则拆分)对)。这样你就可以大大减少查找长度所需的支票数量。不是最佳的(需要一些设置),但应该非常快。
// Eric
答案 6 :(得分:0)
显然,在汇编程序中制作这样的紧密循环会是最快的,但是如果你想要/需要在C(++)中保持它更易读和/或可移植,你仍然可以提高标准的速度使用register
keyword进行功能。
register
关键字提示编译器将计数器存储在CPU上的寄存器中而不是存储器中,这将显着加快循环速度。
但请注意,register
关键字只是一个建议,如果编译器认为它可以做得更好,编译器可以自由忽略它,特别是如果使用了某些优化选项。也就是说,虽然对于三重for循环中的本地类变量几乎肯定会被忽略,但它可能会对下面的代码感到荣幸,从而提高了性能(几乎与汇编程序版本相当) ):
size_t strlen ( const char* s ) {
for (register const char* i=s; *i; ++i);
return (i-s);
}