Intel x64处理器上16位和32位乘法的CPU周期

Ric*_*ick 2 c performance intel clock

我正在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");
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

使用单核并禁用超线程/ TurboBoost的一次示例运行的结果

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

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

Run Code Online (Sandbox Code Playgroud)

最后是我的cpuinfo(摘录)由 lscpu

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

Run Code Online (Sandbox Code Playgroud)

现在我的问题是

  1. 在x64平台上,与32位乘法相比,16位乘法占用总时间的一半是否正确?或我做错了什么。

  2. 如果是,您能给我一些参考来证明这种行为吗?

先感谢您。我感谢您的帮助。

Ext*_*t3h 6

在x64平台上,与32位乘法相比,16位乘法占用总时间的一半是否正确?或我做错了什么。

不,这本身是不正确的。分别不是您实际测试过的。

您的短循环可轻松实现矢量化,因此这正是编译器所做的。取决于目标CPU的生成,这意味着可以使用128、256或512位向量类型,可以将其划分为不同的字长(8位,16位,32位,64位,128位),然后可以在以下位置对多个元素执行矢量化乘法一旦。不仅是乘法,而且从内存到内存的数字加载和存储都已完全向量化,并且不仅对单个元素进行运算。

简而言之,与32位相比,同一向量中可以容纳两倍多的16位整数。而且您的代码实际上也不受乘法的限制-它受加载/存储的限制,因此您仅成功地测量出16位整数是32位整数的一半,因此在进行矢量化并由加载/存储绑定时,您可以加载同时增加两倍的元素。

如果希望对特定指令进行基准测试(在本例中为单元素乘法),则需要通过内联汇编显式使用该特定指令。您还需要了解所有会影响性能的副作用和前提条件,流水线超标量体系结构通常对于基准测试而言并非微不足道。

否则,期望编译器尽可能优化(向量化,折叠,内联等)代码。

  • @Rick:哪个规范,更重要的是,**为什么**?现在是 2019 年了。甚至嵌入式 CPU 现在也有了矢量单元,NVidia 甚至会向您出售具有多个矩阵单元的 SoC。 (2认同)
  • 鉴于您实际上无法再购买没有矢量单元的处理器,因此我不会觉得受到规范的这一部分的特别约束。x64 从一开始就有向量数学;每个 x64 编译器都可以放心地假设它可以使用 SSE2。 (2认同)
  • @Rick如果确实有这样的要求,请确保您理解正确。确保您没有将“不要手动矢量化”与“防止矢量指令”混淆。第一个是合理的,如果不是绝对不可避免的话,你永远不应该尝试这样做。第二个完全是无稽之谈,除非您的目标架构是 Pentium III 或更早版本。 (2认同)