【问题标题】:C++ pool allocator vs static allocation , cache performanceC++ 池分配器与静态分配,缓存性能
【发布时间】:2014-04-23 21:45:56
【问题描述】:

鉴于我有以下结构的两个平行且大小相同的数组:

    struct Matrix
    {
        float data[16];
    };

    struct Vec4
    {
        float data[4];
    }

    //Matrix arrM[256];  //for illustration
    //Vec4 arrV[256];

假设我希望尽可能快地依次迭代这两个数组。假设函数类似于:

for (int i=0; i < 256; ++i)
{
    readonlyfunc(arrMPtr[i].data);
    readonlyfunc(arrVPtr[i].data
}

假设我的分配对于每个数组都是对齐的,无论是在静态分配内存还是堆内存的情况下。假设我的缓存行大小为 64 字节。

如果我将数据存储为,我能否获得相同的缓存位置和性能:

一)

//aligned
 static Matrix arrM[256];
 static Vec4 arrV[256];

 Matrix* arrMPtr = arrM[0];
 Vec4* arrVPtr = arrV[0];

对比

B)

//aligned
char* ptr = (char*) malloc(256*sizeof(Matrix)+256*sizeof(Vec4));

Matrix* arrMPtr = (Matrix*) ptr;
Vec4* arrVPtr = (Vec4*) ptr+256*sizeof(Matrix);

【问题讨论】:

  • 可能没有区别,两者(静态或动态内存)将以相同的方式访问(通过指针)
  • 这两个静态数组是否相对于彼此是连续的?顺序呢,有关系吗?
  • single 访问并不完全相同,但对于这样的循环,我怀疑会有可测量的差异。
  • @skimon:它们可以“任意”分配,所以不,你不能假设它们是连续的(我不明白为什么会重要)。订单是什么意思?
  • 如果不知道更多关于你在做什么,就不可能说出来。请记住,静态数组可能已经在 L1 缓存中(在您的情况下,256 个矩阵和向量将适合 32kb 缓存),而从堆分配的数组必须读入 L1 缓存。

标签: c++ caching memory-management


【解决方案1】:

内存的分配方式(堆或静态分配)对内存的缓存能力没有影响。由于这两种数据结构都相当大(分别为 1024 和 4096 字节),因此第一个和最后一个元素的精确对齐可能也无关紧要(但如果您使用 SSE 指令访问内容,这很重要!) .

内存是否靠近不会有很大的不同,只要分配足够小以轻松放入缓存中,但足够大以占用多个缓存行。

如果您按顺序处理两个数组,您可能会发现使用具有 20 个浮点值的结构效果更好。但这仅在您不需要对具有单个数组更有意义的数据执行其他操作时才有效。

编译器翻译代码以避免额外内存访问的能力可能有所不同。这显然取决于实际代码(例如,编译器内联函数是否包含 for 循环,是否内联 readonlyfunc 代码等,等等。如果是这样,静态分配可以从指针变量(加载将获取数据地址的指针地址)转化为常量地址计算。在这样的大循环中,可能不会有太大的区别。

总是,在性能方面,有时小事会产生很大的影响,所以如果这真的很重要,请使用您的编译器和您的实际代码进行一些实验。根据我们的经验,我们只能给出相对推测的建议。不同的编译器使用相同的代码做不同的事情,不同的处理器使用相同的机器代码做不同的事情(两种不同的实际架构(无论是指令集架构 ARM 与 X86,还是架构的实现,例如 AMD Opteron 与 Intel Atom 或 ARM Cortex A15 vs Cortex M3)。特定系统上的内存配置也会影响事物,缓存有多大等等。

【讨论】:

  • 感谢您的详细回答。我的理解是,将被拉入缓存的内容将是我们访问后立即出现的行/行。如果静态分配在内存中不相邻,我不确定这是否可行。而在 malloc 示例中,分配是完全相邻的(至少在虚拟内存中)。想想缓存是否使用内存位置的物理地址来决定什么被驱逐 - 如果是这样,除非内存在物理内存中真正连续,否则所有这些练习都是没有意义的?
  • 我不是这个意思。我的意思是没有区别 - 所有数组,无论是使用 malloc 分配还是作为静态数组声明分配,都将是每个数组的连续内存区域(在虚拟空间中 - 在物理空间中可能是连续的,也可能不是连续的)。缓存通常在物理地址上工作(因此如果另一个进程进入,则不必清除整个缓存),但在任何合理的现代机器中,物理内存中的内存块仍然至少为 4KB。再次,对您的系统进行基准测试,比较两种解决方案,看看您是否发现任何差异。我怀疑不是。
【解决方案2】:

如果不了解您的工作和测试的更多信息,就不可能说出来。使用数组结构可能更有效。

struct MatrixVec
{
    float m[16];
    float v[4];
};

重要的一点是 malloc 从堆中分配内存,而静态数组在堆栈上分配。堆栈可能已经在 L1 缓存中,而堆中的内存必须被读入。相反,您可以尝试一个鲜为人知的动态内存分配函数alloca,它在堆栈上分配内存。在你的情况下,你可以尝试

char* ptr = (char*) alloca(256*sizeof(Matrix)+256*sizeof(Vec4))

请参阅 Agner Fog 的 Optimizing software in C++。请参阅“9.6 动态内存分配”部分。以下是他列出的 allocamalloc 相比的优势

  • 分配过程的开销很小,因为微处理器 为堆栈提供硬件支持。
  • 由于先进后出的特性,内存空间永远不会碎片化 堆栈的。
  • 释放没有成本,因为它会在函数返回时自动执行。 不需要垃圾回收。
  • 分配的内存与堆栈上的其他对象是连续的,这使得 数据缓存非常高效。

【讨论】:

    猜你喜欢
    • 2020-12-05
    • 2012-02-09
    • 2015-07-23
    • 1970-01-01
    • 2016-02-01
    • 2018-03-25
    • 2018-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多