我正在阅读有关CPU如何在多线程应用程序中保持其缓存一致性的信息。在一个内核的高速缓存中进行写操作会将其标记为脏,并且所有其他内核都必须小心,不要从主内存中读取该段,因为主内存副本不是最新的。
我编写的许多应用程序都像actor系统一样工作,其可变性仅限于单个线程上的局部变量。我通常不将它们标记为“本地线程”,除非出于语义上的考虑。
但是,我是否缺少优化机会?是否将变量明确标记为线程局部变量(而不是仅以这种方式使用)是否通知硬件它不必检查一致性,因为该变量即使在原则上也不会对其他线程可见?
编辑:作为表达同一事物的高级方式,我是否应该期望通过使用像Akka这样的正式演员系统而不是仅仅遵循我班上的演员范式来提高性能?正式的参与者系统增加了严格性,跨计算机扩展的能力以及可能的一些开销,但是它是否还有助于降低底层细节,例如让线程跳过对已知为非共享的缓存数据的一致性检查?
它是通过将数据标记为“本地线程”来实现的吗?
只要您避免错误共享,就可以了。即确保一个线程使用的静态数据与另一个线程使用的静态数据不在同一缓存行中。您可以通过检查内存顺序机器清除perf事件来查找。
如果发现程序有一些错误的共享,您可以重新排列声明的顺序(因为编译器通常倾向于按照声明的顺序存储内容),或者使用链接器部分对其进行处理以选择如何事物被分组在静态存储中。结构或数组也可以为您提供有关内存布局的保证。
TL; DR:如果两个变量将由不同的线程使用,则避免将两个变量放在同一高速缓存行(通常为64B)中。当然,组的东西在一起是在从相同的线程的同时修改。
线程局部变量解决了另一个问题。它们使同一个函数根据调用它的线程来访问不同的静态变量。这是传递指针的替代方法。
它们仍然像其他static
/全局变量一样存储在内存中。您可以确定没有虚假共享,但是有更便宜的方法来避免这种情况。
线程局部变量和“普通”全局变量之间的区别在于它们的寻址方式。不仅仅是通过绝对地址访问它们,它还是与线程本地存储块的偏移量。
在x86上,这是通过segment-override前缀完成的。例如,mov rax, QWORD PTR fs:0x28
从线程本地存储块内部的字节0x28加载(因为每个线程的fs
段寄存器都加载了其自己的TLS块的偏移量)。
因此,TLS不是免费的。如果您不需要它,请不要使用它。但是,它比传递指针便宜。
没有办法让硬件跳过缓存一致性检查,因为硬件没有任何TLS概念。只是存储和加载到内存或从内存加载,以及ISA提供的订购保证。由于TLS只是获得相同功能以便为不同的调用者使用不同地址的技巧,因此实现TLS的软件错误可能导致存储到相同地址。硬件不允许错误的软件以这种方式破坏其缓存一致性,因为它有可能破坏特权分离。
在弱排序的体系结构上,memory_order_consume
(从理论上来说)是一种安排线程间数据依赖关系的方法,以便其他线程仅需等待对共享数据的写操作,而不必对线程私有数据进行写操作。
但是,这对于编译器难以安全可靠地正确执行是太难了,因此它们当前将mo_consume实现为更强大的mo_acquire。不久前,我写了一个非常漫长而漫不经心的答案,其中有一堆指向内存排序内容的链接,并提到了C ++ 11 memory_order_consume。
标准化非常困难,因为不同的体系结构对哪些操作带有依赖项具有不同的规则。我假设某些代码库具有一些利用依赖项排序的手写asm。AFAIK,手写asm是利用依赖排序的唯一方法,可避免在弱排序的ISA上使用内存屏障指令。(例如,在生产者-消费者模型中,或在不仅仅需要无序原子存储的无锁算法中。)
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句