【问题标题】:How can I generate a 256 bit mask如何生成 256 位掩码
【发布时间】:2019-04-15 15:21:08
【问题描述】:

我有一个 uint64_t[4] 数组,我需要生成一个掩码, 这样数组,如果它是一个 256 位整数,则等于 (1

我想出的最好的东西是无分支的,但它需要很多指令。它在 Zig 中,因为 Clang 似乎没有暴露 llvm 的饱和减法。 http://localhost:10240/z/g8h1rV

有没有更好的方法来做到这一点?

var mask: [4]u64 = undefined;
for (mask) |_, i|
    mask[i] = 0xffffffffffffffff;
mask[3] ^= ((u64(1) << @intCast(u6, (inner % 64) + 1)) - 1) << @intCast(u6, 64 - (inner % 64));
mask[2] ^= ((u64(1) << @intCast(u6, (@satSub(u32, inner, 64) % 64) + 1)) - 1) << @intCast(u6, 64 - (inner % 64));
mask[1] ^= ((u64(1) << @intCast(u6, (@satSub(u32, inner, 128) % 64) + 1)) - 1) << @intCast(u6, 64 - (inner % 64));
mask[0] ^= ((u64(1) << @intCast(u6, (@satSub(u32, inner, 192) % 64) + 1)) - 1) << @intCast(u6, 64 - (inner % 64));

【问题讨论】:

  • 到 localhost 的链接有什么用?
  • 您为什么架构编程?您可以使用哪些指令集扩展?你用什么语言编程?
  • @fuz "你用什么语言编程?" Zig,如问题中所述(但由于某种原因不在标签中)
  • |_, i| - 这是什么编程语言?编辑: Zig .. 没听说过,但没关系.. 如果我正确阅读了你的描述,你只需要 255 个不同的面具 - 你考虑过查找表吗?
  • @ShawnLandden 鉴于您的问题被标记为assembly,看来您可能需要优化的汇编代码。是这样吗?如果是,请告诉我您正在编程的架构和指令集扩展。如果不是,请考虑删除assembly 标签。

标签: c assembly bit-manipulation bit zig


【解决方案1】:

您是否将 x86-64 与 AVX2 一起用于 256 位向量?我认为这是一个有趣的案例。

如果是这样,您可以在几条指令中使用饱和减法和可变计数移位来完成此操作。

x86 SIMDvpsrlvq 一样移动使移位计数饱和,当计数 >= 元素宽度时将所有位移出。与整数移位不同,移位计数被屏蔽(因此环绕)。

对于最低的u64 元素,从全1 开始,我们需要保持bitpos >= 64 不变。或者对于较小的位位置,将其右移64-bitpos。正如您所观察到的,无符号饱和减法看起来像是为较大的位位置创建 0 的移位计数的方法。但是 x86 只有 SIMD 饱和减法,并且仅适用于字节或字元素。但是如果我们不关心 bitpos > 256,那很好,我们可以在每个 u64 的底部使用 16 位元素,并让 0-0 发生在 u64 的其余部分。

您的代码看起来相当复杂,创建了(1&lt;&lt;n) - 1 和 XORing。 我认为直接在0xFFFF...FF 元素上使用可变计数移位要容易得多。

我不认识 Zig,所以尽你所能让它发出这样的 asm。希望这很有用,因为您标记了此;应该很容易转换为 C 或 Zig 的内在函数(如果有的话)。

default rel
section .rodata
shift_offsets:  dw  64, 128, 192, 256        ; 16-bit elements, to be loaded with zero-extension to 64

section .text
pos_to_mask256:
    vpmovzxwq   ymm2, [shift_offsets]      ; _mm256_set1_epi64x(256, 192, 128, 64)
    vpcmpeqd    ymm1, ymm1,ymm1            ; ymm1 = all-ones
                                  ; set up vector constants, can be hoisted

    vmovd         xmm0, edi
    vpbroadcastq  ymm0, xmm0           ; ymm0 = _mm256_set1_epi64(bitpos)

    vpsubusw      ymm0, ymm2, ymm0     ; ymm0 = {256,192,128,64}-bitpos with unsigned saturation
    vpsrlvq       ymm0, ymm1, ymm0     ; mask[i] >>= count, where counts >= 64 create 0s.

    ret

如果输入整数在内存中开始,您当然可以高效地将其直接广播加载到 ymm 寄存器中。

shift-offsets 向量当然可以从循环中提升出来,全一也可以。


输入 = 77 时,高 2 个元素通过 256-77=179 和 192-77=115 位的移位归零。用 NASM + GDB for EDI=77 测试,结果为

(gdb) p /x $ymm0.v4_int64
{0xffffffffffffffff, 0x1fff, 0x0, 0x0}

GDB 首先打印低元素,与 Intel 表示法/图表相反。这个向量实际上是0, 0, 0x1fff, 0xffffffffffffffff,即 64+13 = 77 个一位,其余全为零。其他测试用例

  • edi=0: 掩码 = 全零
  • edi=1: 掩码 = 1
  • ... : mask = edi 底部一位,然后是零
  • edi=255: 掩码 = 除了顶部元素的最高位之外的所有 1
  • edi=256: 掩码 = 全部
  • edi&gt;256:掩码 = 全部。 (无符号减法处处饱和为 0。)

可变计数班次需要 AVX2。 psubusb/w is SSE2,因此您可以考虑使用 SIMD 执行该部分,然后返回到标量整数进行移位,或者一次只对一个元素使用 SSE2 移位。就像psrlq xmm1, xmm0 一样,它将xmm0 的低64 位作为xmm1 的所有元素的移位计数。

大多数 ISA没有饱和标量减法。我认为,一些 ARM CPU 可以用于标量整数,但 x86 没有。 IDK 你正在使用什么。

在 x86(和许多其他 ISA)上,您有 2 个问题:

  • 为低元素保留全一(修改移位结果,或将移位计数饱和为 0)
  • 为包含掩码最高位的元素之上的高元素生成0。 x86 标量移位根本无法做到这一点,因此您可以在这种情况下为移位输入0。也许使用cmov 根据sub192-w 或其他东西设置的标志来创建它。
    count = 192-w;
    shift_input = count<0 ? 0 : ~0ULL;
    shift_input >>= count & 63;      // mask to avoid UB in C.  Optimizes away on x86 where shr does this anyway.

嗯,不过,这并不能处理饱和减法到 0 以保持全一。

如果针对 x86 以外的 ISA 进行调优,可能会考虑其他一些选项。或者也许 x86 上也有更好的东西。使用sar reg,63 创建全一或全零是一个有趣的选项(广播符号位),但当192-count 的符号位 = 0 时,我们实际上需要全一。

【讨论】:

  • 感兴趣的读者:另请参阅here,了解 AVX2 内在函数版本,使用与上述或多或少相似的技术来计算 256 位掩码。
  • @wim:呵呵,我觉得这个问题看起来很眼熟。我肯定会说“更相似”,而不是更少:P 我什至考虑使用 32 位元素宽度,但坚持使用 64 以便更容易移植到标量,以防 OP 这样做。此外,64,128,192,256 常量压缩到 pmovzx 加载大小的一半。
【解决方案2】:

这是一些编译和运行的 Zig 代码:

const std = @import("std");

noinline fn thing(x: u256) bool {
    return x > 0xffffffffffffffff;
}

pub fn main() anyerror!void {
    var num: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
    while (thing(num)) {
        num /= 2;
        std.debug.print(".", .{});
    }
    std.debug.print("done\n", .{});
}

Zig master 从中生成相对干净的 x86 汇编器。

【讨论】:

    猜你喜欢
    • 2020-01-28
    • 2011-04-25
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 2015-01-20
    • 1970-01-01
    • 2017-02-12
    • 2015-04-28
    相关资源
    最近更新 更多