CAS 冲突的 CPU 内部特征是什么?

2024-01-01

我正在尝试了解 x86/x64 上 CAS 的低级机制,我非常感谢一些帮助/见解。

我一直在思考这个问题的原因是我试图推理指数退避,并原则上找出正确的退避延迟单位应该是什么。

如果我查看无锁空闲列表基准测试,没有指数退避,我会发现随着线程数量的增加,性能迅速趋于平稳。

Release 7 Lock-Free Freelist Benchmark #1

   M
   N
   S
  L3U
L2U L2U
L1D L1D
L1I L1I
 P   P
L L L L total ops,mean ops/sec per thread,standard deviation,scalability
0 0 0 1 310134488,31013449,0,1.00
0 1 0 1 136313300,6815665,38365,0.22

0 1 0 1 136401284,6820064,50706,0.22
1 1 1 1 111134328,2778358,23851,0.09

0 0 1 1 334747444,16737372,2421,0.54
1 1 1 1 111105898,2777647,40399,0.09

正如我们所知,可能会发生活锁,其中每个线程都会阻止其他线程前进。

我最初的想法(现在我认为是错误的)是 CAS 干扰了 CAS。我的意思是,如果 CAS 指令同时发生,那么它们本身就会与另一个 CAS 发生破坏性冲突。两者都会失败。 (可能是因为我在心里想着以太网)。

这“显然”解释了结果 - 所有这些 CAS 指令同时运行,很少有机会在被破坏性中断之前完全执行。

再想一想,我现在认为不可能是这样。 CAS 指令实际上没有故障模式。它会告诉您目的地等于或不等于比较数。就这样。它不会回来说“哦,对不起,撞到了别人”。

破坏性干扰正在发生,但它发生在更高的层次上,发生在数据结构算法本身中。当我们从空闲列表中推送或弹出时,我们实际上是在尝试交换。我们需要目的地稳定足够长的时间,以便我们可以读取它,做我们需要做的任何工作,然后发现它不变,这样我们就可以完成我们的推送/弹出。

如果其他线程保持 CASing,则目标不稳定 - 它不断变化 - 并且我们必须不断重试我们的操作。

但现在我很困惑。

我们看到,单个线程执行大约 3000 万次入栈/出栈操作。目的地必须在其中一项操作期间保持稳定,操作才能成功,因此我们看到有 3000 万个“槽位”。如果我们有两个线程,那么我们可以拥有的最大理论性能是每个线程 1500 万次操作;每个线程使用一半的插槽。

现在让我们回到 CAS。 CAS没有故障模式。那么,当另一个线程已经在进行 CAS 操作而第二个线程尝试进行 CAS 操作时,会发生什么情况呢?好吧,第二个线程将在数据结构级别失败,因为交换无法发生,因此它将重试交换。

但现在想象我们有很多线程。开始 CAS 的第一个线程将成功(假设每个 CAS 花费完全相同的时间 - 不正确,但该假设不会改变任何基本内容,因此可以推理)。所有其他人都会失败。

但是,一旦第一个线程完成,下一个读取新目标值的线程将使其 CAS 成功(而所有其他线程,仍在执行其 CAS 或现在开始新的 CAS,将失败)。

那么为什么我们没有看到完美的缩放呢?因为每个“槽”都应该被使用!

因此我认为我没有正确理解 CAS。

阅读 Intel 的架构软件开发人员手册,我发现如果所有数据都存在于缓存中(我感兴趣的情况),则缓存一致性协议会负责 CAS。

Drepper 在他的白皮书中描述了 LL/SC 以及它如何使用 MESI 工作。

在我看来,CAS 以类似的方式运作是合理的。

让我们考虑两个线程的情况。第一个线程开始其 CAS。具有目标的缓存行位于其缓存中并标记为独占。

第二个线程开始 CAS。第一个核心将其缓存行发送到第二个核心,并且两个核心都将该缓存行标记为共享。

第一个线程完成 CAS 并写入缓存行(写入始终发生在 x86/x64 上,即使比较为 false;它只写入原始值)。

写入行为将缓存行标记为已修改;发生 RFO,导致第二个核心将其缓存行标记为无效。

第二个线程完成其 CAS 并注意到其缓存行无效......然后,怎么办?我发现很难相信该指令在 CPU 内部循环直到它成功 - 尽管我想知道,因为 ARM 上的 LL/SC 需要you在你的程序集中执行此循环。但CAS指令知道destination的值已经改变,所以它的比较结果无效。但 CAS 不可能出错;它总是返回 true 或 false 进行比较。但即使指令确实循环直到完成,我仍然期望完美的缩放。每个“插槽”仍应使用。

那么会发生什么呢?什么is发生在 CAS 上吗?

我所看到的是,随着线程数的增加,完成的工作越来越少 - 所有可用的“插槽”肯定都没有被使用。有什么原因导致了这种情况。 CAS指令之间是否存在破坏性干扰?或者是大量RFO占用了CPU->北桥总线?

我非常感兴趣地注意到,同一物理核心上的两个线程完美地扩展。在这种情况下,会发生一些特殊且不同的情况 - 单独物理核心上的两个线程也会缩放一半。但这还不足以解释这一切。


您在这里看到的是在两个物理核心的 L1 缓存之间移动数据的成本。当仅使用一个核心时,数据位于 L1 缓存中,并且每个 CAS 以缓存中的数据全速运行。另一方面,当有两个核心处于活动状态时,每次一个核心成功写入数据时,都会使另一个缓存失效,这将导致在另一个核心可以执行任何操作之前需要在缓存之间复制数据(一般会在CAS完成之前阻塞等待加载)。这比实际的 CAS 昂贵得多(它至少需要将数据移动到 L3 缓存,然后返回到另一个 L1 缓存),并且会导致您看到的速度减慢,因为数据最终会出现乒乓球效应在两个 L1 缓存之间来回

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CAS 冲突的 CPU 内部特征是什么? 的相关文章

  • 在 .NET 4.0 中将任务与 Parallel.Foreach 一起使用

    我开始尝试向 Windows 窗体添加一个进度条 以更新 Parallel Foreach 循环中运行的代码的进度 为此 UI 线程必须可用于更新进度条 我使用 Task 来运行 Parallel Foreach 循环 以允许 UI 线程更
  • 如何在 bash 脚本中使用并行编程/多线程?

    这是我的脚本 bin bash script to loop through directories to merge fastq files sourcedir path to source destdir path to dest fo
  • Android Thread、AsyncTask 与从 BLE onCharacteristicChanged() 调用的 IntentService

    我有一个 Android 应用程序 我从中接收 BLE 数据 每 62 毫秒通过通知 该应用程序可以通过 BufferedWriter 将数据保存到文件中 在每次 onCharacteristicChanged 回调时 如果用户启用了文件保
  • 段寄存器如何参与内存地址转换?

    到目前为止我所学到的有关细分的知识 虚拟地址包含段选择器和偏移量 段选择器与GDTR配合使用 查找段描述符的线性地址 段描述符保存有关所选段的信息 包括其线性地址 所以 我的问题是 根据我所读到的内容 虚拟地址被加载到段寄存器中 然后以某种
  • 在无锁单链表的开头插入节点时,正确的内存顺序是什么?

    我有一个简单的链表 不存在 ABA 问题的危险 我对阻塞类别很满意 并且我不在乎我的列表是先进先出 后进先出还是随机的 只要插入成功 不让其他插入失败 其代码如下所示 class Class std atomic
  • 如何在 MOS 6502 的 asm 中创建延迟

    我是 ASM 新手 我正在尝试研究如何为以下代码创建延迟 org 1000 loop inc d021 jmp loop 我想评论已经足够清楚了 每帧更改颜色的代码示例 1 50 秒 sei enable interrupts loop1
  • 该程序如何知道该字符串存储的确切位置?

    我用 Radare2 反汇编了一个 C 程序 在这个程序中有很多调用scanf像下面这样 0x000011fe 488d4594 lea rax var 6ch 0x00001202 4889c6 mov rsi rax 0x0000120
  • Android SurfaceView 使用线程绘制画布

    我正在尝试使用线程在画布上绘图来创建一个简单的游戏引擎 但我遇到了一些无法解释的奇怪问题 这个 游戏 的目的是每秒在画布上画一个圆圈 这是可行的 但不是我想要的工作方式 似乎应用程序正在两个画布之间切换 并向每个画布添加一个圆圈 这样您就可
  • Java:BufferedReader 在 close() 上永远挂起,并且 StreamDecoder 不尊重线程中断

    我有一个 Java 程序 它启动一个由 Process 类表示的单独子进程 然后附加查看 Process 的 stdout stderr 的侦听器 在某些情况下 进程将挂起并停止取得进展 此时 TimeLimiter 将抛出 Timeout
  • Shared_ptr 线程安全的开销是多少?

    std shared ptr保证是线程安全的 我不知道典型的实现使用什么机制来确保这一点 但肯定它必须有一些开销 即使您的应用程序是单线程的 这种开销也会存在 是上述情况吗 如果是这样 如果您不使用线程安全保证 这是否意味着它违反了 您不为
  • 在 x86 汇编语言中获取文件大小的简单方法

    假设我已经在汇编中打开了一个文件 并且在寄存器 eax 中有该文件的文件句柄 我将如何获取文件的大小 以便为其分配足够的缓冲区空间 我在这里研究了另一个讨论 建议使用sys fstat 28 系统调用来获取文件统计信息但无法实现它 My a
  • 非阻塞方法中的饥饿

    一段时间以来 我一直在阅读有关非阻塞方法的内容 这是一段所谓的无锁计数器的代码 public class CasCounter private SimulatedCAS value public int getValue return va
  • VBA 中的多线程

    这里有人知道如何让VBA运行多线程吗 我正在使用 Excel 无法用 VBA 本地完成 VBA 构建在单线程单元中 获得多个线程的唯一方法是使用 VBA 之外的其他具有 COM 接口的东西构建 DLL 并从 VBA 调用它 信息 OLE 线
  • ThreadPoolExecutor 和队列

    我以为使用线程池执行器 http docs oracle com javase 6 docs api java util concurrent ThreadPoolExecutor html我们可以提交Runnables 要在以下位置执行B
  • 使用 Matplotlib、PyQt 和 Threading 进行实时绘图导致 python 崩溃

    我一直在努力研究我的 Python 应用程序 但找不到任何答案 我有 PyQT GUI 应用程序 它使用 Matplotlib 小部件 GUI 启动一个新线程来处理 mpl 小部件的绘图 恐怕我现在通过从另一个线程访问 matplotlib
  • iPhone 相当于 Application.DoEvents();

    iPHone 我们使用 MonoTouch 但 Obj C 答案还可以 我的单例域对象需要一段时间才能获取所有数据 因此它在线程中内部运行部分获取数据 我需要通知 UI 域已完成 目前我正在这样做 有没有更好的办法 在 WinForms 中
  • 当可能存在迭代器时替换并发集合是否是线程安全的?

    我一直在阅读各种内容 似乎这应该有效 但我想确定一下 我有一个静态属性 它应该是一个缓存 加上一些与缓存数据相关的其他功能 它将实际数据存储在 ConcurrentBag 中 并且有一个 IEnumerable 方法来 过滤并 从此包中生成
  • 汇编程序中的过程调用如何工作?

    我刚刚开始摆弄 ASM 我不确定我对过程调用的理解是否正确 假设代码中的某个时刻有一个过程调用 call dword ptr 123 该过程仅包含一个命令 ret ret 0004 该过程调用的效果是什么 返回值将存储在哪里 我在某处读到
  • C# - 当代表执行异步任务时,我仍然需要 System.Threading 吗?

    由于我可以使用委托执行异步操作 我怀疑在我的应用程序中使用 System Threading 的机会很小 是否存在我无法避免 System Threading 的基本情况 只是我正处于学习阶段 例子 class Program public
  • 用于运行可执行文件的python多线程进程

    我正在尝试将一个在 Windows 上运行可执行文件并管理文本输出文件的 python 脚本升级到使用多线程进程的版本 以便我可以利用多个核心 我有四个独立版本的可执行文件 每个线程都知道要访问它们 这部分工作正常 我遇到问题的地方是当它们

随机推荐