【问题标题】:Slow heap array performance堆阵列性能缓慢
【发布时间】:2015-01-08 06:21:26
【问题描述】:

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

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 -fpermissive 的 mingw。

更新4: 我不得不说这些是我程序中的 sn-ps,并且在之前和之后运行了大量的代码/函数。扫描线块确实在退出之前在函数末尾运行。

现在有了适当的测试程序。感谢@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 ms\n",cpu_time_used);

    cpu_time_used = 0;

    for(int i=0;i<SAMPLES;i++)
        cpu_time_used+=heap_test();

    printf("heap_: %f ms\n",cpu_time_used);

    cpu_time_used = 0;

    for(int i=0;i<SAMPLES;i++)
        cpu_time_used+=heap_test2();

    printf("heap2: %f ms\n",cpu_time_used);

}

符合但没有优化。

本地:577.201000 毫秒

堆_:826.802000 毫秒

heap2:686.401000 毫秒

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

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

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

我仍然很困惑为什么堆写入比堆栈段慢得多。 肯定有一些技巧可以让堆更有 cpu/cache 的味道。

最终更新?:

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

【问题讨论】:

  • 请解释一下奇怪的内存访问性能问题..
  • 为什么只有-O1?试试-O2-O3
  • “慢”有多慢?
  • 您应该将测试代码放在外部循环中并忽略第一次迭代,因为您可能只是看到与缓存相关的性能问题。
  • 这就是我在这里得到的coliru.stacked-crooked.com/a/a3037192c50b6445

标签: c++ arrays performance


【解决方案1】:

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

第一个猜测是生成的程序集不一样,但看了看,堆和栈其实是一样的(有道理,内存不应该区分)。

如果程序集相同,则差异必须来自paging mechanism。猜测是在堆栈上,页面已经分配,​​但在堆上,第一次访问会导致页面错误和页面分配(不可见,这一切都发生在内核级别)。为了验证这一点,我做了同样的测试,但首先我会在测量之前访问堆一次。该测试为堆栈和堆提供了相同的时间。可以肯定的是,我也做了一个测试,我首先访问了堆,但只有每 4096 字节(每 1024 个 int),然后是 8192,因为一个页面通常是 4096 字节长。结果是,每 4096 字节访问一次也会为堆和堆栈提供相同的时间,但每 8192 次访问会有所不同,但不如以前完全没有访问。这是因为只有一半的页面被预先访问和分配。

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

对于我使用 Windows 的所有测试,MS 编译器针对 x64。

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

【讨论】:

  • 那么有没有办法暗示一块内存准备好使用/缓存?我正在阅读矢量化和其他东西,只是意外地意识到,缓慢的主要原因是单行数组写入。
  • @xyz 我不认为你可以在这里获得任何性能。这些页面必须在某个时候分配,并且更早分配不会更快。您可以做的是减少最大内存占用以减少使用的页面数量,方法是确保在不再需要它们时尽快删除它们。无论如何,我认为这里没有什么可以优化的。页面分配很快,内存写入也很快。通过预分配,您的带有 make_unique 的代码达到了 20GB/s 的最大 RAM 带宽,并且仍然超过 12GB/s。我认为没有必要对此进行优化。
【解决方案2】:

以下两个代码示例在运行时不应有差异,并具有良好的编译器设置。可能您的编译器会生成相同的代码:

//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];
}

请尝试增加优化设置。

【讨论】:

  • 这可能是真的,但我认为它不能解决问题。
  • 同样的结果。没有区别。
  • 那为什么不看看生成的ASM代码呢?
  • 我对cpu指令不熟悉。但我会尽量收集差异。
猜你喜欢
  • 2013-06-09
  • 1970-01-01
  • 2015-02-01
  • 2010-10-14
  • 2015-01-27
  • 2021-06-04
  • 2016-11-09
  • 2021-06-21
  • 2012-05-11
相关资源
最近更新 更多