我有一个结构链表。假设我将x百万个节点插入到链表中, 然后我遍历所有节点以找到给定的值。
奇怪的是(对我来说至少),如果我有这样的结构:
struct node
{
int a;
node *nxt;
};
然后,我可以通过列表进行迭代,并检查比我在结构中有另一个成员的速度快十倍的值,如下所示:
struct node_complex
{
int a;
string b;
node_complex *nxt;
};
我也用C样式字符串(char数组)尝试过,结果是一样的: 因为我有另一个成员(字符串),整个迭代(+值检查)慢了10倍 ,即使我甚至没有碰过那个会员!现在,我不知道结构的内部是如何工作的,但它看起来要付出很高的代价......
有什么收获?
修改: 我是初学者,这是我第一次使用指针,所以很有可能,错误就在于我。我会尽快发布代码(现在不在家)。
更新: 我再次检查了这些值,我知道看到的差异要小得多:2倍而不是10倍。 这肯定是更合理的。
虽然昨天肯定也是如此,昨晚我感到非常疲惫,我不能分两个数字,我刚做了更多测试,结果令人心碎。
相同数量的节点的时间是:
看看结构中有两个以上的字符串会发生什么!它变快了!有人把LSD放进我的咖啡里吗?没有!我不喝咖啡。
对于我的大脑来说,这太过分了,所以我想我会自己解决这个问题,而不是在SO处耗尽公共资源。
(广告:我认为我的剖析课没有错误,无论如何我能用自己的眼睛看到时差)。
无论如何,谢谢你的帮助。 欢呼声。
答案 0 :(得分:7)
我必须与内存访问有关。你说的是一百万个相关元素。在节点中只有一个int和一个指针,它需要8个字节(假设32位指针)。这占用8 MB内存,大约是缓存大小的大小。
添加其他成员时,会增加数据的整体大小。它不再完全适合缓存内存。您恢复到速度慢得多的普通内存访问。
答案 1 :(得分:5)
这也可能是因为在迭代期间您可能创建结构的副本。那就是:
node* pHead;
// ...
for (node* p = pHead; p; p = p->nxt)
{
node myNode = *p; // here you create a copy!
// ...
}
快速复制简单结构。但是您添加的成员是string
,这是一个复杂的对象。复制它是一个相对复杂的操作,具有堆访问权限。
答案 2 :(得分:3)
最有可能的问题是,较大的结构不再适合单个缓存行。
我记得,主流CPU通常使用32字节的缓存行。这意味着数据一次以32字节的块的形式读入高速缓存,如果超过这32个字节,则需要再次进行内存提取。
查看你的结构,它以int
开头,占4个字节(通常),然后std::string
(我假设,即使没有指定命名空间),这在我的标准库实现(来自VS2010)占用28个字节,总共为32个字节。这意味着初始int
和next
指针将放置在不同的缓存行中,使用两倍的缓存空间,如果在迭代期间访问这两个成员,则需要两倍的内存访问。 / p>
如果仅指针被访问,这应该没有区别,因为只需要从内存中检索第二个缓存行。
如果您始终访问int
和指针,并且不太经常需要字符串,则重新排序成员可能有所帮助:
struct node_complex
{
int a;
node_complex *nxt;
string b;
};
在这种情况下,next
指针和int
位于相同的缓存行上,彼此相邻,因此可以在不需要额外内存读取的情况下读取它们。但是,一旦您需要阅读string
,就会产生额外费用。
当然,您的基准测试代码也可能包括创建节点,或者创建节点的(有意或无意)副本,这显然也会影响性能。
答案 3 :(得分:1)
我根本不是一个空间主义者,但在阅读你的问题时,“缓存未命中”问题在我脑海中浮现。
如果你有一个成员,因为它会使结构的大小变大,它也可能会在链接列表中缓存未命中(如果你没有在一个集团中分配节点,这自然是缓存不友好的在记忆中彼此相距不远。)
我找不到另一种解释。
但是,我们没有提供创建和循环,所以如果您不仅仅是拥有不能以有效方式执行列表探索的代码,那么仍然很难猜测。
答案 4 :(得分:0)
也许解决方案是指向对象的链接列表。它可能会使事情变得更复杂(除非您使用智能指针等,但可能会增加搜索时间。