C ++池分配器与静态分配,缓存性能

时间:2014-04-23 21:45:56

标签: c++ caching memory-management

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

    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字节。

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

A)

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

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

vs

B)

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

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

2 个答案:

答案 0 :(得分:3)

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

内存是否靠近不会产生巨大的差异,只要分配足够小以容易适应缓存,但大到足以占用多个缓存行。

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

编译器翻译代码的能力可能存在差异,以避免额外的内存访问。这显然取决于实际代码(例如,编译器内联函数是否包含for-loop,是否内联readonlyfunc代码等等)如果是这样,静态分配可以从指针变量转换(它将指针的地址加载到数据的地址中进行恒定的地址计算。它可能不会在如此大的循环中产生巨大的差异。

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

答案 1 :(得分:0)

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

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

相比的优势
  • 分配过程的开销很小,因为微处理器 有堆栈的硬件支持。
  • 由于先进先出性,内存空间永远不会变得支离破碎 堆栈。
  • 取消分配没有成本,因为它在函数返回时自动进行。 无需收集垃圾。
  • 分配的内存与堆栈中的其他对象是连续的,这使得 数据缓存非常有效。