Pull to refresh

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

C++ *Visual Studio *
Приступая к написанию тестовой программы для этой статьи я внутренне ожидал, что 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 для получения полной ясности, судя по первому комментарию.
Tags:
Hubs:
Total votes 33: ↑21 and ↓12 +9
Views 3.2K
Comments Comments 29