这种技术是有效的,如果您调用我们的C库strlen
,则无法避免使用它。例如,如果该库是GNU C库的最新版本(至少对于某些目标),它将执行相同的操作。
使其正常工作的关键是确保指针对齐。如果指针对齐,则操作肯定会读取字符串结尾之外的内容,但不会读取相邻页面的内容。如果空终止字节在页面结束的一字之内,则最后一个字将被访问而不会触及随后的页面。
这在C中显然不是定义良好的行为,因此在从一个编译器移植到另一个编译器时需要进行仔细的验证。这也会触发超出边界访问检测器(如Valgrind)的误报。
为了解决Glibc的问题,必须对Valgrind进行修补。如果没有这些修补程序,您将获得诸如以下烦扰错误:
==13669== Invalid read of size 8
==13669== at 0x411D6D7: __wcslen_sse2 (wcslen-sse2.S:59)
==13669== by 0x806923F: length_str (lib.c:2410)
==13669== by 0x807E61A: string_out_put_string (stream.c:997)
==13669== by 0x8075853: obj_pprint (lib.c:7103)
==13669== by 0x8084318: vformat (stream.c:2033)
==13669== by 0x8081599: format (stream.c:2100)
==13669== by 0x408F4D2: (below main) (libc-start.c:226)
==13669== Address 0x43bcaf8 is 56 bytes inside a block of size 60 alloc'd
==13669== at 0x402BE68: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==13669== by 0x8063C4F: chk_malloc (lib.c:1763)
==13669== by 0x806CD79: sub_str (lib.c:2653)
==13669== by 0x804A7E2: sysroot_helper (txr.c:233)
==13669== by 0x408F4D2: (below main) (libc-start.c:226)
Glibc使用SSE指令以8字节为一组计算(而不是的宽度4字节)。
这样做,它会在60字节的块中访问偏移量为56的位置。然而,请注意,此访问永远不会跨越页面边界:该地址可被8整除。
如果您使用汇编语言,您无需对这种技术再三考虑。
实际上,在我所使用的一些优化音频编解码器(针对ARM)中,该技术被广泛使用,并且具有许多手写的汇编语言与Neon指令集。
当我在集成这些编解码器的代码上运行Valgrind时,我注意到了这一点,并联系了供应商。他们解释说这只是一个无害的循环优化技巧;我研究了汇编语言并确信他们是正确的。
strlen()
方法会更快。首先,据我所知,test
和cmp
是任何架构中最昂贵的操作之一,而且你没有在它们上面省一点。你还添加了很多不是特别必要的操作,只是为了一次性复制4个字节的内存到一个本地变量中,这也是完全没有意义的。请记住,L1和L2缓存都参与其中,所以这并不像你从某些慢速媒体缓冲字符串一样。 - Havenard#define hasNulByte(x) ((x - 0x01010101) & ~x & 0x80808080)
在uint32_t
上,这相当于第二个示例中更加暴力的方式。 - geometrianstrlen()
实现更快。你只需要简单地跟踪你的字符串长度即可。 - Havenard