Циклы процессора для 16-битных и 32-битных умножений на процессорах Intel x64 - PullRequest
2 голосов
/ 26 апреля 2019

Я реализую математическую библиотеку в C, которая интенсивно использует умножения.Первоначально все мои умножения были сделаны с использованием uint16_t.Недавно я изменил многие из них на uint32_t и увидел, что время выполнения моего кода стало почти вдвое больше.Я был сбит с толку, так как думал, что в процессорах Intel x64 32- и 16-разрядные умножения занимают одинаковые такты.Я написал диагностический код, пожалуйста, найдите его ниже

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include "cpucycles.c"

#define REPEAT 10000
#define OUT_REPEAT 100000

void main(){

    uint16_t a_16[REPEAT], b_16[REPEAT], c_16[REPEAT];
    uint32_t a_32[REPEAT], b_32[REPEAT], c_32[REPEAT];
    int32_t i,j;
    uint64_t clock1, clock2, CLOCK16, CLOCK32;
    uint64_t acc=0;
    time_t t;

    srand((unsigned) time(&t));

    clock1=clock2=CLOCK16=CLOCK32=0;

    for(j=0;j<OUT_REPEAT;j++){


        for(i=0;i<REPEAT;i++){

            a_16[i]=rand()& ( (1<<13) -1); //need 13-bit integers only
            b_16[i]=rand()& ( (1<<13) -1);

            a_32[i]=rand()&( (1<<19) -1);
            b_32[i]=rand()&( (1<<19) -1); //need 19-bit integers only
        }

        clock1=cpucycles();
        for(i=0;i<REPEAT;i++){
            c_16[i]=a_16[i]*b_16[i];
        }
        clock2=cpucycles();
        CLOCK16=CLOCK16+(clock2-clock1);    

        clock1=cpucycles();
        for(i=0;i<REPEAT;i++){
            c_32[i]=a_32[i]*b_32[i];
        }
        clock2=cpucycles();
        CLOCK32=CLOCK32+(clock2-clock1);    

        for(i=0;i<REPEAT;i++){
            acc=(acc+(c_32[i]-(uint32_t)c_16[i])); //this is just to prevent compiler optimization
        }
        printf("Iteration: %d,  acc:%llu\n", j, acc);
        acc=0;
    }

    printf("\n--------------------------------------------\n");
    printf("Time for 16 bit multiplication : %llu\n", CLOCK16/OUT_REPEAT);
    printf("Time for 32 bit multiplication : %llu\n", CLOCK32/OUT_REPEAT);
    printf("\n--------------------------------------------\n");
}

Код процессора от ECRYPT и приведенный ниже

#include "cpucycles.h"

long long cpucycles(void)
{
  unsigned long long result;
  asm volatile(".byte 15;.byte 49;shlq $32,%%rdx;orq %%rdx,%%rax"
    : "=a" (result) ::  "%rdx");
  return result;
}

Результат от одного пробного прогона,использование одного ядра и гиперпоточности / отключено TurboBoost

--------------------------------------------
Time for 16 bit multiplication : 2795
Time for 32 bit multiplication : 4190

--------------------------------------------

и, наконец, мой cpuinfo (отрывок), заданный lscpu

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Model name:            Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz

Теперь мой вопрос:

  1. Правильно ли, что на платформах x64 16-битное умножение занимает почти половину общего времени, чем 32-битное умножение?Или я делаю что-то не так.

  2. Если да, не могли бы вы показать мне некоторые ссылки, чтобы оправдать такое поведение?

Заранее спасибо,Я ценю вашу помощь.

1 Ответ

6 голосов
/ 26 апреля 2019

Правильно ли, что на платформах x64 16-битное умножение занимает почти половину общего времени, чем 32-битное умножение?Или я делаю что-то не так.

Нет, это само по себе неправильно.Соответственно, это не то, что вы на самом деле тестировали.

Ваши короткие циклы тривиально векторизованы, так что именно это и сделал компилятор.В зависимости от целевого поколения ЦП, это означает, что доступен векторный тип 128, 256 или 512 бит, который может быть разбит на слова разного размера (8 бит, 16 бит, 32 бита, 64 бита, 128 бит), а затем может выполнять векторизованное умножение для нескольких элементов водин раз.Не только умножение, но и загрузка и сохранение чисел из памяти и в память полностью векторизованы и не работают только с отдельными элементами.

Проще говоря, вы можете поместить в два раза больше 16-битных целых чисел втот же вектор, по сравнению с 32-битным.И ваш код на самом деле также не ограничен умножениями - он ограничен чисто загрузкой / хранением, поэтому вы только успешно измерили, что 16-битные целые числа вдвое меньше 32-битных, поэтому при векторизации и ограничениизагрузить / сохранить вы можете загрузить вдвое больше элементов за один раз.

Если вы хотите сравнить конкретную инструкцию (в данном случае умножение одного элемента), вам нужно было бы явно использовать эту конкретную инструкцию с помощью встроенной сборки,Вам также необходимо знать обо всех побочных эффектах и ​​предварительных условиях, влияющих на производительность, конвейерные суперскалярные архитектуры не являются тривиальными для эталонных тестов в целом.

В противном случае ожидается, что компилятор оптимизирует (векторизовать, сворачивать, инлайни т.д.) ваш код максимально.

...