我在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");
}
cpucycles代码来自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
现在我的问题是,
先感谢您。我感谢您的帮助。
在x64平台中,16位乘法几乎占总时间的一半而不是32位乘法是正确的吗?或者我做错了什么。
不,这本身并不正确。分别不是你实际测试过的。
您的短循环可以轻松地进行矢量化,因此这正是编译器所做的。根据目标CPU的生成,这意味着可以使用128,256或512bit矢量类型,可以将其划分为不同的字大小(8位,16位,32位,64位,128位),然后可以在多个元素上执行矢量化乘法运算。一旦。不仅是乘法,而且从内存加载和存储数字是完全矢量化的,并不仅仅在单个元素上运行。
简而言之,与32位相比,在同一向量中可以容纳两倍的16位整数。而且你的代码实际上也不受乘法的限制 - 它纯粹受加载/存储的限制,所以你只能成功地测量出16位整数是32位整数的一半,所以当矢量化和加载/存储绑定时你可以加载在同一时间内两倍的元素。
如果您要对特定指令进行基准测试(在这种情况下为单元素乘法),您是否需要通过内联汇编显式使用该特定指令。您还需要了解影响性能的所有副作用和前提条件,流水线式超标量体系结构通常不易于进行基准测试。
否则,编译器应尽可能地优化(矢量化,折叠,内联等)代码。