【问题标题】:What is the best way to store a large list of Boolean values in x86 assembly?在 x86 程序集中存储大量布尔值的最佳方法是什么?
【发布时间】:2021-09-27 17:53:06
【问题描述】:

最近我一直在处理充满布尔值的大型数组。目前,我使用.space 指令将它们存储在.bss 部分中,这允许我创建字节数组。但是,由于我只需要存储布尔值,我希望从数组中逐位读取和写入数据。

目前,我能想到的最好的方法是使用 .space 指令和所需存储空间的 1/8,并使用 ((1 << k) & n) 公式获取和设置各个位,其中 k 是位和n 是数据,但这似乎相当笨拙,我想知道是否有更优雅的解决方案?谢谢。 (最好是 AT&T 语法)

【问题讨论】:

  • 这听起来像是正常的做法。我不认为有任何位数组指令。
  • 作为位数组或字节数组,取决于较小的缓存占用空间是否值得额外的指令来计算字节或双字索引以加载到bt reg,reg的寄存器中.字节数组更适合写入(除非这会导致更多缓存未命中),因为您不需要读取周围的布尔值,只需存储整个字节即可。
  • 好吧,我想现在我的解决方案是正常的方式。
  • @Barmar: x86 实际上确实 有位数组指令(例如bts [rdi + rcx], eaxrdi + rcx + eax/8 索引内存并测试+将位设置为eax%8。但他们' 很慢,所以您通常希望避免使用它们,除非在手动执行相同索引后的 reg、reg 表单中。felixcloutier.com/x86/bt
  • @PeterCordes 确实如此。但这真的不是特别糟糕,作为初学者可能值得简化代码。

标签: assembly x86 boolean x86-64 bitarray


【解决方案1】:

对于稀疏位集(全部为真或全部为假,少数例外),您可以使用任何集合数据结构(包括哈希表)存储一组索引。您当然可以在 asm 中手动实现任何算法,就像在 C 中一样。可能有一些更专业的数据结构适用于各种目的/用例。


对于“正常”的布尔数组,您的两个主要选项是

  • 每个字节解压 1 个 bool,值为 0 / 1,如 C bool arr[size]
    (在.bss 或动态分配,无论你想放在哪里,与任何字节数组相同)。

    占用的空间是打包位数组的 8 倍(从而占用缓存空间),但非常易于使用。对于随机访问特别有效,尤其是写入,因为您可以存储一个字节而不会干扰其邻居。 (不必读取/修改/写入包含的字节或双字)。

    除了缓存占用空间会导致更多缓存未命中,如果它加上其余数据不适合任何级别的缓存,较低的密度也不利于搜索、弹出计数、复制或设置/清除一系列元素.

    如果可以在写入数组的代码中保存指令,则可以允许 0 / 非 0,而不是 0 / 1。但是,如果你想比较两个元素,或者计算真实值或其他什么,这可能会在阅读时花费说明。请注意,大多数 C/C++ ABI 对 bool 严格使用 0 / 1 字节,并将持有 2bool 传递给 C 函数 could make it crash

  • 每个 bit 打包 1 个 bool,例如 C++ std::vector<bool>。 (除了你当然可以将它存储在任何你想要的地方,不像 std::vector 总是动态分配)。

    Howard Hinnant 的文章 On vector<bool> 讨论了位数组擅长的一些事情(通过适当优化的实现),例如搜索 true 可以一次检查整个块,例如使用 qword 搜索一次 64 位,或者使用 AVX vptest 一次 256 位。 (然后tzcntbsf 当你找到一个非零块时,或多或少与字节元素相同:Efficiently find least significant set bit in a large array?)。所以比使用字节数组快 8 倍(即使假设缓存命中相同),除了一些额外的工作,如果使用 SIMD 向量化,在向量中找到正确的字节或 dword 后找到元素中的位。与字节数组相比,只需 vpslld $7, %ymm0, %ymm0vpmovmskb %ymm0, %eax / bsf %eax,%eax 将字节转换为位图并进行搜索。


x86 位数组又名位串指令:mem 操作数很慢

x86 确实具有位数组指令,例如bt(位测试)和bts(位测试和设置),还有重置(清除)和补码(翻转),但它们' re slow with a memory destination 和一个寄存器位索引;手动索引正确的字节或双字并加载它实际上更快,然后使用bts %reg,%reg 并存储结果。 Using bts assembly instruction with gcc compiler

# fast version:
# set the bit at index n (RSI) in bit-array at RDI
   mov  %esi, %edx           # save the original low bits of the index
   shr  $5, %rsi             # dword index = bit-index / 8 / 4
   mov  (%rdi, %rsi, 4), %eax   # load the dword containing the bit
   bts  %edx, %eax           # eax |= 1 << (n&31)   BTS reg,reg masks the bit-index like shifts
   mov  %eax, (%rdi, %rsi)   # and store it back

这有效地将位索引拆分为双字索引和双字内位索引。 dword 索引是通过移位显式计算的(并使用缩放索引寻址模式转换回对齐 dword 的字节偏移量)。 bit-within-dword 索引是隐式计算的,作为bts %reg,%reg 掩码计数的一部分。

(如果您的位数组肯定小于 2^32 位(512 MiB),您可以使用shr $5, %esi 节省一个字节的代码大小,丢弃位索引的高 32 位。)

这会在 CF 中留下旧位的副本,以备不时之需。 bts reg,reg 在 Intel 上是单 uop,在 AMD 上是 2 uop,因此绝对值得与手动执行 mov $1, %reg / shl / or

这在现代 Intel CPU(https://uops.info/https://agner.org/optimize/)上只有 5 uop,而 bts %rsi, (%rdi) 的 10 uop 则完全相同(但不需要任何 tmp 寄存器)。

你会注意到我只使用了 dword 块,不像在 C 中你经常看到使用 unsigned longuint64_t 块的代码,因此搜索可以快速进行。但是在 asm 中,使用不同大小的访问相同的内存是零问题,除了存储转发停顿,如果你先做一个窄存储然后再做一个宽负载。更窄的 RMW 操作实际上更好,因为它意味着不同位上的操作可以更接近,而不会实际创建错误的依赖关系。如果这是一个主要问题,您甚至可以使用字节访问,除了 bts 和朋友只下降到 16 位 word 操作数大小,因此您必须手动 and $7, %reg 来提取位内-byte 来自位索引。

例如喜欢阅读:

# byte chunks takes more work:
   mov  %esi, %edx      # save the original low bits
   shr  $3, %rsi        # byte index = bit-index / 8
   movzbl (%rdi, %rsi), %eax   # load the byte containing the bit
   and  $7, %edx
   bt   %edx, %eax      # CF = eax & 1 << (n&7)
   # mov  %al, (%rdi, %rsi)   if you used BTS, BTR, or BTC 

字节加载最好使用movzx(又名AT&T movzbl)来避免写入部分寄存器。


如果您需要原子地设置一些位(例如在多线程程序中),您可以lock bts %reg, mem,或者您可以在寄存器中生成1&lt;&lt;(n&amp;31),如果您需要lock or %reg, mem不在乎旧值是什么。 lock bts 很慢而且微编码,所以如果你需要原子性,你可以使用它而不是试图避免疯狂的 CISC 位数组语义。

在多线程的情况下,有更多理由考虑每字节使用 1 个布尔值,这样您就可以只使用普通的 movb $1, (%rdi, %rsi)(保证原子且不会干扰其邻居:Can modern x86 hardware not store a single byte to memory?),而不是一个原子 RMW。

【讨论】:

    猜你喜欢
    • 2010-10-10
    • 1970-01-01
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-18
    • 2012-04-25
    相关资源
    最近更新 更多