是否使用指针或引用来访问向量,然后遍历它缓存不友好?
Posted
技术标签:
【中文标题】是否使用指针或引用来访问向量,然后遍历它缓存不友好?【英文标题】:Is using a pointer or reference to access a vector and then iterating through it cache unfriendly? 【发布时间】:2015-02-25 04:40:07 【问题描述】:我有一个指向存储在其他对象中的向量的指针。
vector<Thing>* m_pThings;
然后当我想遍历这个向量时,我使用下面的for
循环:
for (auto& aThing : *m_pThings)
aThing.DoSomething();
假设Thing::DoSomething()
存在。
我的问题是:这段代码在取消引用m_pThings
时是否会导致过多的缓存未命中?我想知道这一点,因为我特意制作了一个向量,以便所有Thing
s 都连续保存在内存中并避免缓存未命中,并且想知道取消引用这样的指针是否会导致缓存未命中无论如何都会破坏我的努力。
【问题讨论】:
@billz ,我的意思是vector<Thing>
。显然,向量中的对象是连续的。我唯一使用指针的地方是在访问vector
时。我只想知道这是否会导致缓存未命中。
你可能打算声明m_pThings
一个指向向量的指针:vector<Thing>* m_pThings
哦,是的......道歉:P
【参考方案1】:
我只想说,将值传递到 for-each 语句和传递取消引用的指针之间的区别最多只能是几个时钟周期的一次性惩罚。 for-each 语句通过调用您传入的对象的 begin()
函数来工作,该函数返回一个迭代器。然后它只使用那个迭代器完成其余的工作,这在两种情况下都是相同的。不必重复引用指针的开销。
在现代处理器上,您可以执行的几乎所有操作都不会对缓存不友好,除非您使用一些奇怪的 n 次多项式模式来寻址内存。即使您的程序在几个不同的地址之间快速跳转,缓存通常也很容易处理这种行为,特别是如果某些地址是静态的,例如您的 m_pThings 向量。
最后,除非您正在设计一种将在整个庞大程序中使用的模式,否则此类选择实际上并不需要预先进行深入检查。过早的优化是邪恶的,应该不惜一切代价避免!如果它被证明是一个问题,你的分析器会提醒你这个特定的设计正在吞噬周期,应该重新检查。
如果您真的想知道,基准测试是您最好的选择。当涉及到此类问题时,再多的代码分析都无法击败简单的基准测试,这些问题本质上非常依赖于特定的硬件实现。不过,并非所有缓存的行为方式都相同,因此虽然一个 CPU 可能会毫不费力地处理算法,但另一个 CPU 可能会自行烧毁。
所以我的回答基本上可以归结为:做你认为最清楚和/或最方便的事情,因为这实际上可能并不重要。如果它被证明是一个问题,你可能最好重新考虑算法本身,而不是提高缓存性能。缓存应该被认为是一种奖励,而不是必需品。如果您的代码仅在某些特定的缓存行为下运行良好,那么您做错了*。
*我会提出的一个例外情况是,如果您正在为单个特定目标(例如游戏机或其他专有嵌入式系统)进行编程。
【讨论】:
【参考方案2】:对于for (auto& aThing : *m_pThings)
,使用vector<Thing>*
v/s vector<Thing>
几乎不会改变性能,因为在这两种方式中,您都指的是仅包含指针的向量(假设它们是 start、end 和 end_of_allocation)到实际数据和主要性能影响将由矢量数据的布局引起,而不是由矢量原始指针本身引起。
但是为了可读性,你应该更喜欢使用vector<Thing>
【讨论】:
【参考方案3】:它会在你第一次检查它时导致缓存未命中,如果在下次重用同一向量之间有太多时间 - 它可能会从缓存中清除。
连续存储的好处主要来自两件事 - 如果元素小于缓存行,第一次提取也会同时带来接下来的几个项目,因此您可以节省内存带宽(转化为延迟如果您的内存因许多请求而压力过大并且缓冲区不足)。
第二个好处是硬件预取器可以在连续的内存范围内提前运行并减少任何后续需求的访问延迟。
您没有指定 Thing
的大小、该数组的重用距离,或者它是否小到甚至可以放入缓存中(如果是,那么 - 什么级别) - 这些参数可能决定了以上福利适用。
无论哪种方式,您通过指针来尊重数组这一事实确实不会损害它的连续放置 - 硬件最终会看到一个接一个地访问的地址,并且能够在缓存或预取它时使用该事实。但是,请注意,它仍然不能保护您免受缓存抖动的影响。
【讨论】:
以上是关于是否使用指针或引用来访问向量,然后遍历它缓存不友好?的主要内容,如果未能解决你的问题,请参考以下文章