Go中的地址对齐

Posted

技术标签:

【中文标题】Go中的地址对齐【英文标题】:Address alignment in Go 【发布时间】:2018-05-02 14:07:03 【问题描述】:

所以我使用 c2go 将 C 代码与 Go 链接。 C 代码要求从 Go 调用的函数的某些参数是 256 位对齐的(函数参数都是指向 Go 变量的指针)。 Go有没有办法实现这一点(即为Go中的变量指定256对齐)?

在 Go 中,“unsafe.Alignof(f)”显示为 8 个字节对齐(对于“var f [8]float32”),即 Go 保证 f 仅对齐 8 个字节。我需要它以某种方式对齐 32 个字节。

对于好奇的人: C 代码使用 SIMD 指令(具体为 AVX)。我使用的是“vmovaps”指令,它需要操作数 256 位对齐。我可以使用不需要对齐的“vmovups”,但我怀疑这会降低性能。

【问题讨论】:

AVX 中未对齐负载的性能损失通常可以忽略不计(与 10 多年前在旧 CPU 上使用 SSE 的未对齐负载不同)。 Go 保证 var f [8]float32 的最小对齐属性是 4 而不是 8: Size and alignment guarantees。此外,var f [8]float32; fmt.Println(unsafe.Alignof(f)) 的输出是 4 而不是 8:play.golang.org/p/ok2IvK0214 仅当您跨越缓存行或页面边界时,未对齐的加载才会产生额外成本。这对于页面拆分很重要(尤其是在 Skylake 以外的 CPU 上),而对于 Haswell 及更高版本的缓存行拆分则相当小。 (@PaulR:splitting unaligned 256b loads into vmovups / vinsertf128 is actually a win on Sandybridge/Ivybridge, if the data really is misaligned at runtime,而不是在构建时不知道对齐。 Gcc 启用 -mavx256-split-unaligned-load-mtune=generic,即使使用 -mavx2,因为 there's no option to "tune for all CPUs that could run this code"。 :( 这就是为什么如果你正在为 Haswell 构建,使用 gcc -march=haswell 而不是 gcc -mavx2 是一个非常好的主意。(clang 从不做这种分裂的东西。)另外,gcc 现在可能应该放弃它,因为它不是在 SnB 上大获全胜,但在 HSW 上损失惨重,数据确实是一致的。 TL:DR:如果您可以对所有数据使用对齐分配,请执行此操作。只要数据在运行时对齐,您在其上运行的代码是否使用未对齐错误的指令或在硬件中处理它都没有关系。这对于 AVX512(其中向量大小 = 缓存线大小,因此未对齐意味着每个负载都跨越缓存线边界)比 AVX2 重要得多。对于在内存或 L3 高速缓存中循环阵列,仅 16B 对齐或更少与 AVX2 对齐通常不是可测量的减速。对于 AVX2,与 AVX512 不同,未对齐可能只是 L1D 缓存中数据热的问题。 【参考方案1】:

唯一合理的实现方法是在 go 中对函数进行原型化,然后将 (go) 程序集编写为 BYTEWORD 指令,就像在 golang 库本身中所做的那样,如 glang-1.9.1 文档中所述:

不支持的操作码

汇编器旨在支持编译器,因此并非所有 为所有架构定义了硬件指令:如果 编译器不会生成它,它可能不存在。如果你需要 使用缺少的指令,有两种方法可以继续。一种是 更新汇编程序以支持该指令,即 直截了当,但只有当它可能是指令时才值得 将再次使用。相反,对于简单的一次性情况,有可能 使用 BYTE 和 WORD 指令将显式数据放入 TEXT 中的指令流。

例如,

blake2b implementation at line 115 为 AVX2 执行此操作

【讨论】:

【参考方案2】:

例如,用更少的 CPU 时间换取更多的内存,

package main

import (
    "fmt"
    "unsafe"
)

// Float32Align32 returns make([]float32, n) 32-byte aligned.
func Float32Align32(n int) []float32 
    // align >= size && align%size == 0
    const align = 32 // SIMD AVX byte alignment
    const size = unsafe.Sizeof(float32(0))
    const pad = int(align/size - 1)
    if n <= 0 
        return nil
    
    s := make([]float32, n+pad)
    p := uintptr(unsafe.Pointer(&s[0]))
    i := int(((p+align-1)/align*align - p) / size)
    j := i + n
    return s[i:j:j]


func main() 
    f := Float32Align32(8) // SIMD AVX
    fmt.Printf(
        "SIMD AVX: %T %d %d %p %g\n",
        f, len(f), cap(f), &f[0], f,
    )
    CFuncArg := &f[0]
    fmt.Println("CFuncArg:", CFuncArg)

游乐场:https://play.golang.org/p/mmFnHEwGKt

输出:

SIMD AVX: []float32 8 8 0x10436080 [0 0 0 0 0 0 0 0]
CFuncArg: 0x10436080

【讨论】:

以上是关于Go中的地址对齐的主要内容,如果未能解决你的问题,请参考以下文章

Go | 结构体及内存对齐

C中的内存对齐:返回地址中的偏移量是如何考虑的?

内存中的数据对齐

Go-指针

Perl 中的 OS 页面对齐分配

例外4:inst / data fetch中的未对齐地址:0x100100bb