actor - 我可以通过将变量声明为线程本地来避免缓存一致性检查吗?

标签 actor micro-optimization thread-local

我正在阅读有关 CPU 如何在多线程应用程序中保持缓存一致性的内容。一个核心的缓存中的写入会将其标记为脏,所有其他核心必须小心,不要从主内存中读取该段,因为主内存副本不是最新的。

我编写的许多应用程序工作起来就像一个参与者系统,其可变性仅限于单个线程上的局部变量。我通常不会将它们标记为“线程本地”,除非我有这样做的语义原因。

但是,我是否错过了优化机会?与仅以这种方式使用变量相比,显式地将变量标记为线程本地变量是否会通知硬件它不必检查一致性,因为该变量永远不会对其他线程可见,即使原则上也是如此?

编辑:作为表达同一事物的更高级别的方式,我是否应该通过使用正式的 Actor 系统(如 Akka)来获得性能提升,而不是仅仅遵循我的类中的 Actor 范例?正式的参与者系统增加了严格性、跨计算机扩展的能力以及可能的一些开销,但它是否也有助于低级细节,例如让线程跳过对已知非共享的缓存数据的一致性检查?

它是通过将数据标记为“线程本地”来实现的吗?

最佳答案

只要你避免false sharing , 你没事。即确保 static data used by one thread isn't in the same cache line as static data used by another thread 。您可以通过检查 memory order machine-clears perf event 来查找此内容.

如果您发现您的程序存在一些错误共享,您可以重新安排声明的顺序(因为编译器通常倾向于按照声明的顺序存储内容),或者使用链接器部分对此进行处理选择静态存储中事物的分组方式。结构体或数组还可以为您提供内存布局的保证。

TL;DR:如果两个变量将被不同的线程使用,请避免将两个变量放在同一缓存行(通常为 64B)中。当然,将同一线程同时修改的内容组合在一起。


线程局部变量解决不同的问题。它们让同一个函数根据调用它的线程来访问不同的静态变量。这是传递指针的替代方法。

它们仍然像其他静态/全局变量一样存储在内存中。您可以确定不存在虚假共享,但有更便宜的方法可以避免这种情况。

线程局部变量和“普通”全局变量之间的区别在于它们的寻址方式。它不是仅仅通过绝对地址访问它们,而是线程本地存储 block 的偏移量。

在 x86 上,这是通过段覆盖前缀完成的。例如mov rax, QWORD PTR fs:0x28 从线程本地存储 block 内的字节 0x28 加载(因为每个线程的 fs 段寄存器都加载了其自己的 TLS block 的偏移量)。

所以 TLS 不是免费的。如果不需要就不要使用它。不过,它比传递指针更便宜。


没有办法让硬件跳过缓存一致性检查,因为硬件没有任何 TLS 概念。只有内存的存储和加载,并且 the ordering guarantees provided by the ISA 。由于 TLS 只是为不同的调用者获取相同的函数使用不同的地址的技巧,因此实现 TLS 时的软件错误可能会导致存储到相同的地址。硬件不会让有缺陷的软件以这种方式破坏其缓存一致性,因为这可能会破坏特权分离。

在弱有序架构上,memory_order_consume是(理论上)一种安排线程间数据依赖关系的方法,这样只有对共享数据的写入才必须由其他线程等待,而不是对线程私有(private)数据的写入。

然而,这对于编译器来说很难安全可靠地得到正确的结果,因此他们目前将 mo_consume 实现为更强大的 mo_acquire。我写了a really long and rambling answer a while ago包含一堆内存排序内容的链接,并提到了 C++11 memory_order_consume。

标准化非常困难,因为不同的架构有 different rules for what operations carry a dependency 。我假设一些代码库有一些手写的汇编,它们利用了依赖顺序。 AFAIK,手写汇编是利用依赖排序来避免弱排序 ISA 上的内存屏障指令的唯一方法。 (例如,在生产者-消费者模型中,或者在需要的不仅仅是无序原子存储的无锁算法中。)

关于actor - 我可以通过将变量声明为线程本地来避免缓存一致性检查吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37499991/

相关文章:

scala - 如何将参与者消息限制为特定类型?

scala - 将多个 future 与多个远程参与者一起使用时,程序会挂起

assembly - 更短的 x86 调用指令

c# - Azure Service Fabric - 分布式计算代码示例蒙特卡洛模拟 - 性能问题

uml - 如何在 draw.io 中重命名 Actor

cpu-architecture - 为什么指令高速缓存对齐可提高集合关联高速缓存实现中的性能?

dart - Dart 属性结果需要缓存吗?

java - 使用 ThreadLocal 对象存储 Web 请求元数据是一种不好的做法吗?

c++ - 拥有一个与非线程局部变量同名的线程局部变量是否可以?

scala - Akka Slick 和 ThreadLocal