为什么找到字符串的方法比我的单程更快?

时间:2013-03-01 06:55:07

标签: c++ performance stl

我有点震惊为什么C ++ STL字符串查找子字符串的find方法比字符串上的简单O(n)更快。这有两个不同的功能: 为什么在str1中找到str2的第二个函数比第一个函数(已经过优化)更快? 我知道第一个函数执行的任务略有不同,但它只是传递str1str2 (O(n)),而第二个函数可能需要O(n^2)来查找{在str1中{1}}。 真的为什么?你们有什么想法吗?提前谢谢。

P.S这些功能是一个更大项目的一部分。在我的代码中,它们被多次调用以比较两个字符串。如果我使用第二个功能,整个代码的运行时间几乎是一半(135秒VS 235秒)!

str2

3 个答案:

答案 0 :(得分:2)

我已经跟踪了GCC 4.7.2中的实现。 其复杂度为O(nm),其中n,m是两个字符串的长度。

假设n.size()小于m.size(),对于m的每个可能的起始点i,它首先比较n [0]和m [i](traits_type :: eq),然后调用traits_type: :compare,实际执行__builtin_memcmp()。

这不是确切的实现,但它说明了算法。

for (size_t i=0; i<m.size(); ++i) {
    if (traits_type::eq(n[0], m[i]) &&
        traits_type::compare(n[1], m[i+1], n.size()-1) == 0) {
            return i;
    }
}
return -1;

虽然算法的时间顺序更差,但我猜是因为__builtin_memcmp()没有逐个比较字符,因此比我们预期的要快。

顺便说一下,如果经常调用该函数,你应该传递两个字符串的const引用而不是传递值,这会导致不必要的副本。

如下:

bool Is_Included2(const string& str1, const string& str2)
{
    if (str1.size() > str2.size()) return false;
    return str2.find(str1) == 0;
}

答案 1 :(得分:2)

原因必须至少部分是您的查询的具体结构,并且找出一个有趣的侦探挑战!例如,当str2比str1长得多时,你的实现显然会更快(并且不包含完全不同的字符)。为了避免混淆,我们现在假设这两个字符串具有相同的长度。

可能的解释是,您的STL版本实现使用CPU上可用的较长寄存器对字符进行批量比较。您可以将多个字符打包到一个寄存器中,并将它们全部并行比较。这样,您可以一步比较多个连续字符(即使使用标准的64位寄存器,您也可以打包8个字符并同时进行比较)。有关讨论,请参阅This stack overflow question

另一个可能的解释是,STL使用一种算法,比如说,开始比较字符串的结尾,如果字符串的字符串往往与字符串的前缀不同,则会结束。

您可以通过运行测试来检查:速度差异是由于匹配还是不匹配,还是两者都有?对于我的第二个解释,你会发现STL版本中的不匹配更好,第一个解释会让匹配更快。

答案 2 :(得分:2)

区别在于数组访问器[i]与指针算法。

使用str1[i]str2[i]是主要区别。这些访问器通常不像使用底层指针算法那样进行优化,例如。 const char* c1 = str1.cstr()然后执行++c1; ++c2迭代它们(这是任何STL实现在引擎盖下执行的操作)。

通常,底层硬件可以更好地迭代指针而不是数组。有时编译器可以优化循环以使用指针算法而不是数组算法,但由于std::string使用operator[]的复杂重载实现,它基本上总是最终在每个arrayBase+offset上执行bool Is_Included1(string str1, string str2) { size_t i,s; s=str1.size(); if (s<=str2.size()) { const char* c1 = str1.c_str(); const char* c2 = str2.c_str(); for (i=0;i<s;i++, c1++, c2++) if (*c1!=*c2) return false; return true; } return false; } 迭代循环。

试试这个:

int i

了解与STL参考实现的比较。

(请注意,STL版本可能会更快一些,因为现在您可以进一步优化它以完全取消{{1}}的使用)

相关问题