最近在考虑优化程序的执行时间时,考虑过一个问题,就是,如果有一个并发处理的程序,每次调用时,都需要做一部分预处理,比如,发送http请求时,要先组装request,那么每一次都组装好了再发请求和通过channel预处理request,发送是从channel里面获取会不会性能有很大提高呢?
为此,我做了一个小程序检验,代码如下:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
count, concurrcy := 10, 1
costs := make([]time.Duration, count)
cost1s := make([]time.Duration, count)
for index := 0; index < 10; index++ {
// directly
var wg sync.WaitGroup
wg.Add(concurrcy)
now := time.Now()
for index := 0; index < concurrcy; index++ {
go func() {
directlyGet()
doAnother()
wg.Done()
}()
}
wg.Wait()
costs[index] = time.Since(now)
// channel
var wg1 sync.WaitGroup
wg1.Add(concurrcy)
now1 := time.Now()
for index := 0; index < concurrcy; index++ {
go func() {
<-channelGet()
doAnother()
wg1.Done()
}()
}
wg1.Wait()
cost1s[index] = time.Since(now1)
}
var cost, cost1 float64
for index := 0; index < count; index++ {
cost += costs[index].Seconds()
cost1 += cost1s[index].Seconds()
}
fmt.Println("cost(avg, ms):", cost*1000/float64(count))
fmt.Println("cost1(avg, ms):", cost1*1000/float64(count))
fmt.Println("((cost1-cost)/cost)(%):", ((cost1 - cost) / cost))
}
func directlyGet() string {
doOther()
return "result"
}
func doOther() {
time.Sleep(20 * time.Millisecond)
}
func doAnother() {
time.Sleep(20 * time.Millisecond)
}
func channelGet() <-chan string {
stream := make(chan string)
go func() {
defer close(stream)
doOther()
stream <- "result"
}()
return stream
}
direcltyGet是每次使用时,都要doOther完成,然后才能doAnother;而channelGet则是将doOther和doAnother并发处理,简单来说就是当在doAnother的时候,另一个goroutine已经在doOther了。那这个结果是怎么样的呢?这个程序现在主要影响的参数有2,1是concurrcy-并发量,而是doOther:doAnother,即预处理部分相对于后面的处理所占的比例。count是用来消除单次执行的影响,求取花费时间平均值。
经过几次调整后的结果列入下表(单位:ms):
并发量count | 消耗比 doOther:doAnother | 平均线性处理 cost | 平均预处理cost1 | 消耗比1 cost1:cost |
---|---|---|---|---|
1 | 1:0.001 | 20.863898799999998 | 21.0200263 | 0.74831411663098596 |
1 | 1:1 | 41.1631734 | 41.448019099999996 | 0.6919915946033516 |
1 | 1:50 | 1022.5822461 | 1022.6867288000001 | 0.01021753510767501 |
50 | 1:0.001 | 21.314165699999997 | 21.501593299999996 | 0.8793569621165047 |
50 | 1:1 | 42.6392575 | 43.419844499999996 | 1.8306768123248847 |
50 | 1:50 | 1022.7240038 | 1023.0811119 | 0.03491734805022551 |
5000 | 1:0.001 | 23.6074675 | 27.948086 | 18.38663338200085 |
5000 | 1:1 | 47.451019800000005 | 51.9780305 | 9.540386527161635 |
5000 | 1:50 | 1026.7172483 | 1028.0245165000001 | 0.12732504515382329 |
在doOther固定20ms消耗的情况下(更大消耗无明显影响,因为消耗时间是按比例计算),经过channel预处理之后所花费时间无明显减少(比例为负数),反而在大并发量情况下还有所增加,猜测是由于放大goroutine之间的切换调度,即阻塞与唤醒等。因此,在无必要情况下,如将输入转化成流形式,或者有并发共享内存等的影响,可不必刻意追求将输入转化为channel流 。