全局变量的意外性能

Unexpected performance with global variables

我使用全局变量得到了一个奇怪的结果。这个问题的灵感来自 。在下面的代码中,如果我更改

int ncols = 4096;

static int ncols = 4096; 

const int ncols = 4096;

代码运行得更快,汇编也更简单。

//c99 -O3 -Wall -fopenmp foo.c
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

int nrows = 4096;
int ncols = 4096;
//static int ncols = 4096;
char* buff;

void func(char* pbuff, int * _nrows, int * _ncols) {
    for (int i=0; i<*_nrows; i++) {
        for (int j=0; j<*_ncols; j++) {
            *pbuff += 1;
            pbuff++;
        }
    }
}

int main(void) {
    buff = calloc(ncols*nrows, sizeof*buff);
    double dtime = -omp_get_wtime();
    for(int k=0; k<100; k++) func(buff, &nrows, &ncols);
    dtime += omp_get_wtime();
    printf("time %.16e\n", dtime/100);
    return 0;
}

如果 char* buff 是自动变量(即不是 globalstatic),我也会得到相同的结果。我的意思是:

//c99 -O3 -Wall -fopenmp foo.c
#include <stdlib.h>
#include <stdio.h>
#include <omp.h>

int nrows = 4096;
int ncols = 4096;

void func(char* pbuff, int * _nrows, int * _ncols) {
    for (int i=0; i<*_nrows; i++) {
        for (int j=0; j<*_ncols; j++) {
            *pbuff += 1;
            pbuff++;
        }
    }
}

int main(void) {
    char* buff = calloc(ncols*nrows, sizeof*buff);
    double dtime = -omp_get_wtime();
    for(int k=0; k<100; k++) func(buff, &nrows, &ncols);
    dtime += omp_get_wtime();
    printf("time %.16e\n", dtime/100);
    return 0;
}

如果我将 buff 更改为短指针,那么性能会很快,并且不依赖于 ncols 是静态的还是常数,如果 buff 是自动的。但是,当我将 buff 设为 int* 指针时,我观察到与 char*.

相同的效果

我认为这可能是由于指针别名造成的,所以我也尝试了

void func(int * restrict pbuff, int * restrict _nrows, int * restirct _ncols)

但这没什么区别。

这是我的问题

  1. buffchar* 指针或 int* 全局指针时,为什么代码 当 ncols 具有文件范围或常量时更快?
  2. 为什么 buff 作为自动变量而不是全局变量或静态变量会使代码更快?
  3. 为什么 buff 是短指针时没有区别?
  4. 如果这是由于指针别名造成的,为什么 restrict 没有明显的效果?

请注意,我使用 omp_get_wtime() 只是因为它方便计时。

A const 很可能总是会产生比 read/write 变量更快或同样快的代码,因为编译器知道该变量不会被更改,这反过来又可以实现很多优化选项。

声明文件范围变量intstatic int 应该不会对性能产生太大影响,因为它仍将分配在同一个位置:.data 部分。

但是正如评论中提到的,如果变量是全局变量,编译器可能不得不假设其他一些文件(翻译单元)可能会修改它并因此阻止一些优化。我想这就是正在发生的事情。

但这无论如何都不是问题,因为从来没有理由在 C 中声明一个全局变量。始终将它们声明为 static 以防止变量被滥用于意大利面条式编码目的。

一般来说,我也会质疑您的基准测试结果。在 Windows 你应该使用 QueryPerformanceCounter 和类似的。 https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408%28v=vs.85%29.aspx

正如所写的那样,某些元素允许 GCC 在优化方面采取不同的行为;我们看到的最有影响力的优化可能是循环矢量化。因此,

Why is the code faster?

代码速度更快,因为它的热部分,func 中的循环,已经通过自动矢量化进行了优化。在 ncolsstatic/const 的情况下,确实,GCC 发出:

note: loop vectorized
note: loop peeled for vectorization to enhance alignment

如果您打开 -fopt-info-loop-fopt-info-vec 或它们与进一步 -optimized 的组合,则可见,因为它具有相同的效果。


  1. Why does buff being an automatic variable instead of global or static make the code faster?

在这种情况下,GCC 能够计算应用矢量化直观所需的迭代次数。这又是由于 buf 的存储,如果没有另外指定,它是外部的。整个矢量化会立即被跳过,这与 buff 在本地进行并成功的情况不同。

  1. Why does it make no difference when buff is a short pointer?

为什么要这样做? func 接受一个 char* 可以别名任何东西。

  1. If this is due to pointer aliasing why does restrict have no noticeable effect?

我不认为,因为 GCC 可以看到它们在调用 func 时没有别名:不需要 restrict