【问题标题】:Intel(x86_64) 64 bit vs 32 bit integer arithmetic performance difference [duplicate]Intel(x86_64)64位与32位整数算术性能差异[重复]
【发布时间】:2021-05-05 23:50:50
【问题描述】:

我在测试某个程序时遇到了一个相当意外的异常情况。

我编写了一个计算素数的简单程序,并使用 pthreads API 来并行化这个工作负载。

在进行了一些测试后,我发现如果我使用 uint64_t 作为计算和循环的数据类型,程序运行时间要比使用 uint32_t 多得多。

这是我运行的代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <pthread.h>

#define UINT uint64_t
#define SIZE (1024 * 1024)

typedef struct _data
{
    UINT start;
    UINT len;
    int t;
    UINT c;
}data;

int isprime(UINT x)
{
    uint8_t flag = 1;

    if(x < 2)
        return 0;

    for(UINT i = 2;i < x/2; i++)
    {
        if(!(x % i ))
        {
            flag = 0;
            break;
        }
    }

    return flag;
}

void* calc(void *p)
{
    data *a = (data*)p;

    //printf("thread no. %d has start: %lu length: %lu\n",a->t,a->start,a->len);

    for(UINT i = a->start; i < a->len; i++)
    {
        if(isprime(i))
            a->c++;
    }

    //printf("thread no. %d found %lu primes\n", a->t,a->c);
    
    pthread_exit(NULL);
}

int main(int argc,char **argv)
{   
    pthread_t *t;
    data *a;
    uint32_t THREAD_COUNT;

    if(argc < 2)
        THREAD_COUNT = 1;
    else
        sscanf(argv[1],"%u",&THREAD_COUNT);
    
    t = (pthread_t*)malloc(THREAD_COUNT * sizeof(pthread_t));
    a = (data*)malloc(THREAD_COUNT * sizeof(data));

    printf("executing the application on %u thread(s).\n",THREAD_COUNT);

    for(uint8_t i = 0; i < THREAD_COUNT; i++)
    {
        a[i].t = i;
        a[i].start = i * (SIZE / THREAD_COUNT);
        a[i].len = a[i].start + (SIZE / THREAD_COUNT);
        a[i].c = 0;
    }
    
    for(uint8_t i = 0; i < THREAD_COUNT; i++)
        pthread_create(&t[i],NULL,calc,(void*)&a[i]);
    for(uint8_t i = 0; i < THREAD_COUNT; i++)
        pthread_join(t[i],NULL);

    free(a);
    free(t);
    
    return 0;
}

我在 uint32_t 和 uint64_t 之间更改了 UINT 宏,并编译并运行了程序,并在 linux 上使用 time 命令确定了它的运行时间。

我发现 uint64_t 与 uint32_t 的运行时之间存在重大差异。

在使用 uint32_t 时,程序需要 46s 才能运行,而使用 uint64_t 需要 2m49s 才能运行!

我在这里写了一篇关于它的博客文章:https://qcentlabs.com/index.php/2021/02/01/intelx86_64-64-bit-vs-32-bit-arithmetic-big-performance-difference/

如果您想了解更多信息,可以查看该帖子。

这背后的问题可能是什么? x86_64 上的 64 位算术比 32 位慢吗?

【问题讨论】:

  • 如果您填充 struct _data 使其大小为 64 字节,问题会消失吗?较小的结构体意味着在同一个 64 字节缓存行上有更多的结构体,这会损害性能。
  • @MikelRychliski 在 uint32_t 的情况下,结构的大小为 16 字节,而在 uint64_t 的结构的大小为 32 字节的情况下,根据您的建议,我将它们都填充为 64 字节。再次运行测试后没有观察到差异,使用 uint64_t 在单线程上耗时 2m49s,而 uint32_t 在单线程上耗时 46s。
  • 请参阅uops.info/…,它确认 64 位除法(由您的 x % i 调用)比 32 位贵 3-4 倍。所以这似乎与你观察到的一致。
  • 顺便说一句,对于像在 main 函数中那样进行数组索引的循环,为了获得最佳性能,您通常希望将循环计数器设为 size_t 类型。

标签: c linux performance x86 cpu-architecture


【解决方案1】:

一般而言,64 位算术与 32 位一样快,忽略诸如占用更多内存和 BW 之类的较大操作数,并且在 x86-64 上寻址完整的 64 位寄存器需要更长的指令。

但是,您已经成功地遇到了该规则的少数例外之一,即用于计算除法的 div 指令。

【讨论】:

  • 有趣的事实:这是英特尔效应(在 IceLake 之前的 CPU 上)。在 AMD 上,div r64div r32 在输入数字相同的情况下具有(几乎?)相同的性能。 (查看链接的副本)
猜你喜欢
  • 2012-02-15
  • 1970-01-01
  • 2010-11-27
  • 2012-01-13
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2010-09-14
  • 1970-01-01
相关资源
最近更新 更多