堆阵列性能缓慢

Slow heap array performance

本文关键字:缓慢 性能 阵列      更新时间:2023-10-16

我遇到了奇怪的内存访问性能问题,有什么想法吗?

int* pixel_ptr = somewhereFromHeap;
int local_ptr[307200]; //local
//this is very slow
for(int i=0;i<307200;i++){
  pixel_ptr[i] = someCalculatedVal ;
}
//this is very slow
for(int i=0;i<307200;i++){
  pixel_ptr[i] = 1 ; //constant
}
//this is fast
for(int i=0;i<307200;i++){
  int val = pixel_ptr[i];
  local_ptr[i] = val;
}
//this is fast
for(int i=0;i<307200;i++){
  local_ptr[i] = someCalculatedVal ;
}

已尝试将值合并到本地扫描线

int scanline[640]; // local
//this is very slow
for(int i=xMin;i<xMax;i++){
  int screen_pos = sy*screen_width+i;
  int val = scanline[i];
  pixel_ptr[screen_pos] = val ;
}
//this is fast
for(int i=xMin;i<xMax;i++){
  int screen_pos = sy*screen_width+i;
  int val = scanline[i];
  pixel_ptr[screen_pos] = 1 ; //constant
}
//this is fast
for(int i=xMin;i<xMax;i++){
  int screen_pos = sy*screen_width+i;
  int val = i; //or a constant
  pixel_ptr[screen_pos] = val ;
}
//this is slow
for(int i=xMin;i<xMax;i++){
  int screen_pos = sy*screen_width+i;
  int val = scanline[0];
  pixel_ptr[screen_pos] = val ;
}

有什么想法吗?我正在使用带有 cflags -01 -std=c++11 -permissive 的 mingw。

更新4:我不得不说这些是我的程序片段,并且在之前和之后运行了大量代码/函数。扫描线块确实在退出前的功能结束时运行。

现在有了适当的测试程序。 谢谢@Iwillnotexist。

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#define SIZE 307200
#define SAMPLES 1000
double local_test(){
    int local_array[SIZE];
    timeval start, end;
    long cpu_time_used_sec,cpu_time_used_usec;
    double cpu_time_used;
    gettimeofday(&start, NULL);
    for(int i=0;i<SIZE;i++){
        local_array[i] = i;
    }
    gettimeofday(&end, NULL);
    cpu_time_used_sec = end.tv_sec- start.tv_sec;
    cpu_time_used_usec = end.tv_usec- start.tv_usec;
    cpu_time_used = cpu_time_used_sec*1000 + cpu_time_used_usec/1000.0;
    return cpu_time_used;
}
double heap_test(){
    int* heap_array=new int[SIZE];
    timeval start, end;
    long cpu_time_used_sec,cpu_time_used_usec;
    double cpu_time_used;
    gettimeofday(&start, NULL);
    for(int i=0;i<SIZE;i++){
        heap_array[i] = i;
    }
    gettimeofday(&end, NULL);
    cpu_time_used_sec = end.tv_sec- start.tv_sec;
    cpu_time_used_usec = end.tv_usec- start.tv_usec;
    cpu_time_used = cpu_time_used_sec*1000 + cpu_time_used_usec/1000.0;
    delete[] heap_array;
    return cpu_time_used;
}

double heap_test2(){
    static int* heap_array = NULL;
    if(heap_array==NULL){
        heap_array = new int[SIZE];
    }
    timeval start, end;
    long cpu_time_used_sec,cpu_time_used_usec;
    double cpu_time_used;
    gettimeofday(&start, NULL);
    for(int i=0;i<SIZE;i++){
        heap_array[i] = i;
    }
    gettimeofday(&end, NULL);
    cpu_time_used_sec = end.tv_sec- start.tv_sec;
    cpu_time_used_usec = end.tv_usec- start.tv_usec;
    cpu_time_used = cpu_time_used_sec*1000 + cpu_time_used_usec/1000.0;
    return cpu_time_used;
}

int main (int argc, char** argv){
    double cpu_time_used = 0;
    for(int i=0;i<SAMPLES;i++)
        cpu_time_used+=local_test();
    printf("local: %f msn",cpu_time_used);
    cpu_time_used = 0;
    for(int i=0;i<SAMPLES;i++)
        cpu_time_used+=heap_test();
    printf("heap_: %f msn",cpu_time_used);
    cpu_time_used = 0;
    for(int i=0;i<SAMPLES;i++)
        cpu_time_used+=heap_test2();
    printf("heap2: %f msn",cpu_time_used);
}

符合未优化。

本地: 577.201000 ms

heap_: 826.802000 ms

堆2: 686.401000 ms

使用 new 和 delete 的第一次堆测试慢 2 倍。(按照建议分页?

具有重用堆数组的第二个堆仍然慢 1.2 倍。但我想第二个测试并不那么实用,因为至少在我的案例中,往往会有其他代码在之前和之后运行。就我而言,我的pixel_ptr当然只分配一次程序初始化。

但是,如果有人有加快速度的解决方案/想法,请回复!

我仍然很困惑为什么堆写入比堆栈段慢得多。当然,必须有一些技巧可以使堆更具CPU/缓存的味道。

最终更新?

我重新审视了一次,再次拆卸,这一次,突然我知道为什么我的一些断点不要激活。该程序看起来可疑地短,因此我怀疑编译器可能会删除了我输入的冗余虚拟代码,该代码解释了为什么本地数组神奇地快了很多倍。

我有点好奇,所以我做了测试,事实上我可以测量堆栈和堆访问之间的差异。

第一个猜测是生成的程序集是不同的,但是在查看之后,它实际上在堆和堆栈方面是相同的(这是有道理的,内存不应该被区分)。

如果程序集相同,则差异必须来自分页机制。猜测是在堆栈上,页面已经分配,但在堆上,首次访问会导致页面错误和页面分配(不可见,这一切都发生在内核级别)。为了验证这一点,我做了同样的测试,但首先我会在测量之前访问堆一次。该测试给出了相同的堆栈和堆时间。可以肯定的是,我还做了一个测试,我首先访问了堆,但只有每 4096 字节(每 1024 个整数),然后是 8192,因为一个页面通常有 4096 字节长。结果是,仅访问每 4096 字节也会为堆和堆栈提供相同的时间,但访问每个 8192 字节会产生差异,但不如以前根本没有访问那么多。这是因为事先只访问和分配了一半的页面。

所以答案是,在堆栈上,内存页已经分配,但在堆上,页面是动态分配的。这取决于操作系统分页策略,但所有主要的 PC 操作系统可能都有类似的策略。

对于所有测试,我都使用Windows,MS编译器面向x64。

编辑:对于测试,我测量了一个较大的循环,因此每个内存位置只有一个访问。 delete数组并多次测量同一循环应该为堆栈和堆提供相似的时间,因为delete内存可能不会取消分配页面,并且它们已经分配给下一个循环(如果下一个new分配在同一空间上)。

以下两个代码示例在运行时与良好的编译器设置不应有所不同。可能您的编译器将生成相同的代码:

//this is fast
for(int i=0;i<307200;i++){
  int val = pixel_ptr[i];
  local_ptr[i] = val;
}
//this is fast
for(int i=0;i<307200;i++){
  local_ptr[i] = pixel_ptr[i];
}

请尝试增加优化设置。