Приступая к написанию тестовой программы для этой статьи я внутренне ожидал, что CPU Intel положит на обе лопатки AMD, так же как и одноименный компилятор без боя победит Visual Studio. Но не все так просто, может быть, на это повлиял выбор программного теста? Для теста я использовал целочисленное умножение двух 128-ми битных чисел с получением 256-ти битного результата. Тест повторялся 1 млрд раз и занял всего от 12 до 85 секунд. Использовались процессоры AMD FX-8150 3.60GHz и Intel Core i5 2500 3.30GHz. Никакой мультипоточности, никакого разгона.
Использовались компиляторы Intel Parallel Studio XE Version 12.0.0.104 Build 20101006 его более новая реинкарнация 12.1.5.344 Build 20120612, Visual Studio 2010 SP1 и самый современный Visual Studio 2012 (с интерфейсом Metro и CAPSLOCK меню), он же С++ 11.0 Release Candidate. Про опцию -O2 не забываем, она включена у Visual Studio. А для Intel это необязательно, он оптимизирует с -O2 по умолчанию, для Intel включена опция -O3.
Приведу сам тест. Согласен, что для 64-х битового кода нужно было бы сделать BN_WORD равным __int64, BN_DWORD тогда разделить на low и high части, а для умножения этого хозяйства задействовать intrinsic под названием _mul128, который поддерживается данными компиляторами. Все это есть в планах и предполагается сделать позднее. Целью данной статьи является сравнение оптимизирующих компиляторов, но не сравнение скорости 32-х и 64-х битового умножения, а также развенчивание одного мифа.
#include <stdio.h> #include <windows.h> #define QUANTITY 4 typedef unsigned int BN_WORD; typedef unsigned __int64 BN_DWORD; void Mul(BN_WORD *C, BN_WORD *A, BN_WORD *B ) { BN_WORD Carry = 0; BN_WORD h = *(B++); int i, j; union { BN_DWORD sd; BN_WORD sw[2]; } s; for( i = QUANTITY; i > 0; --i) { s.sd = (BN_DWORD) *(A++) * h + Carry; *C++ = s.sw[0]; Carry = s.sw[1]; } *C = Carry; for ( j = QUANTITY-1; j > 0; --j ) { A -= QUANTITY; h = *(B++); C -= QUANTITY-1; Carry = 0; for( i = QUANTITY; i > 0; --i ) { s.sd = (BN_DWORD) *(A++) * h + *C + Carry; *C++ = s.sw[0]; Carry = s.sw[1]; } *C = Carry; } } typedef void (*my_proc)(BN_WORD*, BN_WORD*, BN_WORD*); void put_addr(void) { FILE *f=fopen("tmp.$$$", "wb"); my_proc proc = Mul; fwrite(&proc, 1, sizeof(proc), f); fclose(f); } my_proc get_addr(void) { FILE *f=fopen("tmp.$$$", "rb"); my_proc proc = NULL; fread(&proc, 1, sizeof(proc), f); fclose(f); return proc; } int main(void) { int i,j; LARGE_INTEGER lFrequency, lStart, lEnd; double dfTime1; BN_WORD A[QUANTITY], B[QUANTITY], C[QUANTITY*2]; BN_WORD RES[QUANTITY*2]={0xd7a44a41, 0xf6e4895c, 0x1624c878, 0x35650795, 0xa55cb22f, 0x861c7313, 0x66dc33f7, 0x479bf4db }; //это ухищрение, чтобы избежать inline вставки кода функции Mul в тело main void (*mul)( BN_WORD *C, BN_WORD *A, BN_WORD *B ); put_addr(); mul = get_addr(); for( i=0; i<QUANTITY; ++i) { A[i] = B[i] = 0x87654321; } QueryPerformanceFrequency(&lFrequency); QueryPerformanceCounter(&lStart); for( i=0; i<1000; ++i) { for( j=0; j<1000000; ++j) { mul(C, A, B); } if (memcmp(RES, C, sizeof(RES))!=0) { printf("Something wrong!\n"); } } QueryPerformanceCounter(&lEnd); dfTime1 = (double)(lEnd.QuadPart - lStart.QuadPart) / (double)lFrequency.QuadPart; printf("Time = %g sec\n", dfTime1); }
Полученные результаты приведены в таблице:
| AMD FX-8150 3.60GHz 64 бит | AMD FX-8150 3.60GHz 32 бит | Core i5-2500 3.30GHz 64 бит | Core i5-2500 3.30GHz 32 бит | |
|---|---|---|---|---|
| Intel Parallel Studio XE 12.0.0.104 Build 20101006 | 22.6235 sec | 25.913 sec | 13.0921 sec | 23.1986 sec |
| Intel Parallel Studio XE 12.1.5.344 Build 20120612 | 22.2398 sec | 26.0347 sec | 12.9242 sec | 23.1603 sec |
| Visual Studio 2010 C++ 10.0 SP1 | 22.5853 sec | 84.1714 sec | 12.4991 sec | 53.633 sec |
| Visual Studio 2012 C++ 11.0 Release Candidate | 22.2952 sec | 72.8279 sec | 12.6212 sec | 47.1136 sec |
На 64-битном коде имеем примерно одинаковый результат для всех трех компиляторов.
На 32-х битах существенно выигрывает Intel, за ним плетется VS2010, а новейший VS2012 демонстрирует уверенный рост, хотя до Intel ему далеко.
Интересно также сравнить скорость работы на AMD и Core i5. При схожей цене в 7000 руб процессоры показывают схожую производительность в 32-битных приложениях на компиляторе Intel. Хотя ожидалось, что в однопоточном тесте всегда будет преимущество Core i5. В планах есть написание мультипоточного теста, чтобы задействовать всю мощь 8-ми ядер AMD. И тогда уже он, скорее всего, выиграет, так как у него 8 целочисленных арифметических ядер (но 4 ядра с плавающей точкой) против 4-х ядер у Core i5, поддержки multi-threading у (моего) Core i5 нет.
Еще один важный вывод напрашивается сам собой — производители компиляторов все силы бросили на создание оптимизирующего 64-битного компилятора, при этом добились похожих результатов. Производители процессоров также все силы бросили на 64-битную платформу, при этом Intel существенно выигрывает у AMD.
Еще один интересный факт — развенчан миф, что компилятор Intel якобы создает код, который хорошо работает только на Intel, и показывает плачевную производительность на AMD (медленнее в 2 и более раз). Легко увидеть, что компилятор Intel на 32-битном коде дает примерно такой же результат при переходе с AMD на CPU Intel, а вот компилятор VS дает прирост, почему на Core i5 происходит существенное ускорение, если использовать код от VS, выяснить пока не удалось. (Действительно, почему?)
UPD1. В первую редакцию этой статьи закралась ошибка, связанная с тем, что из-за хитрой оптимизации VS2010 не производил умножения и код получался в 5,5 раз быстрее. Сейчас исходник исправлен (введен указатель mul на ф-ию Mul, причем указатель пишется/читается из файла, чтобы обмануть компилятор), результаты обновлены. И еще _WORD исправлен на BN_WORD для получения полной ясности, судя по первому комментарию.
