Intereting Posts
Как я могу гарантировать захват структурированного исключения EXCEPTION_STACK_OVERFLOW в C ++ в Visual Studio 2005? Почему компилятор выбирает конструктор базового classа внутри списка аргументов шаблона? Почему mstats и malloc_zone_statistics не показывают восстановленную память после бесплатного? Как понять атоматику в автономной реализации C или C ++? измерять потребление ЦП в секунду динамически связанной библиотеки Как преобразовать QImage в opencv Mat Нужна помощь в чтении файла JPEG с помощью libjpeg Можно ли «подготовить» вход от cin? Как установить заголовки заголовков для переноса в QTableWidget Можно ли полностью отключить новый оператор C ++ по умолчанию? getline () устанавливает failbit и пропускает последнюю строку Сохранение экземпляров std :: chrono time_point Переопределить только оператор * () iteratorа настраиваемой структуры данных не удалось получить текст из edit control в winapi Удаление всего после персонажа (а также персонажа)

Трудности измерения производительности C / C ++

Я написал fragment кода C, чтобы показать точку в обсуждении оптимизаций и outlookирования ветвлений. Затем я заметил еще более разнообразный результат, чем ожидал. Моя цель состояла в том, чтобы написать его на языке, который является общим подмножеством между C ++ и C, стандартным для обоих языков, и это довольно портативно. Он был протестирован на разных ПК с ОС Windows:

#include  #include  /// @return - time difference between start and stop in milliseconds int ms_elapsed( clock_t start, clock_t stop ) { return (int)( 1000.0 * ( stop - start ) / CLOCKS_PER_SEC ); } int const Billion = 1000000000; /// & with numbers up to Billion gives 0, 0, 2, 2 repeating pattern int const Pattern_0_0_2_2 = 0x40000002; /// @return - half of Billion int unpredictableIfs() { int sum = 0; for ( int i = 0; i < Billion; ++i ) { // true, true, false, false ... if ( ( i & Pattern_0_0_2_2 ) == 0 ) { ++sum; } } return sum; } /// @return - half of Billion int noIfs() { int sum = 0; for ( int i = 0; i < Billion; ++i ) { // 1, 1, 0, 0 ... sum += ( i & Pattern_0_0_2_2 ) == 0; } return sum; } int main() { clock_t volatile start; clock_t volatile stop; int volatile sum; printf( "Puzzling measurements:\n" ); start = clock(); sum = unpredictableIfs(); stop = clock(); printf( "Unpredictable ifs took %d msec; answer was %d\n" , ms_elapsed(start, stop), sum ); start = clock(); sum = unpredictableIfs(); stop = clock(); printf( "Unpredictable ifs took %d msec; answer was %d\n" , ms_elapsed(start, stop), sum ); start = clock(); sum = noIfs(); stop = clock(); printf( "Same without ifs took %d msec; answer was %d\n" , ms_elapsed(start, stop), sum ); start = clock(); sum = unpredictableIfs(); stop = clock(); printf( "Unpredictable ifs took %d msec; answer was %d\n" , ms_elapsed(start, stop), sum ); } 

Составлено с VS2010; / O2 Оптимизация Intel Core 2, WinXP:

 Puzzling measurements: Unpredictable ifs took 1344 msec; answer was 500000000 Unpredictable ifs took 1016 msec; answer was 500000000 Same without ifs took 1031 msec; answer was 500000000 Unpredictable ifs took 4797 msec; answer was 500000000 

Изменить: Полные ключи компилятора:

/ Zi / nologo / W3 / WX- / O2 / Oi / Oy- / GL / D “WIN32” / D “NDEBUG” / D “_CONSOLE” / D “_UNICODE” / D “UNICODE” / Gm- / EHsc / GS / Gy / fp: exact / Zc: wchar_t / Zc: forScope /Fp”Release\Trying.pch “/ Fa” Release \ “/ Fo” Release \ “/Fd”Release\vc100.pdb” / Gd / analysis- / errorReport: очереди

Другой человек опубликовал такие … Скомпилированные с MinGW, g ++ 4.71, -O1 оптимизация Результаты Intel Core 2, WinXP:

 Puzzling measurements: Unpredictable ifs took 1656 msec; answer was 500000000 Unpredictable ifs took 0 msec; answer was 500000000 Same without ifs took 1969 msec; answer was 500000000 Unpredictable ifs took 0 msec; answer was 500000000 

Также он опубликовал такие результаты для оптимизации -O3:

 Puzzling measurements: Unpredictable ifs took 1890 msec; answer was 500000000 Unpredictable ifs took 2516 msec; answer was 500000000 Same without ifs took 1422 msec; answer was 500000000 Unpredictable ifs took 2516 msec; answer was 500000000 

Теперь у меня вопрос. Что здесь происходит?

Более конкретно … Как фиксированная функция может принимать такое разное количество времени? Что-то не так в моем коде? Что-то сложно с процессором Intel? Компиляторы делают что-то странное? Может ли это быть из-за 32-битного кода, работающего на 64-битном процессоре?

Спасибо за внимание!

Изменить: Я принимаю, что g ++ -O1 просто повторно использует возвращаемые значения в двух других вызовах. Я также согласен с тем, что g ++ -O2 и g ++ -O3 имеют дефект, который оставляет оптимизацию. Значительное разнообразие измеренных скоростей (450% !!!) кажется загадочным.

Я посмотрел на parsingку кода, созданного VS2010. Он делал inline unpredictableIfs 3 раза. Встроенный код был довольно схожим; петля была такой же. Он не noIfs . Это немного noIfs . Для одной итерации требуется 4 шага. noIfs вычисляет, как было написано, в то время как unpredictableIfs использование jne для перехода с увеличением.

С -O1 gcc-4.7.1 вызывает unpredictableIfs только один раз и возвращает результат, поскольку он распознает, что это чистая функция, поэтому результат будет одинаковым при каждом вызове. (Моя работа была проверена, посмотрев собранную сборку.)

При более высоком уровне оптимизации функции встроены, и компилятор не узнает, что это тот же самый код, поэтому он запускается каждый раз, когда в источнике появляется вызов функции.

Кроме того, мой gcc-4.7.1 лучше всего подходит для unpredictableIfs -O1 при использовании -O1 или -O2 (кроме проблемы повторного использования, оба производят один и тот же код), а noIfs обрабатывается намного лучше с -O3 . Временные интервалы между различными прогонами одного и того же кода, однако, здесь непротиворечивы – равны или различаются на 10 миллисекунд (зернистость clock ), поэтому я не знаю, что может привести к значительному разному для unpredictableIfs вы сообщили для -O3 .

С -O2 цикл для unpredictableIfs идентичен коду, сгенерированному с -O1 (за исключением замены регистров):

 .L12: movl %eax, %ecx andl $1073741826, %ecx cmpl $1, %ecx adcl $0, %edx addl $1, %eax cmpl $1000000000, %eax jne .L12 

и для noIfs это похоже:

 .L15: xorl %ecx, %ecx testl $1073741826, %eax sete %cl addl $1, %eax addl %ecx, %edx cmpl $1000000000, %eax jne .L15 

где это было

 .L7: testl $1073741826, %edx sete %cl movzbl %cl, %ecx addl %ecx, %eax addl $1, %edx cmpl $1000000000, %edx jne .L7 

с -O1 . Оба цикла выполняются в одно и то же время, причем с unpredictableIfs бит быстрее.

С -O3 петля для unpredictableIfs становится хуже,

 .L14: leal 1(%rdx), %ecx testl $1073741826, %eax cmove %ecx, %edx addl $1, %eax cmpl $1000000000, %eax jne .L14 

и для noIfs (включая установочный код здесь), становится лучше:

  pxor %xmm2, %xmm2 movq %rax, 32(%rsp) movdqa .LC3(%rip), %xmm6 xorl %eax, %eax movdqa .LC2(%rip), %xmm1 movdqa %xmm2, %xmm3 movdqa .LC4(%rip), %xmm5 movdqa .LC5(%rip), %xmm4 .p2align 4,,10 .p2align 3 .L18: movdqa %xmm1, %xmm0 addl $1, %eax paddd %xmm6, %xmm1 cmpl $250000000, %eax pand %xmm5, %xmm0 pcmpeqd %xmm3, %xmm0 pand %xmm4, %xmm0 paddd %xmm0, %xmm2 jne .L18 .LC2: .long 0 .long 1 .long 2 .long 3 .align 16 .LC3: .long 4 .long 4 .long 4 .long 4 .align 16 .LC4: .long 1073741826 .long 1073741826 .long 1073741826 .long 1073741826 .align 16 .LC5: .long 1 .long 1 .long 1 .long 1 

он вычисляет четыре итерации сразу, и соответственно, noIfs работает почти в четыре раза быстрее.

Правильно, глядя на код ассемблера от gcc на 64-битном Linux, в первом случае с -O1, функция UnpredictableIfs действительно вызывается только один раз, а результат повторно используется.

С -O2 и -O3 функции встроены, и время, которое требуется, должно быть идентичным. Также нет никаких ветвей в любом бите кода, но перевод двух битов кода несколько отличается, я отключил строки, которые обновляют «сумму» [в %edx в обоих случаях]

UnpredictableIfs:

 movl %eax, %ecx andl $1073741826, %ecx cmpl $1, %ecx adcl $0, %edx addl $1, %eax 

NoIfs:

 xorl %ecx, %ecx testl $1073741826, %eax sete %cl addl $1, %eax addl %ecx, %edx 

Как вы можете видеть, это не совсем тождественно, но это очень похоже.

Что касается диапазона результатов в Windows (от 1016 мс до 4797 мс): вы должны знать, что clock() в MSVC возвращает истекшее время стены . В стандарте указано, что clock() должен возвращать приблизительное время процессора, затрачиваемое процессом , а другие реализации лучше справляются с этим.

Учитывая, что MSVC дает время на стене, если ваш процесс получил предварительную утилизацию при выполнении одной итерации теста, это может дать гораздо больший результат, даже если код работает примерно с таким же количеством процессорного времени.

Также обратите внимание, что clock() на многих ПК с ОС Windows имеет довольно паршивое разрешение, часто как 11-19 мс. Вы сделали достаточно итераций, это всего лишь 1%, поэтому я не думаю, что это часть расхождения, но хорошо знать, когда пытаешься написать бенчмарк. Я понимаю, что вы идете на переносимость, но если вам нужны лучшие измерения в Windows, вы можете использовать QueryPerformanceCounter который почти наверняка даст вам гораздо лучшее разрешение, хотя это еще только истекшее время на стене.

ОБНОВЛЕНИЕ: После того, как я узнал, что длительная работа в одном случае происходит последовательно, я активировал VS2010 и воспроизвел результаты. Обычно я получал что-то около 1000 мс для некоторых прогонов, 750 мс для других и 5000+ мс для необъяснимых.

Замечания:

  1. Во всех случаях код incredictableIfs () был встроен.
  2. Удаление кода noIfs () не повлияло (поэтому длительное время не было побочным эффектом этого кода).
  3. Уточнение привязки streamов к одному процессору не имело эффекта.
  4. Времена 5000 мс неизменно были более поздними. Я отметил, что более поздние экземпляры имели дополнительную инструкцию перед началом цикла: lea ecx,[ecx] . Я не понимаю, почему это должно сделать разницу в 5 раз. Кроме того, ранние и поздние экземпляры были идентичными.
  5. Извлечение volatile из переменных start и stop привело к меньшему количеству длинных прогонов, более 750 мс пробега и не более 1000 мс. (Сгенерированный код цикла во всех случаях выглядит точно таким же, а не lea s.)
  6. Удаление volatile из переменной sum (но сохранение ее для таймеров синхронизации) длинные прогоны могут происходить в любой позиции.
  7. Если вы удалите все volatile квалификаторы, вы получите последовательные, быстрые (750 мс) прогоны. (Код выглядит идентичным предыдущим, но edi был выбран для sum вместо ecx .)

Я не уверен, что заключить из всего этого, за исключением того, что volatile имеет непредсказуемые последствия для производительности с MSVC, поэтому вы должны применять ее только тогда, когда это необходимо.

ОБНОВЛЕНИЕ 2: Я вижу согласованные разницы во времени, связанные с использованием изменчивых, хотя parsingка почти идентична.

С изменчивым:

 Puzzling measurements: Unpredictable ifs took 643 msec; answer was 500000000 Unpredictable ifs took 1248 msec; answer was 500000000 Unpredictable ifs took 605 msec; answer was 500000000 Unpredictable ifs took 4611 msec; answer was 500000000 Unpredictable ifs took 4706 msec; answer was 500000000 Unpredictable ifs took 4516 msec; answer was 500000000 Unpredictable ifs took 4382 msec; answer was 500000000 

Разбор для каждого экземпляра выглядит следующим образом:

  start = clock(); 010D1015 mov esi,dword ptr [__imp__clock (10D20A0h)] 010D101B add esp,4 010D101E call esi 010D1020 mov dword ptr [start],eax sum = unpredictableIfs(); 010D1023 xor ecx,ecx 010D1025 xor eax,eax 010D1027 test eax,40000002h 010D102C jne main+2Fh (10D102Fh) 010D102E inc ecx 010D102F inc eax 010D1030 cmp eax,3B9ACA00h 010D1035 jl main+27h (10D1027h) 010D1037 mov dword ptr [sum],ecx stop = clock(); 010D103A call esi 010D103C mov dword ptr [stop],eax 

Без изменчивости:

 Puzzling measurements: Unpredictable ifs took 644 msec; answer was 500000000 Unpredictable ifs took 624 msec; answer was 500000000 Unpredictable ifs took 624 msec; answer was 500000000 Unpredictable ifs took 605 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 Unpredictable ifs took 599 msec; answer was 500000000 start = clock(); 00321014 mov esi,dword ptr [__imp__clock (3220A0h)] 0032101A add esp,4 0032101D call esi 0032101F mov dword ptr [start],eax sum = unpredictableIfs(); 00321022 xor ebx,ebx 00321024 xor eax,eax 00321026 test eax,40000002h 0032102B jne main+2Eh (32102Eh) 0032102D inc ebx 0032102E inc eax 0032102F cmp eax,3B9ACA00h 00321034 jl main+26h (321026h) stop = clock(); 00321036 call esi // The only optimization I see is here, where eax isn't explicitly stored // in stop but is instead immediately used to compute the value for the // printf that follows. 

Помимо выбора регистра, я не вижу существенной разницы.