Приступая к написанию тестовой программы для этой статьи я внутренне ожидал, что CPU Intel положит на обе лопатки AMD, так же как и одноименный компилятор без боя победит Visual Studio. Но не все так просто, может быть, на это повлиял выбор программного теста?
Для теста я использовал целочисленное умножение двух 128-ми битных чисел с получением 256-ти битного результата. Тест повторялся 1 млрд раз и занял всего от 10 до 50 секунд. Использовались процессоры AMD FX-8150 3.60GHz и Intel Core i5 2500 3.30GHz. Никакой мультипоточности, никакого разгона.
Использовались компиляторы Intel Parallel Studio XE Version 12.0.0.104 Build 20101006, Visual Studio 2010 SP1 и самый современный (с интерфейсом Metro и CAPSLOCK меню) Visual Studio 2012, он же С++ 11.0 Release Candidate. Про опцию -O2 не забываем, она включена у всех, хотя для Intel это необязательно, он оптимизирует с -O2 по умолчанию.
Приведу сам тест. Согласен, что для 64-х битового кода нужно было бы сделать _WORD равным __int64, _DWORD тогда разделить на low и high части, а для умножения этого хозяйства задействовать intrinsic под названием _mul128, который поддерживается данными компиляторами. Все это есть в планах и предполагается сделать позднее. Целью данной статьи является сравнение оптимизирующих компиляторов, но не сравнение скорости 32-х и 64-х битового умножения, а также развенчивание одного мифа.
#include <stdio.h>
#include <windows.h>
#define QUANTITY 4
typedef unsigned int _WORD;
typedef unsigned __int64 _DWORD;
void Mul(_WORD *C, _WORD *A, _WORD *B )
{
_WORD Carry = 0;
_WORD h = *(B++);
int i, j;
union {
_DWORD sd;
_WORD sw[2];
} s;
for( i = QUANTITY; i > 0; --i)
{
s.sd = (_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 = (_DWORD) *(A++) * h + *C + Carry;
*C++ = s.sw[0];
Carry = s.sw[1];
}
*C = Carry;
}
}
int get_const_0x87654321(void)
{
volatile int a = 0x87654321;
return a;
}
int main(void)
{
LARGE_INTEGER lFrequency, lStart, lEnd;
double dfTime1;
_WORD A[QUANTITY], B[QUANTITY], C[QUANTITY*2];
_WORD RES[QUANTITY*2]={0xd7a44a41, 0xf6e4895c, 0x1624c878, 0x35650795,
0xa55cb22f, 0x861c7313, 0x66dc33f7, 0x479bf4db };
int i,j;
for( i=0; i<QUANTITY; ++i)
{
A[i] = B[i] = get_const_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 secn", dfTime1);
}
Функцию get_const пришлось вводить отдельно и обозначать переменную внутри нее как volatile, иначе особо умные компиляторы (VS2010) отказывались вызывать функцию Mul и сразу возвращали нужный результат, забивая массив C заранее вычисленной константой.
Полученные результаты приведены в таблице:
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 | 17.0761 sec | 16.0887 sec | 11.1014 sec | 14.6462 sec |
Visual Studio 2010 C++ 10.0 SP1 | 16.8051 sec | 9.67015 sec | 10.9718 sec | 9.23577 sec |
Visual Studio 2012 C++ 11.0 Release Candidate | 16.9935 sec | 53.0926 sec | 10.5944 sec | 41.6702 sec |
На 64-битном AMD (точнее, конечно, на 64-битном коде для AMD) имеем примерно одинаковый результат для всех трех компиляторов.
На 32-х битах существенно выигрывает старый добрый VS2010, Intel плетется за ним, проигрывая 60%, а вот новейший VS2012 подкачал, с результатом в 5,5 раз хуже. Ассемблерный код, генерируемый VS2012, изучается, но почему происходит такое чудовищное замедление, по сравнению с кодом от VS2010, выяснить пока не удалось. Вполне возможно, что это особенность Release Candidate, а когда будет окончательный вариант, этот эффект исчезнет.
Интересно также сравнить скорость работы на AMD и Core i5. При схожей цене в 7000 руб процессоры показывают схожую производительность. Хотя ожидалось, что в однопоточном тесте будет преимущество Core i5. В планах написание мультипоточного теста, чтобы задействовать всю мощь 8-ми ядер AMD. И тогда уже он, скорее всего, выиграет, так как у него 8 целочисленных арифметических ядер (но 4 ядра с плавающей точкой) против 4-х ядер у Core i5, пусть даже и с поддержкой multi-threading.
Еще один важный вывод напрашивается сам собой — производители все силы бросили на создание оптимизирующего 64-х битного компилятора, при этом добились похожих результатов. С другой стороны, 32-х битный код сейчас не в почете, Intel проигрывает VS2010, и даже более новый VS2012 проигрывает ему же, демонстрируя регресс.
Еще один интересный факт — развенчан миф, что компилятор Intel якобы создает код, который хорошо работает только на Intel, и показывает плачевную производительность на AMD (медленнее в 2 и более раз).
Смотрим Intel: 16.0887 sec -> 14.6462 sec, что в 1,098 быстрее.
Теперь VS2010: 9.67015 sec -> 9.23577 sec, что в 1,047 раза быстрее.
Мы видим, что компилятор Intel дает несущественный рост (+9,8% против +4,7%) при переходе на CPU Intel. Рост скорее обусловлен более совершенным железом, чем кодом.
Автор: kostik450