Optimization 此代码是否填充CPU缓存?

Optimization 此代码是否填充CPU缓存?,optimization,cpu-cache,Optimization,Cpu Cache,我有两种方法来编程相同的功能 方法1: doTheWork(int action) { for(int i = 0 i < 1000000000; ++i) { doAction(action); } } doTheWork(int操作) { 对于(int i=0 i

我有两种方法来编程相同的功能

方法1:

doTheWork(int action)
{
    for(int i = 0 i < 1000000000; ++i)
    {
        doAction(action);
    }
}
doTheWork(int操作)
{
对于(int i=0 i<100000000;++i)
{
行动;
}
}
方法2:

doTheWork(int action)
{
    switch(action)
    {
    case 1:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<1>();
        }
        break;
    case 2:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<2>();
        }
        break;
    //-----------------------------------------------
    //... (there are 1000000 cases here)
    //-----------------------------------------------
    case 1000000:
        for(int i = 0 i < 1000000000; ++i)
        {
            doAction<1000000>();
        }
        break;
    }
}
doTheWork(int操作)
{
开关(动作)
{
案例1:
对于(int i=0 i<100000000;++i)
{
doAction();
}
打破
案例2:
对于(int i=0 i<100000000;++i)
{
doAction();
}
打破
//-----------------------------------------------
//…(这里有100万例)
//-----------------------------------------------
案例1000000:
对于(int i=0 i<100000000;++i)
{
doAction();
}
打破
}
}

让我们假设函数
doAction(int action)
和函数
template doAction()
包含大约10行代码,这些代码将在编译时内联。调用
doAction(#)
在功能上等同于
doAction()
,但是非模板化的
doAction(int值)
要比
template doAction()
慢一些,因为在编译时参数值已知时,可以在代码中进行一些很好的优化


因此,我的问题是,在模板函数的情况下,是否所有数百万行代码都填满了CPU一级缓存(以及更多的缓存)(从而大大降低了性能),还是只有
doAction()的行
在当前正在运行的循环中是否缓存?

一级指令缓存将只包含最近或预期近期执行的指令。因此,第二种方法不能仅仅因为代码就填充一级缓存。您的执行路径将导致它加载表示正在运行的当前循环的模板实例化版本。当您移动到下一个循环时,它通常会使最近使用最少(LRU)的缓存线无效,并将其替换为下一个执行的缓存线


换句话说,由于两种方法的循环性质,一级缓存在这两种情况下都会表现出色,不会成为瓶颈。

这取决于实际的代码大小-10行代码可能很少或很多-当然取决于实际的机器

然而,方法2严重违反了这一几十年的经验法则:指令便宜,内存访问不便宜

可扩展性限制

您的优化通常是线性的-您可能会减少10、20甚至30%的执行时间。达到缓存限制是高度非线性的,就像“撞到砖墙”中的非线性一样

一旦您的代码大小显著超过二级/三级缓存的大小,方法2就会损失大量时间,高端消费系统的以下估计结果表明:

  • 具有
    10667MB/s
    峰值内存带宽的DDR3-1333
  • 具有~
    75000 MIPS的英特尔酷睿i7极限
每个指令提供10667MB/75000M=0.14字节的盈亏平衡-任何更大的指令,主内存都跟不上CPU

典型的x86指令大小是以1..2个周期执行的2..3个字节(现在,这不一定是相同的指令,因为x86指令被拆分了。仍然…) 典型的x64指令长度甚至更大

您的缓存有多大帮助?
我发现以下数字(不同来源,因此很难比较): i7 Nehalem二级缓存(256K,>200GB/s带宽),几乎可以满足x86指令的要求,但可能无法满足x64的要求

此外,只有在以下情况下,二级缓存才会完全启动

  • 您对下一条指令有完美的预测,或者您没有第一次运行惩罚,并且它完全适合缓存
  • 没有大量的数据被处理
  • “内部循环”中没有其他重要代码
  • 此核心上没有执行线程

考虑到这一点,您可能会更早地丢失,尤其是在缓存较小的CPU/板上。

“让我们假设函数doAction(int action)和函数模板doAction()由大约10行代码组成,这些代码将在编译时内联。”编译器通常不够聪明,无法优化模板版本,切换将使您的性能下降,远远超过L1缓存。我理解您的意思是,如此大的切换是影响性能的最大问题。但我最感兴趣的是缓存L1是否会在第二种方法中被填满,这在某种程度上是一个问题,或者根本不是。我只是不知道它是怎么工作的。我还想知道为什么编译器不知道如何内联模板版本的函数,假设它与常规函数非常相似,但比普通函数简单一点?假设
doAction
实例化不再折叠,代码需要从L2/L3/主存中提取。使用典型的内存/执行周期比率,即使您完全预测下一步会出现什么指令,您也无法足够快地将它们放入一级缓存。