Concurrency 不同输入数据的Goroutine执行时间

Concurrency 不同输入数据的Goroutine执行时间,concurrency,go,goroutine,Concurrency,Go,Goroutine,我正在用goroutine进行一些并行计算的实验。然而,goroutine的执行时间让我困惑。我的实验装置很简单 runtime.GOMAXPROCS(3) datalen := 1000000000 data21 := make([]float64, datalen) data22 := make([]float64, datalen) data23 := make([]float64, datalen) t := time.Now() res := make(chan interface

我正在用goroutine进行一些并行计算的实验。然而,goroutine的执行时间让我困惑。我的实验装置很简单

runtime.GOMAXPROCS(3)

datalen := 1000000000
data21 := make([]float64, datalen)
data22 := make([]float64, datalen)
data23 := make([]float64, datalen)

t := time.Now()
res := make(chan interface{}, dlen)

go func() {
    for i := 0; i < datalen; i++ {
        data22[i] = math.Sqrt(13)
    }
    res <- true
}()

go func() {
    for i := 0; i < datalen; i++ {
        data22[i] = math.Sqrt(13)
    }
    res <- true
}()

go func() {
    for i := 0; i < datalen; i++ {
        data22[i] = math.Sqrt(13)
    }
    res <- true
}()

for i:=0; i<3; i++ {
    <-res
}
fmt.Printf("The parallel for loop took %v to run.\n", time.Since(t))
但是,如果我让每个goroutine按如下方式处理不同的数据:

runtime.GOMAXPROCS(3)

datalen := 1000000000
data21 := make([]float64, datalen)
data22 := make([]float64, datalen)
data23 := make([]float64, datalen)

t := time.Now()
res := make(chan interface{}, dlen)

go func() {
    for i := 0; i < datalen; i++ {
        data21[i] = math.Sqrt(13)
    }
    res <- true
}()

go func() {
    for i := 0; i < datalen; i++ {
        data22[i] = math.Sqrt(13)
    }
    res <- true
}()

go func() {
    for i := 0; i < datalen; i++ {
        data23[i] = math.Sqrt(13)
    }
    res <- true
}()

for i:=0; i<3; i++ {
    <-res
}
fmt.Printf("The parallel for loop took %v to run.\n", time.Since(t))

我想也许我用错了goroutine。那么,使用多个goroutine处理不同数据段的正确方法应该是什么

由于示例程序没有执行任何实质性的计算,因此瓶颈将是数据写入内存的速度。通过本例中的设置,我们讨论的是22 GB的写入,这并非无关紧要

考虑到两个示例运行时的时间差异,一种可能的情况是它实际上没有向RAM写入那么多内容。考虑到内存写入由CPU缓存,执行过程可能如下所示:

  • 第一个goroutine将数据写入表示
    data22
    数组开始的缓存线
  • 第二个goroutine将数据写入表示相同位置的缓存线。运行第一个goroutine的CPU注意到写操作会使其缓存的写操作无效,因此会丢弃其更改
  • 第三个goroutine将数据写入表示相同位置的缓存线。运行第二个goroutine的CPU注意到写操作会使其缓存的写操作无效,因此会丢弃其更改
  • 第三个CPU中的缓存线被逐出,更改被写入RAM

  • 当goroutines通过
    data22
    数组时,此过程将继续。由于RAM是瓶颈,在这种情况下,我们最终会写入三分之一的数据,因此它的运行速度大约是第二种情况的3倍也就不足为奇了。

    您正在使用大量内存。在第一个示例中100000000*8=8GB,在第二个示例中为3*100000000*8=24GB。在第二个示例中,您可能使用了大量的交换空间。磁盘I/O非常非常慢,即使在SSD上也是如此


    将datalen:=100000000更改为datalen:=100000000,减少10倍。你现在的跑步时间是多少?平均每个示例至少运行三次。你的电脑有多少内存?您使用的是SSD吗?

    您的第一个代码很快,因此是一个格式错误的程序,即没有意义的程序。您不能对格式错误的程序进行推理。而且,正如James所指出的:您“测量”的只是内存带宽和缓存未命中率。还有一点:goroutines是关于并发的;这种并发性可能意味着并行性(以及更快的执行)是一个不错的副产品,但不是goroutines的主要用途。但重点是关于第二个问题。为什么我慢了三倍?我也理解并行性是并发的副产品。非常详细的解释。第二个程序的执行时间与在data21、data22和data23上的顺序执行时间相同/稍差。为什么?数据和计算可以分别在多核上运行,不是吗?你有没有一个基准来证明这一点很重要?@peterSO:我运行了多次,发现当我的数据很大时,顺序版本和并发版本的执行时间几乎相同。有时并发版本甚至更糟。我们怎么解释呢?@YiqunHu:正如我所说,这个程序的瓶颈是内存。在进行如此少的计算的情况下,单个内核可以最大化RAM带宽,这并不奇怪。我有16G内存和SSD。磁盘I/O是一个很好的观点。当我减小数据的大小时,程序的顺序版本和并发版本的执行时间更为合理。@YiqunHu:很明显,交换磁盘I/O导致了几乎所有的性能差异。虽然可能存在其他性能问题,但相比之下,这些问题很小。并发版本的运行速度与顺序版本一样慢。这是否意味着交换是瓶颈?
    runtime.GOMAXPROCS(3)
    
    datalen := 1000000000
    data21 := make([]float64, datalen)
    data22 := make([]float64, datalen)
    data23 := make([]float64, datalen)
    
    t := time.Now()
    res := make(chan interface{}, dlen)
    
    go func() {
        for i := 0; i < datalen; i++ {
            data21[i] = math.Sqrt(13)
        }
        res <- true
    }()
    
    go func() {
        for i := 0; i < datalen; i++ {
            data22[i] = math.Sqrt(13)
        }
        res <- true
    }()
    
    go func() {
        for i := 0; i < datalen; i++ {
            data23[i] = math.Sqrt(13)
        }
        res <- true
    }()
    
    for i:=0; i<3; i++ {
        <-res
    }
    fmt.Printf("The parallel for loop took %v to run.\n", time.Since(t))
    
    The parallel for loop took 20.744438468s to run.