【问题标题】:Why passing pointers to channel is slower为什么将指针传递给通道更慢
【发布时间】:2017-05-01 21:58:46
【问题描述】:

我是 golang 的新手,正在尝试用 golang 重写我的 java 服务器项目。

我发现,与传递值相比,将指针传递到通道会导致近 30% 的性能下降。

这是一个示例 sn-p: 包主 进口 ( “时间” “fmt” )

var c = make(chan t, 1024)
// var c = make(chan *t, 1024)
type t struct {
    a uint
    b uint
}

func main() {

    start := time.Now()
    for i := 0; i < 1000; i++ {
        b := t{a:3, b:5}
        // c <- &b
        c <- b
    }
    elapsed := time.Since(start)
    fmt.Println(t2)
}

更新。修复丢失的包

【问题讨论】:

  • 你是怎么计算的?何定义“性能下降”?我对值 4000、8000 和 30% 之间的关系有点困惑。
  • 问题不在于通道的性能,因为它是相同的,而应该是Why is the performance impacted when I get a pointer from a local variable?,Martin 举了一个很好的例子。

标签: pointers go channel


【解决方案1】:

作为good analysis of Martin Gallagher的补充,必须补充一点,你测量的方式是可疑的。此类小程序的性能差异很大,因此应反复进行测量。您的示例中也存在一些错误。

首先:它没有编译,因为缺少 package 语句。

第二:NanosecondsNanosecond有一个重要的区别

我试图以这种方式评估您的观察结果*

package main

import (
    "time"
    "fmt"
)

const (
    chan_size = 1000
    cycle_count = 1000
)

var (
    v_ch = make(chan t, chan_size)
    p_ch = make(chan *t, chan_size)
)

type t struct {
    a uint
    b uint
}

func fill_v() {
    for i := 0; i < chan_size; i++ {
        b := t{a:3, b:5}
        v_ch <- b
    }
}

func fill_p() {
    for i := 0; i < chan_size; i++ {
        b := t{a:3, b:5}
        p_ch <- &b
    }
}

func measure_f(f func()) int64 {
    start := time.Now()
    f();
    elapsed := time.Since(start)
    return elapsed.Nanoseconds()
}

func main() {

    var v_nanos int64 = 0
    var p_nanos int64 = 0
    for i := 0; i<cycle_count; i++ {
        v_nanos += measure_f(fill_v);
        for i := 0; i < chan_size; i++ {
            _ = <- v_ch
        }
    }
    for i := 0; i<cycle_count; i++ {
        p_nanos += measure_f(fill_p);
        for i := 0; i < chan_size; i++ {
            _ = <- p_ch
        }
    }
    fmt.Println(
        "v:",v_nanos/cycle_count, 
        " p:", p_nanos/cycle_count, 
        "ratio (v/p):", float64(v_nanos)/float64(p_nanos))
}

确实有可测量的性能下降(我这样定义下降drop=1-(candidate/optimum)),但虽然我重复代码 1000 次,但它在 25% 和 50% 之间变化,我不是甚至可以确定堆是如何回收的以及何时回收,所以它可能很难量化


*ideone上查看“正在运行”的演示

...注意标准输出被冻结:v: 34875 p: 59420 ratio (v/p)0.586923845267128

由于某种原因,无法运行this code in the Go Playground

【讨论】:

  • 关于 Go Playground 问题;那是因为time.Now() 总是返回2009-11-10 23:00:00 +0000 UTC
  • @MartinGallagher On the Go Playground 时间始终从2009-11-10 23:00:00 开始,但时间不会冻结,如果您稍后调用time.Now(),它会返回不同的值:time test
  • @icza 也许你需要为此睡觉?我的性能测试显然完全没有在 Go 中消耗时间。但实际上确实如此,您可以等待更长的时间,为 chan_sizecycle_count 设置更大的值
  • @icza;抱歉,我忘了添加上下文:play.golang.org/p/yi_2L6g0C3
  • @MartinGallagher 是的,游乐场使用“假计时”。它不会自行前进,睡眠只是通过时间“跳跃”来模拟。你可以在这里阅读更多信息:Inside the Go Playground: Faking time
【解决方案2】:

作为一个值,它可以被堆栈分配:

go run -gcflags '-m' tmp.go
# command-line-arguments
./tmp.go:18: inlining call to time.Time.Nanosecond
./tmp.go:24: inlining call to time.Time.Nanosecond
./tmp.go:25: t2 escapes to heap
./tmp.go:25: main ... argument does not escape
63613

作为指针,它逃到堆中:

go run -gcflags '-m' tmp.go
# command-line-arguments
./tmp.go:18: inlining call to time.Time.Nanosecond
./tmp.go:24: inlining call to time.Time.Nanosecond
./tmp.go:21: &b escapes to heap <-- Additional GC pressure
./tmp.go:20: moved to heap: b   <-- 
./tmp.go:25: t2 escapes to heap
./tmp.go:25: main ... argument does not escape
122513

转义到堆会引入一些开销/GC 压力。

看汇编,指针版还引入了额外的指令,包括:

go run -gcflags '-S' tmp.go
0x0055 00085 (...tmp.go:18) CALL    runtime.newobject(SB)

在调用runtime.chansend1 之前,非指针变体不会产生这种开销。

【讨论】:

  • 您希望每次通过操作会导致 30% 的性能下降还是固定值?
  • 在大多数情况下不会,但上面的代码是一个人为的例子,其中堆分配和附加汇编指令占程序执行的很大一部分。
  • 我希望堆操作比引用和取消引用指令更广泛,但既然您已经查看了代码,您可能会更了解...
  • 很好的分析,希望这个答案会被接受。它展示了 go 工具 go run -gcflags '-m'go run -gcflags '-S 的一些非常有趣的应用
猜你喜欢
  • 2020-09-14
  • 1970-01-01
  • 2019-09-16
  • 2020-11-16
  • 1970-01-01
  • 2016-06-02
  • 2016-09-25
  • 2012-01-24
  • 1970-01-01
相关资源
最近更新 更多