更便宜的线程

我读过这个主题:C#线程安全快速(est)计数器并在我的并行代码中实现了此功能。 据我所见,这一切都很好,但它显着增加了处理时间,大约在10%左右。

它一直在困扰着我,我认为问题在于,我正在做很多相对便宜(<1个量程)的小数据片段任务,这些小数据片段分配得很好,可能会很好地利用缓存局部性,从而优化运行。 基于我对MESI知之甚少,我最好的猜测是, Interlocked.Increment中的x86 LOCK前缀将缓存行推入独占模式,并强制其他内核上的缓存未命中,并强制缓存重新加载到每一个并行传递中,仅仅是为了递增这个柜台。 对于缓存缺失和我的工作量,100ns-ish延迟似乎加起来。 (再次,我可能是错的)

现在,我看不到解决方法,但也许我错过了一些明显的东西。 我甚至想过使用n个计数器(对应于并行化程度),然后在特定核心上递增每个计数器,但似乎是不可行的(检测我所在的核心可能会更昂贵,更不用说精心制作if / then / else结构和搞乱执行管道)。 任何想法如何打破这个野兽? :)


来自同一缓存线上多个内核的操作在硬件上竞争。 对于锁定和常规内存访问而言,这是正确的。 这是一个真正的问题。 添加更多内核时,争用访问根本无法扩展。 缩放通常是很难的。

大部分时间您都需要在每个内核中使用多个缓存行。

你可以使用ThreadLocal<Holder>class Holder { public int I; } class Holder { public int I; }为此。 ThreadLocal支持枚举所有已创建的实例,以便对它们进行求和。 您也可以使用填充到高速缓存行大小的结构。 这更安全。

请注意,每个内核使用一个计数器并不重要。 每个线程都足够好,因为与增量操作相比,时间量非常长。 一些不好的访问不是性能问题。

更快的选择是使用Holder[] 。 每个线程一次绘制一个随机数组索引,然后访问该持有者对象。 数组索引比线程本地访问更快。 如果您使用的持有者实例的数量比线程数量大得多(10倍),那么争用就会很少。 大部分的写入操作都将与已经缓存的行相同。

随着更多线程加入处理,您可以使用List<Holder>并添加项目,而不是随机索引。


我想我会提供一些有关缓存一致性的说明,以及LOCK前缀在英特尔架构中的作用。 由于评论的时间太长,同时也回答了你提出的一些观点,所以我认为适当地发表一个答案。

在MESI缓存一致性协议中,无论是否使用LOCK前缀,对缓存行的任何写入都会导致状态更改为独占状态。 因此,如果两个处理器都重复访问相同的缓存行,并且至少有一个处理器正在写入数据,那么处理器在访问它们共享的行时会遇到缓存行未命中。 而如果它们都只从行中读取,则它们将具有缓存行命中,因为它们可以将它们的私有L1缓存中的行保持在共享状态。

LOCK前缀的作用是限制处理器在等待锁定指令完成执行时可以执行的推测性工作量。 Intel 64和IA-32架构软件开发人员手册的第8.1.2节说:

锁定操作对于所有其他内存操作和所有外部可见事件都是原子的。 只有取指和页表访问才能传递锁定的指令。 锁定指令可用于同步一个处理器写入的数据和另一个处理器读取的数据。

在正常情况下,处理器能够推测性地执行指令,同时等待缓存未命中得到解决。 但是LOCK前缀阻止了这一点,并且在锁定的指令完成执行之前基本上停止了流水线。

链接地址: http://www.djcxy.com/p/86779.html

上一篇: Even faster inexpensive thread

下一篇: Overwriting Meteor's default login handler