0

我有一个实现两个简单的、预先确定大小的堆栈的类;这些存储为由构造函数预先确定大小的向量类型类的成员。它们是小型且缓存行大小友好的对象。这两个堆栈的大小是恒定的,持久化和延迟更新,并且经常通过一些计算成本低的方法一起访问,然而,这些方法可以被调用很多次(每秒数万到数十万次)。

所有对象都已经处于良好状态(代码很干净并且可以执行它应该做的事情),所有大小都受到控制(包括结果在内的整个操作链的大多数情况下为 64k 到 128K,很少接近 256k,所以更糟糕的是L2 查找,通常是 L1)。

一些自动矢量化开始发挥作用,但除此之外,它始终是单线程代码。

这个类,减去一些小东西和填充,看起来像这样:

class Curve{
private:
    std::vector<ControlPoint> m_controls;
    std::vector<Segment> m_segments;

    unsigned int m_cvCount;
    unsigned int m_sgCount;
    std::vector<unsigned int> m_sgSampleCount;

    unsigned int m_maxIter;
    unsigned int m_iterSamples;
    float m_lengthTolerance;

    float m_length;
}

Curve::Curve(){
    m_controls = std::vector<ControlPoint>(CONTROL_CAP);
    m_segments = std::vector<Segment>( (CONTROL_CAP-3) );

    m_cvCount = 0;
    m_sgCount = 0;
    std::vector<unsigned int> m_sgSampleCount(CONTROL_CAP-3);

    m_maxIter = 3;
    m_iterSamples = 20;
    m_lengthTolerance = 0.001;

    m_length = 0.0;
}

Curve::~Curve(){}

请忍受冗长,我正在努力教育自己,并确保我没有通过一些半开玩笑的知识来操作:

考虑到在它们上运行的操作及其实际用途,性能主要受内存 I/O 限制。我有一些与数据的最佳定位有关的问题,请记住,这是在 Intel CPU(Ivy 和一些 Haswell)上以及使用 GCC 4.4 时,我没有其他用例:

我假设如果控件和段的实际存储与 Curve 的实例相邻,这是缓存的理想方案(从大小上看,很多可以很容易地适合我的目标 CPU)。一个相关的假设是,如果向量远离 Curve 的实例,并且在它们之间,当方法交替访问这两个成员的内容时,将会有更频繁的驱逐和重新填充 L1 缓存。

1)这是正确的(数据是从新操作中首先查找的地址中提取的整个缓存大小的数据,而不是方便的适当大小的多个段),还是我误解了缓存机制和缓存可以拉动并保存多个较小的 ram 吗?

2)根据上述情况,就纯粹的情况而言,我所有的测试总是以类的实例和连续的向量结束,但我认为这只是运气不好,无论在统计上多么有可能。通常实例化该类仅保留该对象的空间,然后将向量分配在下一个可用的空闲连续块中,如果之前在内存中找到一个小的空位,则不能保证它位于我的 Curve 实例附近的任何地方。这个对吗?

3) 假设 1 和 2 是正确的,或者从功能上讲足够接近,我理解为了保证性能,我必须编写一个分配器以确保类对象本身在实例化时足够大,然后复制向量在那里我自己和从那里参考那些。如果这是解决问题的唯一方法,我可能会破解类似的方法,但如果有很好/聪明的方法来解决类似的问题,我宁愿不要可怕地破解它。任何关于最佳实践和建议方法的指示都会非常有帮助(除了“不要使用 malloc,它不能保证是连续的”,我已经知道了 :))。

4

1 回答 1

0
  1. If the Curve instances fit into a cache line and the data of the two vectors also fit a cachline each, the situation is not that bad, because you have four constant cachelines then. If every element was accessed indirectly and randomly positioned in memory, every access to an element might cost you a fetch operation, which is avoided in that case. In the case that both Curve and its elements fit into less than four cachelines, you would reap benefits from putting them into contiguous storage.

  2. True.

  3. If you used std::array, you would have the guarantee that the elements are embedded in the owning class and not have the dynamic allocation (which in and of itself costs you memory space and bandwidth). You would then even avoid the indirect access that you would still have if you used a special allocator that puts the vector content in contiguous storage with the Curve instance.

BTW: Short style remark:

Curve::Curve()
{
  m_controls = std::vector<ControlPoint>(CONTROL_CAP, ControlPoint());
  m_segments = std::vector<Segment>(CONTROL_CAP - 3, Segment());
  ...
}

...should be written like this:

Curve::Curve():
  m_controls(CONTROL_CAP),
  m_segments(CONTROL_CAP - 3)
{
  ...
}

This is called "initializer list", search for that term for further explanations. Also, a default-initialized element, which you provide as second parameter, is already the default, so no need to specify that explicitly.

于 2015-02-27T07:23:03.187 回答