Сравниваем производительность целочисленного умножения

в 15:43, , рубрики: intel parallel studio, Visual Studio, vs2010, vs2012, метки: , ,

Сравниваем производительность целочисленного умноженияПриступая к написанию тестовой программы для этой статьи я внутренне ожидал, что 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

* - обязательные к заполнению поля