ホームページ >バックエンド開発 >Golang >コードを使用すると Golang の数学実行が遅くなる

コードを使用すると Golang の数学実行が遅くなる

王林
王林転載
2024-02-05 21:39:03926ブラウズ

使用代码时 Golang 数学执行缓慢

问题内容

问题:使用 math 包中的代码比使用 math 包慢得多。

下面的代码是从数学模块复制的,但是 math.Sqrt 的执行速度比 sqrt 快得多。我的问题是:为什么,可以做什么?

示例:(注意,感兴趣的函数不是 math.Sqrt。我想编写函数组合的自定义版本。)

package main

import (
    "fmt"
    "math"
    "unsafe"
)

const (
    uvnan    = 0x7FF8000000000001
    uvinf    = 0x7FF0000000000000
    uvneginf = 0xFFF0000000000000
    uvone    = 0x3FF0000000000000
    mask     = 0x7FF
    shift    = 64 - 11 - 1
    bias     = 1023
    signMask = 1 << 63
    fracMask = 1<<shift - 1
)

func Float64bits(f float64) uint64 { return *(*uint64)(unsafe.Pointer(&f)) }
func Float64frombits(b uint64) float64 { return *(*float64)(unsafe.Pointer(&b)) }
func sqrt(x float64) float64 {
    // special cases
    ix := Float64bits(x)
    // normalize x
    exp := int((ix >> shift) & mask)
    if exp == 0 { // subnormal x
        for ix&(1<<shift) == 0 {
            ix <<= 1
            exp--
        }
        exp++
    }
    exp -= bias // unbias exponent
    ix &^= mask << shift
    ix |= 1 << shift
    if exp&1 == 1 { // odd exp, double x to make it even
        ix <<= 1
    }
    exp >>= 1 // exp = exp/2, exponent of square root
    // generate sqrt(x) bit by bit
    ix <<= 1
    var q, s uint64               // q = sqrt(x)
    r := uint64(1 << (shift + 1)) // r = moving bit from MSB to LSB
    for r != 0 {
        t := s + r
        if t <= ix {
            s = t + r
            ix -= t
            q += r
        }
        ix <<= 1
        r >>= 1
    }
    // final rounding
    if ix != 0 { // remainder, result not exact
        q += q & 1 // round according to extra bit
    }
    ix = q>>1 + uint64(exp-1+bias)<<shift // significand + biased exponent
    return Float64frombits(ix)
}

func main() {
    n_iter := 100000000
    var x float64
    var y float64
    for i := 0; i < n_iter; i++ {
        y = math.Sqrt(2426.1)
    }
    fmt.Printf("Done\n") // Much, much faster

    for i := 0; i < n_iter; i++ {
        x = sqrt(2426.1)
    }
    fmt.Printf("Done\n")
    fmt.Printf("%f/%f\n", x, y)
}

正确答案


检查 math.Sqrt 的生成代码。使用 -S 标志构建此 main.go

package main

import (
    "fmt"
    "math"
    "math/rand"
)

func main() {
    x := rand.Float64()
    y := math.Sqrt(x)
    fmt.Println(y)
}

生成程序集:go build -gcflags='-S' main.go

...
        0x000e 00014 (./main.go:10)     PCDATA  $1, $0
        0x000e 00014 (./main.go:10)     CALL    math/rand.Float64(SB)
        0x0013 00019 (/home/user/go/pkg/mod/golang.org/<a href="https://www.php.cn/link/89fee0513b6668e555959f5dc23238e9" class="__cf_email__" data-cfemail="8afee5e5e6e9e2ebe3e4cafcbaa4baa4bba7ede5bba4b8bba4bfa4e6e3e4fff2a7ebe7eebcbe">[email&#160;protected]</a>/src/math/sqrt.go:94)    SQRTSD  X0, X0
        0x0017 00023 (./main.go:12)     MOVQ    X0, AX
        0x001c 00028 (./main.go:11)     XCHGL   AX, AX
...

可以看到,实际调用的是SQRTSD机器指令。

它受 cmd/compile/internal/ssagen/ssa.go 配置:在平台 sys.I386、sys.AMD64、sys.ARM、sys.ARM64、sys.Loong64、sys.MIPS、sys. MIPS64、sys.PPC64、sys.RISCV64、sys.S390X、sys.Wasm 编译器插入 SQRT 操作码。

您还可以检查许多其他被汇编替换的函数。

顺便说一句。在示例中,我在随机参数上调用 math.Sqrt ,因为对于文字值,编译器会计算平方根本身并编译为实际值。

这里是math.Sqrt(2.0)的汇编代码

0x0014 00020 (./main.go:11)     MOVQ    $4609047870845172685, AX
0x001e 00030 (./main.go:11)     PCDATA  $1, $1
0x001e 00030 (./main.go:11)     NOP
0x0020 00032 (./main.go:11)     CALL    runtime.convT64(SB)
0x0025 00037 (./main.go:11)     LEAQ    type:float64(SB), CX

4609047870845172685 是 64 位表示形式的实际 sqrt(2)

以上がコードを使用すると Golang の数学実行が遅くなるの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はstackoverflow.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。