对象的位置和共享访问

时间:2011-04-22 15:10:53

标签: c++ caching

分析我的代码,我看到很多缓存未命中,并想知道是否有办法改善这种情况。实际上并不需要优化,我对是否存在解决此问题的一般方法更为好奇(这是一个后续问题)。

// class to compute stuff
class A {
    double compute();
    ...
    // depends on other objects
    std::vector<A*> dependencies;
}

我有一个容器类,它存储指向类A的所有已创建对象的指针。我不存储副本,因为我想拥有共享访问权限。在我使用shared_ptr之前,但由于单个A在没有容器的情况下毫无意义,原始指针就可以了。

class Container {
    ...
    void compute_all();
    std::vector<A*> objects;
    ...
}

向量objects的插入排序方式是只需迭代并调用A.compute()就可以完成整个评估,A中的所有依赖项都会被解析。

对于a_i类的A个对象,评估可能如下所示:

a_1 => a_2 => a_3 --> a_2 --> a_1 => a_4 => ....

其中=&gt;表示Container中的迭代和 - &gt;迭代A::dependencies

此外,Container类只创建一次并且compute_all()被多次调用,因此在创建之后重新排列整个结构是一个选项并且不会对效率造成太大影响。

现在观察/提问:

  1. 显然,迭代Container::objects可以提高缓存效率,但访问权限绝对不是。

  2. 此外,因为A类型的每个对象都必须遍历A::dependencies,这又会产生缓存未命中。

  3. 在评估顺序中为所有需要的对象创建单独的vector<A*>是否有帮助,以便将A中的依赖项作为副本插入?

    这样的事情:

    a_1 => a_2 => a_3 => a_2_c => a_1_c => a_4 -> ....
    

    其中a_i_c是a_i的副本。

    感谢您的帮助和对不起,如果这个问题令人困惑,但我发现从简单的示例推断到大型应用程序是相当困难的。

2 个答案:

答案 0 :(得分:1)

不幸的是,我不确定我是否正确理解你的问题,但我会尽力回答。

缓存未命中是由处理器需要分散在整个内存中的数据引起的。

增加缓存命中率的一种非常常见的方法是组织数据,以便按顺序访问的所有内容都位于相同的内存区域中。从你的解释来看,我认为这很可能是你的问题;你的A对象遍布整个地方。

如果您只是在每次需要分配new时调用常规A,那么您最终可能会分散所有A个对象。

您可以为将要创建多次并按顺序访问的对象创建自定义分配器。此自定义分配器可以分配大量对象并按要求将其移出。这可能类似于重新排序数据的含义。

然而,实现这一点可能需要一些工作,因为你必须考虑一些情况,例如当它用完对象时会发生什么,它如何知道分发了哪些对象等等。

// This example is very simple. Instead of using new to create an Object,
// the code can just call Allocate() and use the pointer returned.
// This ensures that all Object instances reside in the same region of memory.
struct CustomAllocator {
    CustomAllocator() : nextObject(cache) { }

    Object* Allocate() {
        return nextObject++;
    }

    Object* nextObject;
    Object cache[1024];
}

另一种方法涉及对顺序数据起作用的缓存操作,但不是按顺序执行的。我认为这就是你有一个单独的向量的意思。

但是,重要的是要了解您的CPU不是一次只在缓存中保留一部分内存。它保持缓存多个内存部分。

如果你在一个部分的数据操作和另一个部分的数据操作之间来回跳转,这很可能不会导致很多缓存命中;你的CPU可以而且应该同时保持这两个部分的缓存。

如果您在50个不同数据集的操作之间跳转,则可能会遇到许多缓存未命中。在这种情况下,缓存操作将是有益的。

在您的情况下,我认为缓存操作不会给您带来太多好处。但是,确保所有A对象都位于内存的同一部分中,可能会。

要考虑的另一件事是线程,但这可能变得相当复杂。如果你的线程正在进行大量的上下文切换,你可能会遇到很多缓存未命中。

答案 1 :(得分:0)

+1首先进行性能分析:)

虽然使用cusomt分配器可能是正确的解决方案,但我当然首先推荐两件事:

  • 保留一个引用/指针指向A的整个向量而不是A *的向量:

class Container {
    ...
    void compute_all();
    std::vector<A>* objects;
    ...
}
  • 使用带有自定义分配器的标准库(我认为boost有一些好的,EASTL以这个概念为中心)

$ 0.02