我有一些问题。给出以下C ++代码片段:
#include <boost/progress.hpp>
#include <vector>
#include <algorithm>
#include <numeric>
#include <iostream>
struct incrementor
{
incrementor() : curr_() {}
unsigned int operator()()
{ return curr_++; }
private:
unsigned int curr_;
};
template<class Vec>
char const* value_found(Vec const& v, typename Vec::const_iterator i)
{
return i==v.end() ? "no" : "yes";
}
template<class Vec>
typename Vec::const_iterator find1(Vec const& v, typename Vec::value_type val)
{
return find(v.begin(), v.end(), val);
}
template<class Vec>
typename Vec::const_iterator find2(Vec const& v, typename Vec::value_type val)
{
for(typename Vec::const_iterator i=v.begin(), end=v.end(); i<end; ++i)
if(*i==val) return i;
return v.end();
}
int main()
{
using namespace std;
typedef vector<unsigned int>::const_iterator iter;
vector<unsigned int> vec;
vec.reserve(10000000);
boost::progress_timer pt;
generate_n(back_inserter(vec), vec.capacity(), incrementor());
//added this line, to avoid any doubts, that compiler is able to
// guess the data is sorted
random_shuffle(vec.begin(), vec.end());
cout << "value generation required: " << pt.elapsed() << endl;
double d;
pt.restart();
iter found=find1(vec, vec.capacity());
d=pt.elapsed();
cout << "first search required: " << d << endl;
cout << "first search found value: " << value_found(vec, found)<< endl;
pt.restart();
found=find2(vec, vec.capacity());
d=pt.elapsed();
cout << "second search required: " << d << endl;
cout << "second search found value: " << value_found(vec, found)<< endl;
return 0;
}
在我的机器上(Intel i7,Windows Vista)STL查找(通过find1调用)运行速度比手工制作的循环快10倍(通过find2调用)。我首先想到Visual C ++执行某种矢量化(可能我在这里弄错了),但据我所知,程序集看起来不像它使用矢量化。为什么STL循环更快?手工制作的循环与STL-find主体的循环相同。
我被要求发布节目的输出。没有洗牌:
value generation required: 0.078
first search required: 0.008
first search found value: no
second search required: 0.098
second search found value: no
随机播放(缓存效果):
value generation required: 1.454
first search required: 0.009
first search found value: no
second search required: 0.044
second search found value: no
非常感谢,
dusha。
P.S。我返回迭代器并写出结果(找到与否),因为我想阻止编译器优化,它认为根本不需要循环。搜索到的值显然不在向量中。
P.P.S。我被要求发布为查找功能生成的程序集。这是:
found=find1(vec, vec.capacity());
001811D0 lea eax,[esp+5Ch]
001811D4 call std::vector<unsigned int,std::allocator<unsigned int> >::capacity (1814D0h)
001811D9 mov esi,dword ptr [esp+60h]
001811DD mov ecx,dword ptr [esp+64h]
001811E1 cmp esi,ecx
001811E3 je wmain+180h (1811F0h)
001811E5 cmp dword ptr [esi],eax
001811E7 je wmain+180h (1811F0h)
001811E9 add esi,4
001811EC cmp esi,ecx
001811EE jne wmain+175h (1811E5h)
found=find2(vec, vec.capacity());
001812AE lea eax,[esp+5Ch]
001812B2 call std::vector<unsigned int,std::allocator<unsigned int> >::capacity (1814D0h)
001812B7 mov ecx,dword ptr [esp+60h]
001812BB mov edx,dword ptr [esp+64h]
001812BF cmp ecx,edx
001812C1 je wmain+262h (1812D2h)
001812C3 cmp dword ptr [ecx],eax
001812C5 je wmain+34Fh (1813BFh)
001812CB add ecx,4
001812CE cmp ecx,edx
001812D0 jne wmain+253h (1812C3h)
find2使用ecx-register而不是esi。这两个寄存器有什么区别?是否可以认为esi假定指针正确对齐,从而带来额外的性能?
读取一些程序集引用ecx只是一个计数器,而esi是内存源。所以我认为STL算法知道Random Access Iterator正确对齐,因此使用内存指针。在非STL版本中,没有猜测对齐方式。我是对的吗?
答案 0 :(得分:5)
Visual C ++的find
算法使用未经检查的迭代器,而您的手写循环使用已检查的迭代器。
我的另一个猜测是,您在
我是个白痴。std::vector<t>::end()
的循环的每次迭代中调用find2
,而std::find
只导致对开始和结束访问者的一次调用。
答案 1 :(得分:3)
确保在关闭checked iterators的发布模式下编译代码
在预处理器定义中设置_SECURE_SCL = 0.
此外,boost :: progress_timer的分辨率为毫秒,我相信(它基于std :: clock),这使得它对于短持续时间的精确测量非常不可靠。您需要使测量的代码显着变慢,以便摆脱其他因素(例如您的进程处于保持状态等)。您应该使用DeadMG建议的高性能计数器进行测量。
答案 2 :(得分:2)
find不接受value_type,它需要一个const value_type&amp;。现在,我会说对于unsigned int,这应该没有区别。但是,您的优化器完全可能没有注意到这一点,并且无法正确优化循环体。
编辑:我建议你使用for循环对编译器说谎。您可以将其重写为
typename Vec::iterator i, end;
i = vec.begin();
end = vec.end();
while(i != end && *i != val)
i++;
return i;
当然,编写std :: find的人确切地知道优化器有多聪明,以及它究竟能够和不能应付什么。
编辑:我在我的机器上运行了测试。这是在Visual Studio 2010上的i7 930,没有超频。我用高性能计数器取代了boost :: progress_timer。
__int64 frequency, begin, end;
QueryPerformanceCounter(frequency);
double d;
QueryPerformanceCounter(begin);
iter found=find1(vec, vec.capacity());
QueryPerformanceCounter(end);
d = ((end - begin) / (double)frequency) * 1000000;
cout << "first search required: " << d << endl;
cout << "first search found value: " << value_found(vec, found)<< endl;
QueryPerformanceCounter(begin);
found=find2(vec, vec.capacity());
QueryPerformanceCounter(end);
d = ((end - begin) / (double)frequency) * 1000000;
cout << "second search required: " << d << endl;
cout << "second search found value: " << value_found(vec, found)<< endl;
说它们都需要0.24(大约)纳秒来操作 - 也就是说,没有区别。我的建议是你的优化器只是不成熟,而你的std :: find版本正是为了呈现正确的优化而编写的,而你的find只是没有勾选正确的优化框。
编辑:您的计时数据明显被破坏。我的i7运行在0.23纳秒 - 即0.00000023秒,而你的需要0.008秒。除非我的i7比你的快了大约40,000倍,否则就没办法了。 i7无法用这么长的时间来遍历千万件物品。当然,我实际上运行64位Windows 7,虽然没有在64位模式下编译。
现在要发布反汇编程序。
FIND1:
00F810D3 mov esi,dword ptr [esp+34h]
00F810D7 mov eax,dword ptr [esp+3Ch]
00F810DB mov ecx,dword ptr [esp+38h]
00F810DF sub eax,esi
00F810E1 sar eax,2
00F810E4 cmp esi,ecx
00F810E6 je main+0B3h (0F810F3h)
00F810E8 cmp dword ptr [esi],eax
00F810EA je main+0B3h (0F810F3h)
00F810EC add esi,4
00F810EF cmp esi,ecx
00F810F1 jne main+0A8h (0F810E8h)
find2:
00F8119A mov ecx,dword ptr [esp+34h]
00F8119E mov eax,dword ptr [esp+3Ch]
00F811A2 mov edx,dword ptr [esp+38h]
00F811A6 sub eax,ecx
00F811A8 sar eax,2
00F811AB cmp ecx,edx
00F811AD jae main+17Fh (0F811BFh)
00F811AF nop
00F811B0 cmp dword ptr [ecx],eax
00F811B2 je main+254h (0F81294h)
00F811B8 add ecx,4
00F811BB cmp ecx,edx
00F811BD jb main+170h (0F811B0h)
00F811BF mov esi,edx
您可以看到find2 与find1略有不同。我通过将另一个find1调用替换为find2的调用来检查, 生成相同的反汇编。很奇怪他们会产生不同的装配。
答案 3 :(得分:1)
您的测量方法已被破坏。测量代码的执行速度非常难以正确执行,因为总耗用时间取决于您编写的代码可能无法明确控制的因素。
要检查的一些事情(你可能会考虑其中一些显而易见的事):
i < end
而不是i != end
? (我实际上怀疑这有什么不同,但谁知道呢?)(我原来的答案完全是愚蠢的 - 不知道为什么会投票 - 我将其留在这里,因为有些评论与此有关)
在这种情况下,我怀疑你只是看到了内存层次结构的影响。当你调用find1()时,CPU必须从RAM中读取所有数据。然后,该数据将存储在CPU的缓存中,这比访问RAM要快很多(容易10到100倍)。当你调用find2()时,CPU可以从高速缓存中读取整个数组,因此find2()的执行时间更短。
要获得更多证据,请尝试交换代码,以便先测量find2()然后再测量find1()。如果结果反转,则可能是您看到了缓存的影响。如果他们不这样做,那就是别的了。
修改强> 经过一番深思熟虑(实际上,就在一些之后),我认为我原来的怀疑一定是不正确的(你正在搜索的数组的大小使整个数组不太可能被放入缓存)。可能仍然存在缓存效果,但它们可能更微妙。不过,无论如何都要尝试颠倒测量,看看它有什么影响会很有趣。
答案 4 :(得分:0)
我自己不使用Visual C ++,但是对于GCC我也得到find2
稍微慢一点的结果。但是,通过手动展开循环,我能够使find2
略快于find1
:
template<class Vec>
typename Vec::const_iterator find2(Vec const& v, typename Vec::value_type val)
{
for(typename Vec::const_iterator i=v.begin(), end=v.end(); i != end; ) {
if (i[0]==val) return i;
if (i[1]==val) return i + 1;
i += 2;
}
return v.end();
}
我对std::find
更快的猜测是编译器拥有所有信息来确定向量大小是2的倍数,并且可以进行展开。
另一个猜测是它只是在空间/大小之间进行权衡 - 编译器在一般情况下会跳过此优化。
答案 5 :(得分:-1)
许多C / C ++用户抱怨说,一旦他们编写了函数的特化,非专业版就会执行它!
原因很简单,一旦你在编译器后端编写优化传递,你就会想到改进std::find
代码生成的方法,因此它会执行你的实现。
此外,至少VC ++ std::find
的节点具有不同的版本,它将为不同类型的迭代器调用不同的函数和搜索算法。
所以我觉得编译器似乎都理解你的数据是排序的,因此可以更好地搜索。