【问题标题】:the instruction cache and conditional statements指令缓存和条件语句
【发布时间】:2012-06-03 22:10:11
【问题描述】:

我试图通过面向数据的设计来定位我的代码以尽可能高效地使用缓存,这是我第一次考虑这样的事情。我想出了一种方法来循环在屏幕上绘制精灵的相同指令,发送到函数的向量包括所有游戏实体的位置和精灵。

我的问题是条件语句是否从指令缓存中删除了绘图函数,从而破坏了我的计划?还是我在做什么通常是疯了?

struct position
{
    position(int x_, int y_):x(x_), y(Y_)
    int x,y;
};

vector<position> thePositions;
vector<sprite> theSprites;
vector<int> theNoOfEntities; //eg 3 things, 4 thingies, 36 dodahs
int noOfEntitesTotal;

//invoking the draw function
draw(&thePositions[0], &theSprites[0], &theNoOfEntities[0], noOfEntitesTotal)

void draw(position* thepos, sprite* thesp, int* theints, int totalsize)
{
    for(int j=0;int i=0;i<totalsize;i++)
    {
        j+=i%size[j]?1:0;
        thesp[j].draw(thepos[i]);
    }
}

【问题讨论】:

  • 为什么不存储pair&lt;sprite, position&gt; 并传递迭代器进行绘制。缓存局部性会更好,它会少很多模糊和存储无关。
  • 数据缓存是否必须在每个函数调用中查找每一对?我的解决方案试图在缓存中维护所有数据和指令,并且没有缓存未命中,只是循环重复需要多少个周期。
  • 条件语句与指令缓存关系不大。
  • 为什么你认为条件语句可能和指令缓存有关系?
  • j+=i%size[j]?1:0; 可能存在一些分支预测问题,但不太可能以任何明显的方式影响指令缓存。

标签: c++ cache-control data-oriented-design


【解决方案1】:

您是否确认条件在汇编中仍然是条件?通常使用上面介绍的简单条件,表达式可以优化为无分支序列(在机器级别使用机器特定指令,或在 IR 级别使用一些花哨的位数学)。

在你的情况下,你有条件在 x86 上很好地折叠成一个平面序列(并且 AFAIK,这也将发生在大多数非 x86 平台上,因为它是一种数学优化,而不是特定于机器的优化):

IDIV DWORD PTR SS:[ARG.1]
MOV EAX,EDX
NEG EAX                                  ; Converts EAX to boolean
SBB EAX,EAX
NEG EAX

所以这意味着除了你的外部循环之外没有任何分支可以预测,它遵循一个模式,这意味着它不会导致任何错误预测(它可能会在退出时错误预测,具体取决于生成的程序集,但它已退出,所以没关系)。

这带来了第二点,永远不要假设,总是分析和测试(汇编知识有很大帮助的情况之一),这样你就可以花时间优化它真正的地方 很重要(您也可以更好地理解代码在目标平台上的内部和内部工作原理)。

如果您真的担心分支错误预测和所招致的惩罚,请使用目标架构制造商提供的资源(不同的架构对分支错误预测的行为非常不同),例如来自thisthis英特尔。 AMD 的 CodeAnalyst 是检查分支错误预测及其可能造成的惩罚的绝佳工具。

【讨论】:

    【解决方案2】:

    哇,伙计!没有冒犯,但看起来你在没有完全理解它的方式和原因的情况下阅读了有关 DOD 的内容。现在,您只需遵循有关 DOD 的文章中设置的指南,就像它们很重要一样。他们不是,在 DOD 中重要的是理解数据、理解计算机架构以及理解你的代码如何利用你对架构的了解尽可能高效地操作数据。国防部文章中列出的指导原则只是作为提醒人们要考虑的常见问题。

    想知道何时以及为何需要使用 DOD?了解您正在使用的架构。你知道一次缓存未命中的代价吗?真的真的真的真的很低。算一算。我是认真的,你自己算一下,我可能会给你一些数字,但那样你就不会学到很多东西。 因此,找出有关架构的信息,处理器如何工作,内存和缓存如何工作,汇编语言如何工作,编译器生成的程序集是什么样的。一旦你了解并理解了所有这些,DOD 只不过是陈述一些几乎显而易见的指导方针来编写真正高效的代码。

    【讨论】:

    • defragdev.com/blog/?p=570 “主要好处是减少缓存未命中”。一次缓存未命中的成本可能非常小,但是如果您在相同的代码上迭代遇到相同的缓存未命中,那么这一切都会加起来。我给出的示例可能不会迭代一个大集合,但它可能会在未来发生。
    • 当然加起来了。但是你知道多少吗?您是否知道在您选择的架构中错过了 L1 或 L2 丢失了多少个周期?你知道你每帧有多少个总cpu周期吗?你游戏中每个组件的预算是多少毫秒?你说“它可能在未来”。那不是国防部的目的,因为你只是承认你不知道你的数据。你应该知道你的数据集有多大。如果/当集合很小时,不要太担心它。如果它很大或将来变得更大,那么是时候为它创建良好的缓存/cpu 高效代码了。
    • 面向数据的设计是关于组织数据以进行有效处理,而不是等到需要完成更多处理。说在你需要使用它之前不要学习它是荒谬的。
    • 我并不是说不要学,因为 DOD 非常有用,是编写高性能游戏和应用程序的好方法。我只是说知道你的数据,知道 DOD 什么时候有用,什么时候没用,更重要的是,知道为什么它在某些情况下有用而在其他情况下没有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-10
    • 2012-01-12
    • 2013-08-02
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多