我有一个实现两个简单的预先大小的堆栈的类;那些存储为构造函数预先调整大小的类型vector的成员。它们很小,并且缓存行大小友好的对象。 这两个堆栈的大小不变,持久化并且懒惰地更新,并且通常通过一些计算上便宜的方法一起访问,然而,这些方法可以被称为大量次数(每秒数十到数十万次)。
所有对象都已经处于良好状态(代码干净并完成它应该做的事情),所有大小都受到控制(大多数情况下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)。 一个相关的假设是,如果向量远离曲线的实例,并且在它们之间,当方法可选地访问这两个成员的内容时,将会更频繁地逐出并重新填充L1缓存。
1)这是正确的(从首次查找新操作的地址中拉出整个缓存大小的数据,而不是在方便的多个适当大小的段中),或者我是否误解了缓存机制缓存可以拉动并保留多个较小的ram?
2)在上述情况之后,在纯粹的情况下,我的所有测试总是最终得到类的实例和连续的向量,但我认为这只是运气不好,但在统计上是可能的。通常实例化类只保留该对象的空间,然后向量被分配在下一个可用的连续块中,如果之前在内存中找到了一个小的空间,则不能保证在我的Curve实例附近的任何位置。 这是对的吗?
3)假设1和2是正确的,或者在功能上足够接近,我理解保证性能我必须编写一个分配器来确保类对象本身在实例化时足够大,然后复制我自己和那里的矢量指的是那些。 如果这是解决问题的唯一方法,我可能会破解我的方式,但如果有好的/聪明的方法来解决这个问题,我宁愿也不要破解它。任何关于最佳实践和建议方法的指针都会非常有用(除了“不要使用malloc它不能保证连续”,我已经有了:))。
答案 0 :(得分:0)
如果曲线实例适合缓存行,并且两个向量的数据也各自适合一个cachline,那么情况就不那么糟了,因为那时你有四个常量缓存行。如果每个元素都是间接访问并随机定位在内存中,那么对元素的每次访问都可能会花费您的获取操作,在这种情况下可以避免这种操作。如果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)
{
...
}
这称为&#34;初始化列表&#34;,搜索该术语以获得进一步说明。另外,作为第二个参数提供的默认初始化元素已经是默认值,因此无需明确指定。