为什么查找字符串的方法比我的单遍快
Why find method for string is faster than my single pass?
我有点震惊,为什么c++ STL字符串查找子字符串的查找方法比简单的O(n)
传递字符串更快。这里有两个不同的函数:为什么在str2
中找到str1
的第二个函数比第一个函数(优化得很好)快?我知道第一个函数做了一个稍微不同的任务,但它仍然只是通过str1
和str2
(O(n))
,而第二个函数可能需要O(n^2)
才能在str2
中找到str1
。真的为什么?你们知道吗?提前谢谢你。
p。这些功能是一个更大项目的一部分。它们在我的代码中被调用了很多次来比较两个字符串。如果我使用第二个函数,整个代码的运行时间几乎减半(135秒VS 235秒)!
bool Is_Included1(string str1, string str2)
{
size_t i,s;
s=str1.size();
if (s<=str2.size())
{
for (i=0;i<s;i++)
if (str1[i]!=str2[i])
return false;
return true;
}
return false;
}
bool Is_Included2(string str1, string str2)
{
size_t i;
if (str1.size()<=str2.size())
{
i=str2.find(str1);
if (i==0)
return true;
else
return false;
}
return false;
}
我在GCC 4.7.2中跟踪了它的实现。复杂度为0 (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;
}
原因必须至少部分是查询的特定结构,找出这是一个有趣的侦探挑战!例如,当str2比str1长得多(并且不包含完全不同的字符)时,您的实现显然会更快。为了避免混淆,我们现在假设两个字符串具有相同的长度。
可能的解释是您的STL版本实现使用CPU上可用的更长的寄存器对字符进行批量比较。您可以将几个字符打包到一个寄存器中,然后并行地对它们进行比较。通过这种方式,您可以一步比较几个连续的字符(即使使用标准的64位寄存器,您也可以打包8个字符并同时比较它们)。有关讨论,请参阅此堆栈溢出问题。
另一种可能的解释是,STL使用了一种算法,比如,从字符串的末端开始比较,如果字符串的末端倾向于比字符串的前缀不同,则从末端开始比较。
您可以通过运行测试来检查:速度差异是由于匹配,还是不匹配,还是两者都有?对于我的第二个解释,您将看到非匹配在STL版本中更好,而第一个解释将为匹配提供更快的速度。
区别在于数组访问器[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;
}
看看它与STL参考实现的比较。
(注意,STL版本可能仍然快一点,因为现在您可以进一步优化它以完全删除int i
的使用)
- 为什么我的 BaseClass:Method 代码编译(带有单冒号)?
- 如何格式化我的文本文件以使其不会一遍又一遍地重复同一行?
- 为什么即使使用 for 循环遍历我的向量,它也没有输出到控制台?(C++)
- 我的 c++ 应用程序中的运行时间从 0 增加到 60 太快了(例如一毫秒或一微秒)
- 我的主窗口在创建时或单击更新区域时是否会收到编辑控件?
- 如何将我的程序添加到文件和文件夹的macOS右键单击菜单?
- C++单例模板类使我的程序崩溃
- 为什么在我释放左键单击后,我的绘图会消失
- 遍历我的数据结构,并向其中输入随机值
- 我该如何循环遍历我的数组(缓冲区——包含一个文本文件),并将其打印成30字节的块
- 如何在我的基本计算器中循环遍历数组
- 我的 RichEdit 控件是否可以包含可单击的链接?
- 我的单例中的数组在离开函数后没有保留信息,然后在尝试再次访问信息时崩溃
- 如果我这样写,我的单例类会导致什么错误
- 如何使用 Open MPI 使我的程序更快?我的 Open MPI 程序目前比原来慢,我有什么不明白的?
- 为什么我的单例实现两次启动?(一个进程,多个线程)
- 我的单例模板真的是单例吗?
- 为什么未定义在我的单例类中引用了mMutex
- 为什么V8在Node.JS中比我的本地c++插件快
- 为什么查找字符串的方法比我的单遍快