optimization - 函数调用导致性能下降

标签 optimization go slice

对于以下函数:

func CycleClock(c *ballclock.Clock) int {
    for i := 0; i < fiveMinutesPerDay; i++ {
        c.TickFive()
    }

    return 1 + CalculateBallCycle(append([]int{}, c.BallQueue...))
}

其中 c.BallQueue 定义为 []intCalculateBallCycle 定义为 func CalculateBallCycle(s []int)整数for 循环和 return 语句之间的性能大幅下降。

我写了以下基准测试。第一个基准测试整个函数,第二个基准测试 for 循环,而第三个基准测试 CalculateBallCycle 函数:

func BenchmarkCycleClock(b *testing.B) {
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ {
        j := i
        b.Run("BallCount="+strconv.Itoa(i), func(b *testing.B) {
            for n := 0; n < b.N; n++ {
                c, _ := ballclock.NewClock(j)

                CycleClock(c)
            }
        })
    }
}

func BenchmarkCycle24(b *testing.B) {
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ {
        j := i
        b.Run("BallCount="+strconv.Itoa(i), func(b *testing.B) {
            for n := 0; n < b.N; n++ {
                c, _ := ballclock.NewClock(j)

                for k := 0; k < fiveMinutesPerDay; k++ {
                    c.TickFive()
                }
            }
        })
    }
}

func BenchmarkCalculateBallCycle123(b *testing.B) {
    m := []int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16}

    for n := 0; n < b.N; n++ {
        CalculateBallCycle(m)
    }
}

使用 123 个球,得到以下结果:

BenchmarkCycleClock/BallCount=123-8                  200           9254136 ns/op
BenchmarkCycle24/BallCount=123-8                  200000              7610 ns/op
BenchmarkCalculateBallCycle123-8                 3000000               456 ns/op

从这个角度来看,基准之间存在巨大差异。我希望第一个基准测试大约需要 ~8000 ns/op,因为那将是各部分的总和。

Here 是 github 存储库。

编辑:

我发现基准测试的结果和运行程序的结果大不相同。我采用了@yazgazan 发现的内容并修改了 main.go 中的基准函数,有点模仿 main_test.go 中的 BenchmarkCalculateBallCycle123:

func Benchmark() {
    for i := ballclock.MinBalls; i <= ballclock.MaxBalls; i++ {
        if i != 123 {
            continue
        }

        start := time.Now()

        t := CalculateBallCycle([]int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16})

        duration := time.Since(start)

        fmt.Printf("Ballclock with %v balls took %s;\n", i, duration)
    }
}

这给出了输出:

Ballclock with 123 balls took 11.86748ms;

如您所见,总时间为 11.86 毫秒,所有时间都花在了 CalculateBallCycle 函数中。什么会导致基准测试以 456 ns/op 运行,而正在运行的程序以 11867480 ms/op 左右运行?

最佳答案

您写道 CalcualteBallCycle() 通过设计修改 slice 。

我不能说这种方法的正确性,但这就是为什么 BenchmarkCalculateBallCycle123 的基准时间如此不同的原因。

在第一次运行时它做了预期的事情,但在随后的运行中它做了完全不同的事情,因为您传递的是不同的数据作为输入。

对修改后的代码进行基准测试:

func BenchmarkCalculateBallCycle123v2(b *testing.B) {
    m := []int{8, 62, 42, 87, 108, 35, 17, 6, 22, 75, 116, 112, 39, 119, 52, 60, 30, 88, 56, 36, 38, 26, 51, 31, 55, 120, 33, 99, 111, 24, 45, 21, 23, 34, 43, 41, 67, 65, 66, 85, 82, 89, 9, 25, 109, 47, 40, 0, 83, 46, 73, 13, 12, 63, 15, 90, 121, 2, 69, 53, 28, 72, 97, 3, 4, 94, 106, 61, 96, 18, 80, 74, 44, 84, 107, 98, 93, 103, 5, 91, 32, 76, 20, 68, 81, 95, 29, 27, 86, 104, 7, 64, 113, 78, 105, 58, 118, 117, 50, 70, 10, 101, 110, 19, 1, 115, 102, 71, 79, 57, 77, 122, 48, 114, 54, 37, 59, 49, 100, 11, 14, 92, 16}
    for n := 0; n < b.N; n++ {
        tmp := append([]int{}, m...)
        CalculateBallCycle(tmp)
    }
}

这通过制作 m 的副本来解决此行为,以便 CalculateBallCycle 修改本地副本。

运行时间变得更像其他:

BenchmarkCalculateBallCycle123-8         3000000           500 ns/op
BenchmarkCalculateBallCycle123v2-8           100      10483347 ns/op

关于optimization - 函数调用导致性能下降,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45147353/

相关文章:

WCF - 在您的服务中抛出 FaultExceptions 的开销

mysql - 全选在 golaong gorm 中不起作用

go - 如何将特定字符串转换为键和值的 “=”并将其转换为golang中的json

java - 数组访问优化

algorithm - 在 MATLAB 中使用优化工具箱使用遗传算法求解多目标函数

api - 如何从 HTTP 处理程序中的 URL 获取参数

emacs - 在 Emacs Lisp 中逐步迭代列表

c++ - 2D vector 的子 vector

python - 通常只切片列表/元组的一个元素?

python - 为什么 python 中的空函数调用对于动态编译的 python 代码要慢 15% 左右