缓存优化理论

Cache Optimization Theory

本文关键字:理论 优化 缓存      更新时间:2023-10-16

我正在考虑重记忆缓存优化,并且喜欢有一些反馈。考虑此示例:

class example
    {
        float phase1;
        float phaseInc;
        float factor;
    public:
        void process(float* buffer,unsigned int iSamples)//<-high prio audio thread
        {
            for(unsigned int i = 0; i < iSamples; i++)// mostly iSamples is 32
            {
                phase1 += phaseInc;
                float f1 = sinf(phase1);//<-sinf is just an example!            
                buffer[i] = f1*factor;
            }
        }
    };

优化想法:

 void example::process(float* buffer,unsigned int iSamples)
    {
        float stackMemory[3];// should fit in L1 
        memcpy(stackMemory,&phase1,sizeof(float)*3);// get all memory at once
        for(unsigned int i = 0; i < iSamples; i++)
        {
            stackMemory[0] += stackMemory[1];
            float f1 = sinf(stackMemory[0]);
            buffer[i] = f1*stackMemory[2];
        }
        memcpy(&phase1,stackMemory,sizeof(float)*1);// write back only changed mameory 
    }

请注意,实际样品循环将包含数千个操作。因此,堆叠式符号可能会变得很大。我认为它不会比32KB更多(那里有较小的L1吗?)

在此堆栈记录中,使用的变量的顺序是否存在?我希望不要,因为我想订购它们,以便可以降低写入大小。还是L1缓存具有与RAM具有相同的缓存行为?

我有一种感觉,我以某种方式做了预摘要的作品,但是我读到的有关预购的只是对如何有效使用它相对模糊。尝试和错误不是5000多行代码的选项。

代码将在Win,Mac和iOS上运行。任何手臂&lt; -> Intel问题会期望吗?

,由于所有内存都可以访问并转移到循环的第一次迭代中,因此这种优化是否无用?

感谢您的任何提示和想法。

起初,我认为由于额外的内存访问和memcpy所需的指示,第二个很有可能会慢一点,而第一个可以直接与这三个合作使用班级成员已经加载到寄存器中。

尽管如此,我还是尝试使用-O2-O3中的GCC 5.2中的代码摆弄,发现,无论我尝试什么,我都得到了两者的相同汇编指令。考虑到memcpy通常必须做的所有额外概念工作,这显然被压制到Zilch。

,这真是太神奇了。

在某些情况下,在某些编译器上,我可以想到您的第二个版本可能更快的速度,即是否涉及访问this->data_member的混叠,干扰了优化,并导致冗余负载和存储到寄存器。p>在这种情况下,它与L1缓存无关,以及与编译器侧的寄存器分配有关的一切。当您加载相同的内存(成员变量)时,缓存在很大程度上是无关紧要的,无论数据的连续块,它都与寄存器有关。尽管如此,我找不到一个单一的情况,我可以在编译器在一个方案中与另一个情况相比,我测试的每种情况都会产生相同的结果。在一个足够复杂的现实世界中,也许有区别。

然后,在这种情况下,它应该在更安全的一面,简单地做:

 void process(float* buffer,unsigned int iSamples)
 {
     const float pi = phaseInc;
     const float p1 = phase1;
     const float fact = factor;
     for(unsigned int i = 0; i < iSamples; i++)
     {
         phase1 += pi;
         float f1 = sinf(p1);
         buffer[i] = f1*fact;
     }
 }

无需使用memcpy跳过箍即可将结果存储到阵列和返回中。即使在我的发现中,优化器设法消除了通常相关的开销。

,这会给优化器带来其他压力。

我意识到您的示例是简化的,但是无论您处理多少数据成员,都不需要将结构降低到这样的原始数组(除非这样的数组实际上是最方便的表示)。从性能的角度来看,编译器将有一个"更轻松"的时间(即使今天的优化器非常出色并可以处理),如果您只使用本地变量而不是 memcpy汇总数据成员进出的数组。<<<<<<<<<<<<<<<<<<<</p>