STL find работает лучше, чем цикл, созданный вручную

У меня вопрос. Учитывая следующий фрагмент кода 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.

Меня попросили опубликовать вывод программы. Без перемешивания:

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

Большое спасибо,

душа.

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 вместо esi. В чем разница между этими двумя регистрами? Может ли быть, что esi предполагает, что указатель правильно выровнен и, следовательно, обеспечивает дополнительную производительность?

Прочтите некоторую ссылку на сборку. Ecx - это просто счетчик, а esi - источник памяти. Поэтому я думаю, что алгоритм STL знает, что итератор произвольного доступа правильно выровнен и поэтому использует указатели памяти. Где в версии, отличной от STL, нет никаких предположений о том, как происходит выравнивание. Я прав?


person dusha    schedule 02.01.2011    source источник
comment
Насколько быстрее? Можете ли вы опубликовать результаты своей программы?   -  person peoro    schedule 03.01.2011
comment
В любом эксперименте одна точка данных ничего не значит. Я предлагаю вам повторять в цикле каждый поиск, измеряя время на каждой итерации и в конце получая среднее значение и стандартное отклонение ваших измеренных значений времени. Среднее значение должно устранять большую часть шума измерения (как нагревание кеша, так и компенсация случайных колебаний), а стандартное отклонение должно дать вам меру того, насколько велики такие колебания и насколько надежен ваш результат. Если после этого время по-прежнему отличается более чем на 2σ, вам, вероятно, следует более внимательно изучить профилировщик.   -  person Matteo Italia    schedule 03.01.2011
comment
сделал это, как вы предложили ... результаты следующие: требуется первый поиск: 0,01004, требуется второй поиск: 0,02006. Просто запустил цикл 100 раз и вычислил среднее значение.   -  person dusha    schedule 03.01.2011
comment
Вероятно, это не лучшая идея: i<end Используйте i != end   -  person Martin York    schedule 03.01.2011
comment
find1 () присваивается найденному во время построения. find2 () присваивается найденному с использованием конструкции копирования. Это, вероятно, не имеет большого значения, но когда вы рассчитываете время, вы всегда должны делать одно и то же везде (за исключением того, что вы синхронизируете).   -  person Martin York    schedule 03.01.2011
comment
@dusha: ну тогда у нас ситуация :) А как насчет stddev?   -  person Matteo Italia    schedule 03.01.2011
comment
Я не так хорош в сборке ... Может ли кто-нибудь перепроверить, используется ли разворачивание петли, как предлагает Котлински? Я вижу, что индексный регистр увеличивается на 4, но цикл, кажется, увеличивается на 1 ... Я сомневаюсь.   -  person dusha    schedule 03.01.2011
comment
@dusha: Вы должны выложить разборку.   -  person Puppy    schedule 03.01.2011
comment
Вы действительно имеете в виду стандартную библиотеку C ++, а не STL.   -  person Lightness Races in Orbit    schedule 03.01.2011
comment
STL - это стандартная библиотека шаблонов ... Так упоминается в литературе.   -  person dusha    schedule 03.01.2011
comment
@dusha: Все регистры одинаковые. Некоторые регистры имеют особое значение, когда дело доходит до сохранения данных между вызовами функций (например, обратных адресов), но есть причина, по которой они называются универсальными, потому что все они одинаковы.   -  person Puppy    schedule 03.01.2011


Ответы (6)


Алгоритм find Visual C ++ использует неотмеченные итераторы, тогда как ваш рукописный цикл использует проверенные итераторы.

Еще я предполагаю, что вы вызываете std::vector<t>::end() на каждой итерации цикла в find2, в то время как std::find приводит только к одному вызову методов доступа begin и end. Я идиот.

person Billy ONeal    schedule 02.01.2011
comment
Если вы посмотрите на код, вы увидите, что end () не вызывается на каждой итерации. - person Johan Kotlinski; 03.01.2011
comment
@dusha: Но find может амортизировать стоимость проверки, проверяя только при входе в алгоритм - он будет использовать непроверенные операции внутри, даже если алгоритм все еще проверяется. - person Billy ONeal; 03.01.2011
comment
Проблема с отмеченными / непроверенными итераторами является ключевой. Если вы добавите #define _SCL_SECURE 0 в начале программы, find2 станет таким же быстрым, как find1. - person Adrian McCarthy; 03.01.2011
comment
@ Адриан: Возможно, тебе придется поиграть с _SCL_HAS_ITERATOR_DEBUGGING - не уверен. - person Billy ONeal; 03.01.2011
comment
Проверенные итераторы: msdn.microsoft.com/en- us / library / aa985965 (v = VS.90) .aspx. - person Adrian McCarthy; 03.01.2011
comment
@Yttrill: причина не в этом. Я тестировал его с помощью #define _SCL_SECURE 0, это не помогает. Так что не позволяйте другим вводить в заблуждение! - person dusha; 03.01.2011
comment
@dusha: Где ты поставил #define для _SCL_SECURE 0? Для работы он должен быть перед любым стандартным заголовком C ++. (Ну, технически не раньше некоторых, но многие заголовки могут косвенно включать algorithm) Кроме того, если ваша программа имеет несколько единиц трансляции, это должно быть установлено таким образом в каждой единице, иначе это нарушение ODR. - person Billy ONeal; 03.01.2011
comment
@Billy: В первом тесте я поместил первый оператор в stdafx.h, после того, как он не помог, я зашел в Project Properties | C / C ++ / Preprocessor и добавил определение: _SCL_SECURE = 0. Оба не помогли. Есть ли у вас еще предложения? Но в любом случае, зачем выпускать сборку с использованием checked_iterators? И более того, почему генерируется другая сборка, которая отличается только двумя регистрами. Думаю, вопрос в том, почему esi регистрируется быстрее, чем ecx? - person dusha; 03.01.2011
comment
@dusha: Ну, я предлагаю просто использовать std::find: P - person Billy ONeal; 03.01.2011
comment
@dusha: Также проверьте настройку _HAS_ITERATOR_DEBUGGING. (msdn.microsoft.com/en-us/ библиотека / aa985939 (v = vs.80) .aspx) - person Billy ONeal; 04.01.2011
comment
@Billy: Я все время использую алгоритмы STL ... ИМО, они более выразительны. Но хотелось бы понять, почему компилятор оптимизирует алгоритм поиска, отличный от идентичного тела поиска. - person dusha; 04.01.2011
comment
@Billy: Только что проверил параметр _HAS_ITERATOR_DEBUGGING, который выключен = ›без разницы. Но я не думаю, что обмененный регистр (как показывает сборка) будет выполнять проверки итератора. - person dusha; 04.01.2011
comment
Тогда понятия не имею. В любом случае лучше использовать std::find. - person Billy ONeal; 04.01.2011
comment
@Billy: Вы должны вычеркнуть текст в своем посте. Я считаю неправильным вводить пользователей в заблуждение и позволять им голосовать за это, поскольку мы уточнили, что ваш подход не решает проблему. - person dusha; 04.01.2011
comment
@dusha: В общем случае это был бы хороший ответ. И я не проводил сравнительный анализ, подтверждающий неправильный ответ. Я согласен с ответом @ DeadMG в том, что я думаю, что ваш таймер сломан. Я оставляю это здесь на будущее, если кто-то устраняет подобную проблему, потому что проверенное поведение в некоторых случаях составляет 50% накладных расходов. - person Billy ONeal; 04.01.2011
comment
@Billy: Мой таймер не сломан, так как я реализовал таймер, предложенный DeadMG, и провел с ним дальнейшие тесты. Тогда я был бы достаточно вежлив, чтобы заявить в ответ, что тесты показали, что это предложение не решает проблему или не объясняет ее, но: .... С другой стороны: не проводить сравнительный анализ, чтобы не подтвердить ответ, неверно это очень гибкий способ решения проблемы. Если вы оставите ответ как есть, я спрошу решение у других администраторов, поскольку ответ действительно вводит в заблуждение и, кроме того, как показывает сборка, не имеет ничего общего с итераторами !!! - person dusha; 05.01.2011

Убедитесь, что вы компилируете свой код в режиме выпуска с помощью проверенные итераторы отключены

Установите _SECURE_SCL = 0 в определениях препроцессора.

Кроме того, boost :: progress_timer имеет разрешение в миллисекунды, как я полагаю (он основан на std :: clock), что делает его очень ненадежным для точных измерений в короткие сроки. Вам нужно сделать код, который вы измеряете, значительно медленнее, чтобы избавиться от других факторов (таких как приостановка процесса и т. Д.). Вы должны измерять, используя высокопроизводительные счетчики, как предлагает DeadMG.

person villintehaspam    schedule 02.01.2011
comment
Компилирую в релизном режиме. Получилось, без разницы. проверенные итераторы используются в STL и в моем цикле, потому что это свойство итератора ... - person dusha; 03.01.2011
comment
@dusha: Куда ты #define положил? Это должно произойти до фактического включения find. - person Billy ONeal; 03.01.2011
comment
@dusha: Режим релиза маловат. Вам нужно явно отключить проверенные итераторы. Определение _SECURE_SCL равным 0 в верхней части программы делает рукописный цикл столь же быстрым, как stl::find. - person Adrian McCarthy; 03.01.2011

find не принимает value_type, он принимает const value_type &. Теперь я бы сказал, что для 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 написана именно для представления правильных оптимизаций, тогда как ваша находка просто не помечает правильные поля оптимизации.

Изменить: ваши временные данные явно повреждены. Мой 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. Я проверил, заменив вызов find2 другим вызовом find1, который действительно производит идентичную разборку. Любопытно, что производят разную сборку.

person Puppy    schedule 02.01.2011
comment
Я использую 32-битную машину .... sizeof (reference) == sizeof (unsigned int). Почему это должно иметь значение? И find вызывается только один раз, поэтому он просто не копирует значение только один раз, а запускает цикл со ссылкой. - person dusha; 03.01.2011
comment
Опубликованный вами код не является реализацией моей находки ... Но просто протестировал его. Медленнее как найти. На самом деле мой созданный вручную цикл обеспечивает такую ​​же производительность, как и ваш фрагмент. - person dusha; 03.01.2011
comment
@dusha: Это имеет значение, если оптимизатор не может определить, что они такие же. - person Puppy; 03.01.2011
comment
+1 за указание на счетчики высокой производительности. boost :: progress_timer не предназначен для подобных тестов производительности - он основан на std :: clock. - person villintehaspam; 03.01.2011
comment
Я запускаю свой код в VMWare ... Это может объяснить задержку. Я протестирую предложение Perf Counter. Еще раз проверьте, правильно ли вы получаете частоту в своем тесте (QueryPerformanceFrequency, а не QueryPerformanceCounter). - person dusha; 03.01.2011
comment
@dusha: Я все время пишу этот код, и он определенно правильный. Я перегрузил QPF / QPC, чтобы избежать неприятного приведения, которое вы обычно получаете при их вызове, но это все. - person Puppy; 03.01.2011
comment
@dusha: Вы никогда не должны проводить такие тесты внутри виртуальной машины. По большей части время виртуальной машины согласуется с реальным оборудованием, но не всегда (т.е. если один элемент использует инструкцию, виртуальная машина должна имитировать, а не гипервизор) - person Billy ONeal; 03.01.2011
comment
@Billy: Если виртуальная машина ведет себя по-разному в отношении разных инструкций, то вполне возможно, что очень немного отличающаяся разборка во второй версии просто недовольна виртуальной машиной. - person Puppy; 03.01.2011
comment
Ни за что на i7 не уйдет так много времени - узкое место наверняка будет в памяти, а не в процессоре! - person Johan Kotlinski; 03.01.2011
comment
Вы используете 32- или 64-битные окна? Я использую 32-битную ОС ... Я просто заменил boost :: progress_timer на PerfCounters, но они все равно различаются в два раза. Я запускаю его в VS 2008. - person dusha; 03.01.2011
comment
@kotlinski: i7s имеют одинаковое значение, например, тайминги кеша, и, хотя у него МОЖЕТ быть узкое место в памяти, его память не будет в 40 000 раз медленнее, чем моя, поскольку у нас один и тот же размер кеша и, следовательно, такая же подкачка памяти к требованиям RAM. - person Puppy; 03.01.2011
comment
@dusha: VS2010, и я использую 64-битную Windows 7. Я все еще подозреваю, что виртуальная машина имеет здесь наибольшее значение. - person Puppy; 03.01.2011
comment
Но я так не думаю ... Я не забываю протестировать эту проблему раньше на Windows, отличной от виртуальной машины, и она все еще сохраняется. Ваше утверждение: VM выполняет некоторые операторы сборки лучше, чем другие, и я смог выяснить, какие из них;) Я так не думаю. Думаю, MS где-то настраивал VS2010. Попробуйте скомпилировать свой код как 32-битный исполняемый файл ... - person dusha; 03.01.2011
comment
@dusha: Я сделал. Я использую 64-битную версию, но компилирую как 32-битную. Я тоже скомпилировал как 64bit - разборка была идентичной. Разборку нужно выложить. - person Puppy; 03.01.2011
comment
Только что запустил свой 32-битный exe в ОС Windows 2008 Server (где работают все виртуальные машины) с теми же недостатками производительности для find2. Хорошо, 64-битные ОС выполняют 32-битный исполняемый файл на каких-то виртуальных машинах. Но я виртуализирую с помощью VMware, а Windows использует собственную виртуализацию, и обе не работают с инструкциями сборки из find2. Не совсем правдоподобно. - person dusha; 03.01.2011
comment
Ок, выложил сборку в моем исходном сообщении. - person dusha; 03.01.2011
comment
@dusha: 64-битная ОС не выполняет 32-битную ВМ - это функция процессора, а не ОС. Однако я также не вижу правдоподобия того, что он трактует очень похожие инструкции так по-разному. - person Puppy; 03.01.2011
comment
Я имею в виду WOW64: en.wikipedia.org/wiki/WOW64, 32-битный код проходит через этот слой . Это на мой взгляд какая-то виртуализация. Это не просто функция процессора. - person dusha; 03.01.2011
comment
@dusha: этот уровень обрабатывает взаимодействие кода между Windows и 32-битным кодом, а не между 32-битным кодом и процессором. Wow64cpu.dll, который заботится о переключении процессора из 32-битного в 64-битный режим. Следовательно, процессор имеет 32-битный режим. - person Puppy; 03.01.2011
comment
Да, конечно, но это также обеспечивает совместимость между 32-битными и 64-битными библиотеками. Не все исполняемые файлы являются самодостаточными. Они могут ссылаться на другие 32-битные системные вызовы api и т. Д. - person dusha; 03.01.2011
comment
@dusha: Это не какая-либо форма виртуализации - 32-битный код работает полностью изначально. Биты WOW64 необходимы только для вызовов 32-битного кода в 64-битное ядро. В вашем коде такие системные вызовы не используются, поэтому WOW64 здесь не играет роли. - person Billy ONeal; 04.01.2011
comment
Вы не разместили дизассемблер. - person Lightness Races in Orbit; 07.04.2013

Ваша методология измерения не работает. Правильно измерить скорость выполнения кода очень сложно, потому что общее затраченное время зависит от факторов, которые не могут явно контролироваться написанным вами кодом.

Что нужно проверить (некоторые из них можно считать очевидными):

  • Какие настройки оптимизации вы используете? Вы ведь тестируете релизную сборку?
  • Вы сказали, что проверили ассемблерный код, сгенерированный версией STL, и он не использует векторизацию. Но, может быть, он использует какой-то другой распространенный метод оптимизации, такой как развертывание цикла?
  • Почему вы используете i < end, а не i != end в своем цикле? (Я действительно сомневаюсь, что это имеет какое-то значение, но кто знает?)

(Мой первоначальный ответ был совершенно глупым - я не уверен, почему он получил голосование - я оставляю его здесь, поскольку некоторые комментарии относятся к нему)

В этом случае я подозреваю, что вы просто видите эффекты иерархии памяти. Когда вы вызываете find1 (), CPU должен прочитать все данные из RAM. Затем эти данные будут храниться в кеше ЦП, что намного (легко в 10-100 раз) быстрее, чем доступ к ОЗУ. Когда вы вызываете find2 (), ЦП может читать весь массив из кеш-памяти, поэтому find2 () требует меньше времени для выполнения.

Чтобы получить больше доказательств, попробуйте поменять местами код, чтобы сначала измерить find2 (), а затем find1 (). Если ваши результаты поменялись местами, вероятно, вы заметили эффект кеширования. Если нет, то дело в другом.

Изменить. После некоторых дополнительных размышлений (на самом деле, сразу после некоторых размышлений) я думаю, что мое первоначальное подозрение должно быть неверным (размер массива, который вы просматриваете, делает маловероятным что весь массив помещен в кеш). Могут быть эффекты кеширования, но они, вероятно, более тонкие. Тем не менее, все равно попробуйте поменять местами измерения, было бы интересно посмотреть, какой эффект это даст.

person John Bartholomew    schedule 02.01.2011
comment
Вектор имеет размер sizeof (unsigned int) * 10 000 000 байт. Разве это не слишком много, чтобы поместиться в кеш-память процессора? Также OP говорит, что find1 (первый вызов) быстрее. Так что ваше объяснение кажется немного неправильным. - person villintehaspam; 03.01.2011
comment
да. Мое предложение является ложным по нескольким причинам (данные слишком велики, и, конечно, они тоже будут в кеше сразу после того, как они были сгенерированы). Кэш может по-прежнему играть роль в разнице в скорости, но это не совсем простая роль. - person John Bartholomew; 03.01.2011
comment
find2 требует больше времени для выполнения! И даже если я поменяю местами исполнение, результат будет тот же. - person dusha; 03.01.2011
comment
Проверил ваши баллы ... Единственное: поменял '‹на'! = 'Без разницы. Что мешает компилятору развернуть мой созданный вручную цикл? Он все еще знает, что я использую итератор произвольного доступа ... - person dusha; 03.01.2011
comment
Компилятор, вероятно, не разворачивает ваш цикл, потому что современные компиляторы ничего не разворачивают, потому что развертывание обычно замедляет работу современных процессоров. Сверхтонкие циклы обычно быстрее, чем развернутые, из-за ограниченного доступа к кешу, более легкого предсказания ветвлений, более легкого заполнения конвейера инструкций и т.д. и т.п. Это не всегда верно, но это лучшая эвристика для компилятора. - person Yttrill; 03.01.2011

Я сам не использую 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, и что это можно сделать, развернув.

Другое предположение состоит в том, что это просто компромисс между пространством / размером - компилятор пропускает эту оптимизацию в общем случае.

person Johan Kotlinski    schedule 02.01.2011
comment
Очень интересно. Просто для справки, не могли бы вы постпроцессор, версию g ++ и флаги компиляции? - person John Bartholomew; 03.01.2011
comment
Мне действительно интересно, почему VC или G ++ не разворачивают созданные вручную циклы? Это часть их оптимизации ... Протестировано ... Мой цикл с ручной разверткой все еще медленнее ... В вашем коде есть небольшая ошибка ... return должен возвращать i + OffsetValue, а не только i. - person dusha; 03.01.2011
comment
@dusha: заметил и исправил. - person Johan Kotlinski; 03.01.2011
comment
@dusha: Я проверил еще раз - мои временные различия намного меньше, чем у вас - так что, вероятно, это не объяснение вашей проблемы. - person Johan Kotlinski; 03.01.2011

Многие пользователи C / C ++ жалуются, что как только они пишут специализацию функции, неспециализированная версия выполняет это!

Причина просто в том, что после того, как вы напишете проходы оптимизации на серверной части вашего компилятора, вы будете думать о способах улучшения генерации кода std::find, и, следовательно, он выполняет вашу реализацию.

Также узел, который std::find для VC ++, по крайней мере, имеет разные версии, которые будут вызывать разные функции и алгоритмы поиска для разных типов итераторов.

Итак, все, что я думаю, компилятор, похоже, понимает, что ваши данные отсортированы, и, следовательно, лучше выполняет поиск.

person langerra.com    schedule 02.01.2011
comment
Итак, все, что я думаю, компилятор, кажется, понимает, что ваши данные отсортированы ... ‹- это невозможно. - person Edward Strange; 03.01.2011
comment
Вряд ли сомневаюсь, просто поставьте random_shuffle (vec.begin (), vec.end ()); после вызова генерации. Теперь разница такая же (в 10 раз), но общее время меньше. Могу только пояснить, что часть данных все еще в кеше. - person dusha; 03.01.2011
comment
На самом деле первые две строчки, вероятно, верны: я, конечно, использую именно эту технику для улучшения своего оптимизатора. - person Yttrill; 03.01.2011
comment
Это возможно. Даже если компилятор не мог угадать данные, из сборки очевидно, что он может лучше угадать структуру данных и, следовательно, генерирует лучший код. - person langerra.com; 03.01.2011