У меня есть вопрос. Учитывая следующий фрагмент кода С++:
#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 find (вызов через find1) работает примерно в 10 раз быстрее, чем ручной цикл (вызов через find2). Сначала я подумал, что Visual С++ выполняет какую-то вектологию (может быть, я ошибаюсь здесь), но насколько я вижу, сборка не выглядит так, как она использует векторию. Почему цикл 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 знает, что Итератор случайного доступа правильно выровнен и поэтому использует указатели на память. Где в версии, отличной от STL, нет предположений о том, как выполняется выравнивание. Я прав?