【问题标题】:Can I atomically increment a 16 bit counter on x86/x86_64?我可以在 x86/x86_64 上原子地增加一个 16 位计数器吗?
【发布时间】:2009-10-09 05:53:44
【问题描述】:

我想通过将现有的 32 位计数器转换为 16 位计数器来节省内存。该计数器以原子方式递增/递减。如果我这样做:

  1. 对于 x86/x86_64 上的 atomic_inc(uint16_t x) 使用什么指令?
  2. 这在多处理器 x86/x86_64 机器中是否可靠?
  3. 在这些体系结构中执行此操作是否会导致性能损失?
  4. 如果 (3) 为“是”,那么预期的性能损失是多少?

感谢您的 cmets!

【问题讨论】:

  • 除非你有很多计数器(而且在“兆字节”中很多),否则节省 2 个字节似乎需要付出很多努力。您在这里尝试解决的实际问题是什么?
  • 是的,我有很多这些计数器,以兆字节为单位。每个这样的计数器代表在相应的内存块上的未决操作。当计数器归零时,我应该触发另一个操作。

标签: performance assembly x86 atomic x86-64


【解决方案1】:

这是使用 GCC 程序集扩展的一个,作为 Steve 的 Delphi 答案的替代方案:

uint16_t atomic_inc(uint16_t volatile* ptr)
{
    uint16_t value(1);
    __asm__("lock xadd %w0, %w1" : "+r" (value) : "m" (*ptr));
    return ++value;
}

将 1 更改为 -1,将 ++ 更改为 --,以减少。

【讨论】:

  • 谢谢,但我在这里真正谈论的是 AMD64/Pentiums。 :)
  • 这很酷。如果您不想使用 Delphi 进行编码,我仍然会为您提供 C 替代方案。 :-)
  • (好吧,将 C 的初始值设定项从 uint_t value(1) 更改为 uint_t value = 1(我的 C++ 习惯正在影响我),但是是的。:-P)
  • lock inc / setcc 可以解决问题,因为inc 根据结果(即写入内存的值)设置标志。在 GNU C 中,gcc6 introduced an extension 允许返回标志,因此它可以内联到 lock inc / jcc 而不是 lock inc / setcc / test/jcc。但如果没有这些,lock xadd 可能还不错:与lock inc / setcc 相比,代码量更小,insn 更少。
【解决方案2】:

这是一个有效的 Delphi 函数:

function LockedInc( var Target :WORD ) :WORD;
asm
        mov     ecx, eax
        mov     ax, 1
   Lock xadd    [ecx], ax
        Inc     eax
end;

我猜你可以把它转换成你需要的任何语言。

【讨论】:

  • 为了澄清非 Delphi/BASM 用户,我要补充一点,在这个(32 位)例程中,指向 Target 的指针将在 EAX 中传递,函数的返回值将是在 AX 中。
  • 你是故意留下返回值的高半部分=输入指针的高半部分,还是一个错误?你可能想要mov ecx, 1(不仅仅是CX)/lock xadd [eax], cx/lea eax, [ecx+1]。 (在写入cx 之后,从阅读ecx 得到partial-register stall on old Intel CPUs (Nehalem and earlier)。如果需要,请使用单独的 movzx + inc 指令来避免这种情况。)
【解决方案3】:

执行原子增加的最简单方法如下(这是内联 ASM):

asm
  lock inc dword ptr Counter;
end;

其中 J 是一个整数。这将直接增加其内存位置中的 Counter。

我已经用蛮力测试了它,它可以 100% 工作。

【讨论】:

  • OP 想要一个 16 位计数器,但是可以使用 word 而不是 dword。顺便说一句,您可以基于蛮力测试(无需同时检查手册)说“100% 有效”的唯一方法是,如果您在每个当前 和未来 x86 CPU 上对其进行测试。 lock 前缀没有故障的任何东西都是 100% 保证原子的(我认为;至少对于记录了 lock 前缀适用的指令)。但例如movdqa [eax], xmm0 在某些 CPU 上是原子的,但在其他 CPU 上不是,因此在 Core2 上进行测试不会揭示某些多插槽 Opterons 上的问题。
【解决方案4】:

回答其他三个问题:

  1. 没有找到一种方法来制作以 2 开头的编号列表
  2. 是的,这在多处理器环境中是可靠的
  3. 是的,存在性能损失
  4. “lock”前缀锁定了总线,不仅针对处理器,还针对可能希望通过 DMA(大容量存储、图形...)访问总线的任何外部硬件。所以它很慢,通常约为 100 个时钟周期,但成本可能更高。但是,如果您有“兆字节”的计数器,很可能您将面临缓存未命中,在这种情况下,无论如何您将不得不等待大约 100 个时钟(内存访问时间),如果页面未命中,则需要几个百,因此锁定的开销可能无关紧要。

【讨论】:

  • 感谢您的回答。它最接近我在 Chris 的回复后寻找的内容。
  • 我相信公交车上锁已成为过去。当前一代处理器执行缓存行锁定:这也与用于缓存一致性的 MESI 或类似 MESI 的协议紧密结合。
  • 否决,因为没有性能损失,或者即使对于单个原子计数器也可能是微不足道的。使用大量原子计数器,16 位操作数大小应该是一个巨大的胜利。 x86 缓存硬件本机支持字节和字操作,因此如果字是 16 位对齐的,我预计不会有任何问题。 lock inc word [mem] 应该与 lock inc dword [mem] 具有基本相同的性能(忽略缓存未命中)。
猜你喜欢
  • 2012-05-17
  • 2019-01-17
  • 1970-01-01
  • 2018-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-05
  • 2019-03-18
相关资源
最近更新 更多