我有一个实现两个简单的、预先确定大小的堆栈的类;这些存储为由构造函数预先确定大小的向量类型类的成员。它们是小型且缓存行大小友好的对象。这两个堆栈的大小是恒定的,持久化和延迟更新,并且经常通过一些计算成本低的方法一起访问,然而,这些方法可以被调用很多次(每秒数万到数十万次)。
所有对象都已经处于良好状态(代码很干净并且可以执行它应该做的事情),所有大小都受到控制(包括结果在内的整个操作链的大多数情况下为 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,它不能保证是连续的”,我已经知道了 :))。