go - 对 byte slice 进行更快的按位与运算

标签 go assembly bit-manipulation slice

我想对字节矩阵的每一列执行按位与,该矩阵存储在golang的[][]byte中。我创建了一个repo带有可运行的测试代码。

它可以简化为对两个长度相等的 byte slice 进行按位与运算。最简单的方法是使用 for 循环来处理每对字节。

func and(x, y []byte) []byte {
    z := make([]byte, lenght(x))
    for i:= 0; i < len(x); i++ {
        z[i] = x[i] & y[i]
    }
    return z
}

但是,对于长 slice 来说,速度非常慢。更快的方法是展开 for 循环(检查 benchmark result )

BenchmarkLoop-16                   14467             84265 ns/op
BenchmarkUnrollLoop-16             17668             67550 ns/op

还有什么更快的方法吗?去组装吗?

提前谢谢您。

最佳答案

我写了一个go assembly implementation经过两天的(go)汇编学习后,使用 AVX2 指令。

性能良好,是简单循环版本的10X。但仍需要对兼容性和性能进行优化。欢迎提出建议和 PR。

注意:代码和基准测试结果已更新。

我感谢@PeterCordes 提供的许多宝贵建议。

#include "textflag.h"

// func AND(x []byte, y []byte)
// Requires: AVX
TEXT ·AND(SB), NOSPLIT|NOPTR, $0-48
    // pointer of x
    MOVQ x_base+0(FP), AX

    // length of x
    MOVQ x_len+8(FP), CX

    // pointer of y
    MOVQ y_base+24(FP), DX

    // --------------------------------------------
    // end address of x, will not change: p + n
    MOVQ AX, BX
    ADDQ CX, BX

    // end address for loop
    // n <= 8, jump to tail
    CMPQ CX, $0x00000008
    JLE  tail

    // n < 16, jump to loop8
    CMPQ CX, $0x00000010
    JL   loop8_start

    // n < 32, jump to loop16
    CMPQ CX, $0x00000020
    JL   loop16_start

    // --------------------------------------------
    // end address for loop32
    MOVQ BX, CX
    SUBQ $0x0000001f, CX

loop32:
    // compute x & y, and save value to x
    VMOVDQU (AX), Y0
    VANDPS  (DX), Y0, Y0
    VMOVDQU Y0, (AX)

    // move pointer
    ADDQ $0x00000020, AX
    ADDQ $0x00000020, DX
    CMPQ AX, CX
    JL   loop32

    // n <= 8, jump to tail
    MOVQ BX, CX
    SUBQ AX, CX
    CMPQ CX, $0x00000008
    JLE  tail

    // n < 16, jump to loop8
    CMPQ CX, $0x00000010
    JL   loop8_start

    // --------------------------------------------
loop16_start:
    // end address for loop16
    MOVQ BX, CX
    SUBQ $0x0000000f, CX

loop16:
    // compute x & y, and save value to x
    VMOVDQU (AX), X0
    VANDPS  (DX), X0, X0
    VMOVDQU X0, (AX)

    // move pointer
    ADDQ $0x00000010, AX
    ADDQ $0x00000010, DX
    CMPQ AX, CX
    JL   loop16

    // n <= 8, jump to tail
    MOVQ BX, CX
    SUBQ AX, CX
    CMPQ CX, $0x00000008
    JLE  tail

    // --------------------------------------------
loop8_start:
    // end address for loop8
    MOVQ BX, CX
    SUBQ $0x00000007, CX

loop8:
    // compute x & y, and save value to x
    MOVQ (AX), BX
    ANDQ (DX), BX
    MOVQ BX, (AX)

    // move pointer
    ADDQ $0x00000008, AX
    ADDQ $0x00000008, DX
    CMPQ AX, CX
    JL   loop8

    // --------------------------------------------
tail:
    // left elements (<=8)
    MOVQ (AX), BX
    ANDQ (DX), BX
    MOVQ BX, (AX)
    RET

基准结果:

test                       data-size        time
-------------------        ---------        -----------
BenchmarkGrailbio          8.00_B           4.654 ns/op
BenchmarkGoAsm             8.00_B           4.824 ns/op
BenchmarkUnrollLoop        8.00_B           6.851 ns/op
BenchmarkLoop              8.00_B           8.683 ns/op

BenchmarkGrailbio          16.00_B          5.363 ns/op
BenchmarkGoAsm             16.00_B          6.369 ns/op
BenchmarkUnrollLoop        16.00_B          10.47 ns/op
BenchmarkLoop              16.00_B          13.48 ns/op

BenchmarkGoAsm             32.00_B          6.079 ns/op
BenchmarkGrailbio          32.00_B          6.497 ns/op
BenchmarkUnrollLoop        32.00_B          17.46 ns/op
BenchmarkLoop              32.00_B          21.09 ns/op

BenchmarkGoAsm             128.00_B         10.52 ns/op
BenchmarkGrailbio          128.00_B         14.40 ns/op
BenchmarkUnrollLoop        128.00_B         56.97 ns/op
BenchmarkLoop              128.00_B         80.12 ns/op

BenchmarkGoAsm             256.00_B         15.48 ns/op
BenchmarkGrailbio          256.00_B         23.76 ns/op
BenchmarkUnrollLoop        256.00_B         110.8 ns/op
BenchmarkLoop              256.00_B         147.5 ns/op

BenchmarkGoAsm             1.00_KB          47.16 ns/op
BenchmarkGrailbio          1.00_KB          87.75 ns/op
BenchmarkUnrollLoop        1.00_KB          443.1 ns/op
BenchmarkLoop              1.00_KB          540.5 ns/op

BenchmarkGoAsm             16.00_KB         751.6 ns/op
BenchmarkGrailbio          16.00_KB         1342 ns/op
BenchmarkUnrollLoop        16.00_KB         7007 ns/op
BenchmarkLoop              16.00_KB         8623 ns/op

关于go - 对 byte slice 进行更快的按位与运算,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68280854/

相关文章:

amazon-web-services - 如何创建预签名的 put url 并使用环境变量来设置 Bucket 和 Key

reactjs - 无法加载 http ://localhost:3000/js/app. jsx

go - 使用包加载器,如何更新包含注释的ast并打印结果代码

c++ - gcc 的汇编输出奇怪/错误?

visual-studio - 如何在 Visual Studio 2017 的 x86 程序集中使用 printf?

c - 如何在C中实现算术右移

go - 返回 Golang 中的 CPU 插槽数、核心数和线程数

algorithm - 在没有PWM的AVR组件中生成方波

c - 打印数字的二进制表示

c# - 计算 Int32 中的前导零