我遇到了一个奇怪的问题:我有以下代码:
int matches = 0;
for (int str_id = 0; str_id < STR_COUNT; str_id++) {
if (test(strings1[str_id], strings2[str_id]) == 0)
matches++;
}
它使用test()
函数比较以null结尾的字符串对。 strings1
和strings2
是包含STR_COUNT
以空字符结尾的相同长度字符串的向量。
根据test()
取消引用其参数,此代码段在strings1
和strings2
中的字符串长度的常量或线性时间内执行。也就是说,如果我使用:
int test(char* a, char* b) {
return (a != b)
}
然后运行时间不依赖于strings1和strings2中存储的字符串的长度。另一方面,如果我使用
int test(char* a, char* b) {
return (*a != *b)
}
然后,运行时间随着strings1
和strings2
中存储的字符串的长度线性增加。
为什么会发生这种情况?
编辑:此处出现问题的完整示例:http://pastebin.com/QTPAkP1g
答案 0 :(得分:3)
您正在看到数据位置的影响。
在仅仅比较指针的情况下,操作仅访问两个向量中的内存。向量连续存储它们的元素,因此每个存储器访问的位置非常接近在前一次迭代期间访问的位置。这是非常好的地方,缓存会对你微笑。
如果您取消引用指针,则会向混合添加额外的内存访问,因此缓存还有更多工作要做,效果很大程度上取决于实现。
从您的数据推断,似乎字符串在内存中打包在一起,因此从一个字符串的开头到下一个字符串的开头的距离取决于字符串的长度。与较长的字符串相比,短字符串打包得更紧密。
特别是,您可以将一些非常短的字符串打包到单个缓存行中。当发生这种情况时,单个缓存行可以为多次迭代的内存访问提供服务。随着字符串变长,其中较少的字符串将适合单个缓存行,因此缓存效率会降低。最终,字符串足够长,每个字符串占用一个单独的缓存行,缓存没有任何好处。
答案 1 :(得分:2)
因为在第一种情况下可以证明只要strings1 != strings2
,条件永远不会成立。优化编译器可以推断出整个循环永远不会有任何可观察到的副作用,因此可以将其优化为遗忘。
考虑strings[str_id]
等于strings + str_id * sizeof(*strings)
;让我们假设sizeof
为简单起见等于1(我们可以做到这一点而不失一般性)。然后你的情况变成:
if (test(strings1 + str_id, strings2 + str_id) == 0)
如果编译器能够内联test
,则test
的第一个版本代码将成为
if ((strings1 + str_id != strings2 + str_id) == 0)
或(连续简化但等效的形式)
if (strings1 + str_id == strings2 + str_id)
if (strings1 == strings2)
所以,因为strings1 != strings2
(这几乎肯定是这种情况)并且由于编译器可以假设strings1
和strings2
不会被外部原因修改,因此它可以简单地跳过整个循环而不做任何事。什么都不做就是不变的时间。
对于test
的第二个版本,除了实际执行循环并在每次迭代时取消引用指针之外,无法知道条件是否为真,因此运行时间变为线性。