通过内联汇编锁定内存操作

2024-02-07

我对低级的东西很陌生,所以我完全不知道你可能会遇到什么样的问题,我什至不确定我是否正确理解“原子”一词。现在我正在尝试通过扩展程序集围绕内存操作制作简单的原子锁。为什么?为了好奇心。我知道我正在重新发明轮子,并且可能过度简化了整个过程。

问题是? 我在这里提供的代码是否实现了使内存操作既线程安全又可重入的目标?

  • 如果有效,为什么?
  • 如果不起作用,为什么?
  • 还不够好?例如,我应该使用registerC 中的关键字?

我只想做的事...

  • 在操作内存之前,先锁定。
  • 内存操作完成后,解锁。

代码:

volatile int atomic_gate_memory = 0;

static inline void atomic_open(volatile int *gate)
{
    asm volatile (
        "wait:\n"
        "cmp %[lock], %[gate]\n"
        "je wait\n"
        "mov %[lock], %[gate]\n"
        : [gate] "=m" (*gate)
        : [lock] "r" (1)
    );
}

static inline void atomic_close(volatile int *gate)
{
    asm volatile (
        "mov %[lock], %[gate]\n"
        : [gate] "=m" (*gate)
        : [lock] "r" (0)
    );
}

然后是这样的:

void *_malloc(size_t size)
{
        atomic_open(&atomic_gate_memory);
        void *mem = malloc(size);
        atomic_close(&atomic_gate_memory);
        return mem;
}
#define malloc(size) _malloc(size)

.. 对于 calloc、realloc、free 和 fork(对于 linux)也是如此。

#ifdef _UNISTD_H
int _fork()
{
        pid_t pid;
        atomic_open(&atomic_gate_memory);
        pid = fork();
        atomic_close(&atomic_gate_memory);
        return pid;
}
#define fork() _fork()
#endif

加载atomic_open的堆栈帧后,objdump生成:

00000000004009a7 <wait>:
4009a7: 39 10                   cmp    %edx,(%rax)
4009a9: 74 fc                   je     4009a7 <wait>
4009ab: 89 10                   mov    %edx,(%rax)

另外,考虑到上面的反汇编;我可以假设我正在进行原子操作,因为它只是一条指令吗?


我认为在 x86 上不存在任何真正主要/明显的性能问题的简单自旋锁就是这样的。当然,真正的互斥锁实现将使用系统调用(如 Linuxfutex http://man7.org/linux/man-pages/man2/futex.2.html)旋转一段时间后,解锁必须检查是否需要通过另一个系统调用通知任何服务员。这个很重要;你不想永远浪费 CPU 时间(和能量/热量)无所事事。但从概念上讲,这是在采取后备路径之前互斥锁的旋转部分。这是如何做到这一点的一个重要部分轻量级锁定 http://preshing.com/20111124/always-use-a-lightweight-mutex/已实施。 (在调用内核之前只尝试获取一次锁是一个有效的选择,而不是旋转。)

在内联汇编中尽可能多地实现此功能,或者最好使用 C11stdatomic, 像这样信号量实现 https://stackoverflow.com/a/36097001/224132。这是 NASM 语法。如果使用 GNU C 内联汇编,请确保使用"memory"破坏停止编译时内存访问重新排序 https://stackoverflow.com/questions/66855137/ttas-coherence-issue。但不要使用内联汇编;使用C_Atomic uint8_t or C++ std::atomic<uint8_t> with .exchange(1, std::memory_order_acquire) and .store(0, std::memory_order_release), and _mm_pause() from immintrin.h.

;;; UNTESTED ;;;;;;;;
;;; TODO: **IMPORTANT** fall back to OS-supported sleep/wakeup after spinning some
;;; e.g. Linux futex
    ; first arg in rdi as per AMD64 SysV ABI (Linux / Mac / etc)

;;;;;void spin_lock  (volatile char *lock)
global spin_unlock
spin_unlock:
       ; movzx  eax, byte [rdi]  ; debug check for double-unlocking.  Expect 1
    mov   byte [rdi], 0        ; lock.store(0, std::memory_order_release)
    ret

align 16
;;;;;void spin_unlock(volatile char *lock)
global spin_lock
spin_lock:
    mov   eax, 1                 ; only need to do this the first time, otherwise we know al is non-zero
.retry:
    xchg  al, [rdi]

    test  al,al                  ; check if we actually got the lock
    jnz   .spinloop
    ret                          ; no taken branches on the fast-path

align 8
.spinloop:                    ; do {
    pause
    cmp   byte [rdi], al      ; C++11
    jne   .retry              ; if (lock.load(std::memory_order_acquire) != 1)
    jmp   .spinloop

; if not translating this to inline asm, you could put the spin loop *before* the function entry point, saving the last jmp
; but since this is probably too simplistic for real use, I'm going to leave it as-is.

普通存储具有发布语义,但不具有顺序一致性(您可以从 xchg 或其他东西获得)。获取/释放 https://preshing.com/20120913/acquire-and-release-semantics足以保护关键部分(因此得名)。


如果您使用原子标志位字段,您可以使用lock bts(测试和设置)相当于 xchg-with-1。你可以旋转bt or test。要解锁,您需要lock btr, 不只是btr,因为这将是字节的非原子读取-修改-写入,甚至是包含 32 位的字节。

使用通常使用的字节或整数大小的锁,您甚至不需要locked操作解锁;释放语义就足够了 https://stackoverflow.com/questions/36731166/spinlock-with-xchg/37246395#37246395。 glibc的pthread_spin_unlock http://repo.or.cz/glibc.git/blob/3f0eedddbe260aad3a7b88051d6aa2b205218aa9:/sysdeps/x86_64/nptl/pthread_spin_unlock.S它和我的解锁功能一样吗:一个简单的商店。

(lock bts没有必要;xchg or lock cmpxchg对于普通锁来说同样好。)


第一次访问应该是原子 RMW

参见讨论cmpxchg 是否会在失败时写入目标缓存行?如果不是,对于自旋锁来说它比 xchg 更好吗? https://stackoverflow.com/questions/63008857/does-cmpxchg-write-destination-cache-line-on-failure-if-not-is-it-better-than- 如果第一次访问是只读的,CPU 可能只发出对该高速缓存行的共享请求。然后,如果它看到该行已解锁(希望是常见的低争用情况),则必须发送 RFO(读取所有权)才能真正能够写入缓存行。因此,这是非核心事务的两倍。

缺点是这需要MESI https://en.wikipedia.org/wiki/MESI_protocol该缓存行的独占所有权,但真正重要的是拥有锁的线程可以有效地存储0这样我们就可以看到它已解锁。无论哪种方式,只读或 RMW,该核心都将失去该行的独占所有权,并且必须先进行 RFO,然后才能提交该解锁存储。

我认为,当多个线程排队等待已获取的锁时,只读首次访问只会优化内核之间稍微减少的流量。对此进行优化是一件愚蠢的事情。

(最快的内联组装自旋锁 https://stackoverflow.com/questions/11959374/fastest-inline-assembly-spinlock/12979828#12979828还测试了大规模竞争自旋锁的想法,其中多个线程除了尝试获取锁之外什么都不做,但结果很差。该链接的答案提出了一些不正确的主张xchg全局锁定总线 - 对齐lock不要这样做,只是一个缓存锁(在特定情况下递增 int 是否有效地原子? https://stackoverflow.com/questions/39393850/can-num-be-atomic-for-int-num),每个核心可以在 a 上执行单独的原子 RMW不同的同时缓存行 https://stackoverflow.com/questions/11959374/fastest-inline-assembly-spinlock/12979828#comment118186534_12979828.)


然而,如果最初的尝试发现它锁住了,我们不想继续用原子 RMW 来敲击缓存行。那就是我们回到只读状态的时候。 10 个线程全是垃圾邮件xchg因为相同的自旋锁会使内存仲裁硬件非常繁忙。它可能会延迟解锁的存储的可见性(因为该线程必须争夺该行的独占所有权),因此它会直接适得其反。它也可以是其他核心的一般存储器。

PAUSE也是必不可少的,以避免 CPU 对内存排序的错误推测。仅当您正在读取的内存时才退出循环was由另一个核心修改。然而,我们不想pause在无争议的情况下。在天湖上,PAUSE等待的时间要长得多,比如从 ~5 个周期增加到 ~100 个周期,因此您绝对应该将自旋循环与初始解锁检查分开。

我确信 Intel 和 AMD 的优化手册谈到了这一点,请参阅x86 /questions/tagged/x86标记 wiki 以及大量其他链接。


还不够好?例如,我应该使用 C 中的 register 关键字吗?

register在现代优化编译器中是毫无意义的提示,除了调试版本(gcc -O0).

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

通过内联汇编锁定内存操作 的相关文章

随机推荐

  • 即使我使用 html_entity_decode ,html 实体也会传递到数据库中

    string susan 039 s string is scraped from website string html entity decode string sql INSERT INTO database SET name str
  • 读取csv文件ios

    我在读取 csv 文件时遇到问题 仅显示 csv 文件的最后一行 但是在我的 fetchedResultsController 中我有 2 行 这是代码 NSString writeString NSInteger i 0 for id o
  • 如何评估 Application Insights 请求“自己”的持续时间,而不考虑依赖项的持续时间?

    我正在尝试生成一个 Kusto 查询来测量请求的 自己 持续时间 减去依赖项的持续时间 但是 我无法真正弄清楚如何通过纯 Kusto 查询来解决这个问题 为了更好地理解预期的结果 下面是一个示例案例 高级视图 其中 R 是请求 Dx 是依赖
  • Python - re.findall 返回不需要的结果

    re findall 100 0 9 0 9 0 9 89 这仅返回结果 89 我需要退还全部 89 请问有什么想法怎么做吗 gt gt gt re findall 100 0 9 0 9 0 9 89 89 当有捕获组时findall仅返
  • mongodb第二个id字段自动递增

    我想在我的 mongodb 集合中有一个额外的 ID 字段 objectId 非常适合获取唯一 ID 但我需要更短的 ID 来进行用户管理 这些 ID 应该类似于100001 100002等等 是否可以通过自动增量获得这些 Thx Mong
  • 使用 Flex 和 Bison 编译时未定义对“_yyerror”的引用

    我正在尝试为迷你 Pascal 语言制作一个编译器 我为此使用了 Flex 和 Bison 并且出现了这个错误 我的 Flex 文件 include y tab h include
  • PyTorch ROCm 已推出 - 如何选择 Radeon GPU 作为设备

    由于 Pytorch 发布了 ROCm 版本 这使我能够使用 nvidias 之外的其他 GPU 我如何在 python 中选择我的 radeon gpu 作为设备 显然 像 device torch cuda is available 或
  • 将布尔属性编辑器转换为 MVC 视图中的下拉列表

    我目前已经搭建了一个视图 其中模型的布尔属性被传递给 Html EditorFor 帮助器 Html EditorFor model gt model EndCurrentDeal 一切都很好 但我真正想做的是将其按摩到下拉菜单中 例如
  • 在reactJS中下载文件的按钮

    我目前正在制作个人作品集 我正在尝试制作一个按钮 如果您单击它 则应下载简历 code
  • 需要详细说明未处理的延续参考

    我们的公司门户无法从 AD 中获取某个用户的组 在门户日志中 我们看到此错误 javax naming PartialResultException 未处理的继续引用剩余名称 我在 Google 上搜索了该错误 似乎描述此情况的最佳症状以及
  • C# 中的 Unix 时间转换 [重复]

    这个问题在这里已经有答案了 我正在尝试以unix时间获取GMT 我使用以下代码 public static long GetGMTInMS var unixTime DateTime Now ToUniversalTime new Date
  • CloudFormation 问题:无法删除堆栈

    我为我们的资源创建 CloudFormation 模板 它包括 Lambda 函数 API 网关 角色等 为了验证我们的模板 我使用它创建 CloudFormation 堆栈 检查我更新的一些资源 然后删除堆栈 但上次我尝试删除堆栈时收到这
  • Git 查找历史上所有的二进制文件

    抱歉 如果这与上一个问题重复 但我找不到我要找的东西 我正在将一个大型 cvs 代码集 20 多个具有 15 年历史的存储库 10 15 GB 大小 转换为 git 大部分大小是由于过去与代码一起提交的二进制文件造成的 虽然某些二进制文件是
  • 在 R 中绘制简单数据

    我有一个逗号分隔的文件 名为foo csv包含以下数据 scale serial spawn for worker 5 0 000178 0 000288 0 000292 0 000300 10 0 156986 0 297926 0 0
  • 在 Android Studio 中调试时证书验证路径错误

    我刚刚生成了示例应用程序https flutter dev docs get started codelab https flutter dev docs get started codelab 当我点击 Android Studio 中的
  • Objective-C:在应用程序上播放 Youtube 视频

    我正在尝试探索在 iOS 应用程序开发中我还能做些什么 现在我尝试在我的应用程序中包含一个视频 我下面有这段代码 旨在在视图加载时播放 YouTube 视频 但我得到的只是一个黑色的 webView NSString videoURL ht
  • R 中是否有 FoldLeft 函数?

    我想知道R中是否有foldLeft函数 和foldRight 的实现 该语言应该是 相当 面向功能的 因此我认为应该有类似的东西 但我在文档中找不到它 对我来说 foldLeft 函数适用于列表并具有以下签名 foldLeft B z B
  • 我应该分配或重置 unique_ptr 吗?

    考虑到所拥有对象的生命周期与其所有者相关联的常见情况 我可以通过以下两种方式之一使用唯一指针 它可以被赋值 class owner std unique ptr
  • iPhone开发:如何为UIActionSheet创建彩色或半透明背景?

    当您尝试在 iPhone 的 便笺 应用程序中删除便笺时 会弹出 UIActionSheet 该片材是半透明的 但不是黑色半透明的 这是如何实现的 是否可以将 UIActionSheet 的背景设置为某种颜色 我通常实现以下委托方法 voi
  • 通过内联汇编锁定内存操作

    我对低级的东西很陌生 所以我完全不知道你可能会遇到什么样的问题 我什至不确定我是否正确理解 原子 一词 现在我正在尝试通过扩展程序集围绕内存操作制作简单的原子锁 为什么 为了好奇心 我知道我正在重新发明轮子 并且可能过度简化了整个过程 问题