保持类的向量成员与类实例连续

Posted

技术标签:

【中文标题】保持类的向量成员与类实例连续【英文标题】:Keep vector members of class contiguous with class instance 【发布时间】:2015-02-27 06:54:39 【问题描述】:

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

所有对象都已经处于良好状态(代码很干净并且可以执行它应该做的事情),所有大小都得到控制(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) 是否正确(数据是从在新操作中首次查找的地址中提取的整个缓存大小的数据,而不是在方便的多个适当大小的段中),还是我误解了缓存机制并且缓存可以拉取并保留多个较小的内存段?

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

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

【问题讨论】:

【参考方案1】:

    如果 Curve 实例适合一个缓存行,并且两个向量的数据也分别适合一个缓存行,那么情况还不错,因为那时您有四个常量缓存行。如果每个元素都被间接访问并随机定位在内存中,那么每次访问元素都可能会花费您一次 fetch 操作,在这种情况下可以避免这种情况。如果 Curve 及其元素都适合少于四个高速缓存行,则您可以从将它们放入连续存储中获益。

    是的。

    如果您使用 std::array,您可以保证元素嵌入到所属类中并且没有动态分配(这本身会消耗您的内存空间和带宽)。然后,您甚至可以避免间接访问,如果您使用特殊分配器将向量内容与 Curve 实例放在连续存储中,那么您仍然可以使用。

顺便说一句:短样式备注:

Curve::Curve()

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

...应该这样写:

Curve::Curve():
  m_controls(CONTROL_CAP),
  m_segments(CONTROL_CAP - 3)

  ...

这称为“初始化列表”,搜索该术语以获得进一步的解释。此外,您作为第二个参数提供的默认初始化元素已经是默认值,因此无需明确指定。

【讨论】:

感谢您的回复。 1) 曲线大小为 8,向量包含小对象,它们分别与缓存行的一到倍数对齐,但数量大于一,整个数据集不适合一个缓存行,但最适合 L1时间和 L2 一直 - 2)谢谢 3)我在 GCC 4.4 上,所以没有 C++11。我不知道它有这个保证。它反映在sizeof中吗? Style-Note)谢谢,虽然默认初始化在那里,因为我清理了原始代码以从具有一些参数的实际使用中发布它,为了清晰起见,将编辑原始帖子。

以上是关于保持类的向量成员与类实例连续的主要内容,如果未能解决你的问题,请参考以下文章

C++——静态成员详解

C++——静态成员详解

static在类中的功能

c#核心基础--类的继承

提升js编写能力

Java类的实例化对象成员在内存空间怎么分配,调用构造函数又是在内存中怎么分配