出于好奇,我一直在查看标准库中的sort包和即将成为added to the standard library的实验slices包的源代码.这两个包都定义了一个IsSorted函数,它们通过验证相邻元素是否排序来判断列表是否排序.然而,这是在reverse年完成的.

我创建了一个小复制品,大致基于通用的slices包.我的实验确实表明,reverse版的速度更快.

func IsSortedForward[T any](slice []T, compare func(a, b T) int) bool {
    for i := 0; i < len(slice) - 1; i++ {
        if compare(slice[i], slice[i+1]) > 0 {
            return false
        }
    }
    return true
}

func IsSortedForward2[T any](slice []T, compare func(a, b T) int) bool {
    n := len(slice) - 1
    for i := 0; i < n; i++ {
        if compare(slice[i], slice[i+1]) > 0 {
            return false
        }
    }
    return true
}

func IsSortedReverse[T any](slice []T, compare func(a, b T) int) bool {
    for i := len(slice) - 1; i > 0; i-- {
        if compare(slice[i], slice[i-1]) < 0 {
            return false
        }
    }
    return true
}

我最初的 idea 是,向前版本会更"缓存友好".然而,我确实注意到反向版本的一个优点,因为len(slice) - 1只计算一次.然而,在前向版本中,它是在每次迭代时计算的.我的假设是,这就是为什么反向版本略微快一些的原因.

然后,我在前向版本的循环外提取了len(slice) - 1,因此只计算一次,这确实提高了它的性能.然而,它仍然比反向版本略微慢一些.我的猜测是,反向版本中的比较i > 0(零)比向前版本中的比较i < n更快?

Why do you think the reverse version is faster? Why is the adjusted forward version still not as fast as the reverse version?

编辑:我不知道编译器资源管理器(Godbolt)可以和Go一起工作……这里是这个问题中涉及的little reproduction个函数.看来围棋有它自己的assembly language

推荐答案

简而言之:因为向下循环可以生成更有效的代码,特别是对于条件部分.

100 Should you use it in your own code? Not if performance is not critical. Use a loop that is more readable and makes sense. There is obvious gain using the faster version in the standard library (as everyone is using it), but just for a few microseconds don't sacrifice readability in your code.

IsSortedForward2()IsSortedReverse()大致相同,除了前者在主体内使用递增运算符(i++)和i+1,后者在主体内使用减量运算符(i--)和i-1,它们在性能方面是等价的.

显著的差异来自于条件:正向变量使用i < n,而反向变量使用i > 0.前者需要比较两个变量,后者需要将一个变量与一个非常特殊的值进行比较:0.

通常,第一个条件需要将2个变量(内存位置)加载到寄存器并比较它们的值.后者需要将1变量加载到寄存器中,并将其与特殊的0值进行比较.请注意,在实践中,这些变量可能驻留在寄存器中,因此可能不必发生实际的内存负载.

您可以通过运行以下命令来判断生成的汇编代码:

go tool compile -S play.go > play.s

我使用的源代码行:

20 func IsSortedForward2[T any](slice []T, compare func(a, b T) int) bool {
21     n := len(slice) - 1
22     for i := 0; i < n; i++ {
23          if compare(slice[i], slice[i+1]) > 0 {
24              return false
25          }
26     }
27     return true
28 }
29 
30 func IsSortedReverse[T any](slice []T, compare func(a, b T) int) bool {
31     for i := len(slice) - 1; i > 0; i-- {
32         if compare(slice[i], slice[i-1]) < 0 {
33             return false
34         }
35     }
36     return true
37 }

有趣的行是22和31,以下是为这些行生成的程序集:

play.go:22) JMP     77
play.go:22) MOVQ    main.n+16(SP), DI
play.go:22) MOVQ    main.compare+80(SP), SI
play.go:22) MOVQ    main.slice+64(SP), CX
play.go:22) MOVQ    main.slice+56(SP), BX
play.go:22) MOVQ    main..autotmp_9+24(SP), AX
play.go:22) CMPQ    AX, DI
play.go:22) JGE     136

play.go:31) DECQ    CX
play.go:31) JMP     53
play.go:32) MOVQ    main.i+16(SP), CX
play.go:32) DECQ    CX
play.go:32) MOVQ    main.slice+48(SP), BX
play.go:32) MOVQ    main.compare+72(SP), SI
play.go:31) TESTQ   CX, CX
play.go:31) JLE     100
play.go:31) MOVQ    CX, main.i+16(SP)

IsSortedForward2()in加载到AXDI寄存器中,并使用CMPQ对它们进行比较.

IsSortedReverse()只需将i加载到CX中,并使用TESTQ测试它是否为零.

Go相关问答推荐

如何获得与cksum相同的CRC 32?

由于索引器压缩比限制,扫描包含golang包的图像时,伪影XRAY失败

Date.Format正在输出非常奇怪的日期

GORM:一个表的两个外键

Go - 永远停止带有上下文的循环

在golang中以JSON格式获取xwwwformurlencoded请求的嵌套键值对

在 Windows 11 上运行 go mod tidy 时的 gitlab 权限问题

Golang 网络应用程序安全性:您是否应该判断输入是否为有效的 utf-8?

使用 os/exec 和在命令行执行之间的结果莫名其妙地不同

数据流中的无根单元错误,从 Golang 中的 PubSub 到 Bigquery

Golang并发写入多个文件

Golang - 客户 Unmarshaler/Marshaler 在指针上具有 nil/null 值

Go 泛型:自引用接口约束

在 docker kill --signal=SIGX 上以这种方式关闭容器内运行的 go 应用程序是否安全?

Golang 工作池实现意外工作

使用 `didip/tollbooth` 限制每小时最大请求数

实现接口的指针的泛型类型是什么?

为什么 Go 中的 maps.Keys() 将 map 类型指定为 M?

正则表达式处理数字签名的多个条目

有没有办法停止long blocking 函数?