zl程序教程

您现在的位置是:首页 >  Java

当前栏目

使用channel流提前预处理部分信息,和普通的线性处理会有巨大的差别吗

2023-02-18 16:44:44 时间

研究课题

最近在考虑优化程序的执行时间时,考虑过一个问题,就是,如果有一个并发处理的程序,每次调用时,都需要做一部分预处理,比如,发送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流