为什么并行化会使我的程序变慢?

时间:2020-03-01 23:52:02

标签: go concurrency

我有两个程序。他们解决了线性方程组。他们两个都正常工作(它们产生相同的结果)。

第一个程序无需并发即可工作。

第二个程序与第一个程序非常相似,除了我在某些地方添加了并行性。这些地方用代码标记。

这是两个程序:

第一个。没有并发。

package main

import (
        "fmt"
        "math"
        "os"
        "time"
)

func main() {
        start := time.Now()
        N := 1000
        a := CreateRandomMatrix(N)
        b := CreateRandomVector(N)

        index := make([]int, len(a))
        for i := range index {
            index[i] = i
        }

        for i := 0; i < len(a); i++ {

            r := a[i][index[i]]

            var kk int

            var maxElemInRow float64
            for k := i; k < len(a); k++ {
                if math.Abs(a[i][index[k]]) > maxElemInRow {
                    kk = k
                    maxElemInRow = math.Abs(a[i][index[k]])
                }
            }

            index[i], index[kk] = index[kk], index[i]

            r = a[i][index[i]]

            if r == 0 {
                if b[i] == 0 {
                    fmt.Println("a lot of solutions")
                } else {
                    fmt.Println("no solutions")
                }
                os.Exit(1)
            }

            for j := 0; j < len(a[i]); j++ {
                a[i][index[j]] /= r
            }
            b[i] /= r

            for k := i + 1; k < len(a); k++ {
                r = a[k][index[i]]
                for j := 0; j < len(a[i]); j++ {
                    a[k][index[j]] = a[k][index[j]] - a[i][index[j]]*r
                }
                b[k] = b[k] - b[i]*r
            }

        }

        var x vector = make(vector, len(b))

        for i := len(a) - 1; i >= 0; i-- {
            x[i] = b[i]

            for j := i + 1; j < len(a); j++ {
                x[i] = x[i] - (x[j] * a[i][index[j]])
            }
        }

        result := make([]string, len(x))
        for i, val := range index {
            result[val] = fmt.Sprintf("%.2f", x[i])
        }
        fmt.Println("tested part took:", time.Now().Sub(start))
    }

第二个:

package main

import (
    "fmt"
    "math"
    "os"
    "sync"
    "time"
)

const (
    workers = 8
)

var wg sync.WaitGroup

func main() {

    start := time.Now()
    N := 1000
    a := CreateRandomMatrix(N)
    b := CreateRandomVector(N)

    index := make([]int, len(a))
    for i := range index {
        index[i] = i
    }

    for i := 0; i < len(a); i++ {

        r := a[i][index[i]]

        var kk int
        var max float64

        for k := i; k < len(a); k++ {
            if math.Abs(a[i][index[k]]) > max {
                kk = k
                max = math.Abs(a[i][index[k]])
            }
        }

        index[i], index[kk] = index[kk], index[i]

        r = a[i][index[i]]

        if r == 0 {
            if b[i] == 0 {
                fmt.Println("a lot of solutions")
            } else {
                fmt.Println("no solutions")
            }
            os.Exit(1)
        }

        // concurrency here
        for w := 0; w < workers; w++ {
            wg.Add(1)
            go func(w int) {
                start := len(a[i]) / workers * w
                end := len(a[i]) / workers * (w + 1)

                if end > len(a[i]) {
                    end = len(a[i])
                }

                for j := start; j < end; j++ {
                    a[i][index[j]] /= r
                }
                wg.Done()
            }(w)
        }

        b[i] /= r
        wg.Wait()

        for k := i + 1; k < len(a); k++ {
            r = a[k][index[i]]
            for j := 0; j < len(a[i]); j++ {
                a[k][index[j]] = a[k][index[j]] - a[i][index[j]]*r
            }
            b[k] = b[k] - b[i]*r
        }

    }

    var x vector = make(vector, len(b))

    for i := len(a) - 1; i >= 0; i-- {
        x[i] = b[i]

        for j := i + 1; j < len(a); j++ {
            x[i] = x[i] - (x[j] * a[i][index[j]])
        }
    }

    result := make([]string, len(x))
    for i, val := range index {
        result[val] = fmt.Sprintf("%.2f", x[i])
    }
    fmt.Println("tested part took:", time.Now().Sub(start))
}

两个程序的附加代码块相同

package main

import "math/rand"

type matrix [][]float64
type vector []float64

func CreateRandomMatrix(n int) matrix {
    m := make(matrix, n)
    for i := 0; i < n; i++ {
        m[i] = make(vector, n)
        for j := 0; j < n; j++ {
            m[i][j] = float64(rand.Intn(100))
        }
    }
    return m
}

func CreateRandomVector(n int) vector {
    v := make(vector, n)
    for i := 0; i < n; i++ {
        v[i] = float64(rand.Intn(100))
    }
    return v
}

如此。问题出在这里:

理论上,第二个程序应该运行得更快,因为一些计算分布在处理器内核中。但这不会发生。每次添加并行性元素,第二个程序就会开始变慢。

我测试了N的大值和小值。该程序第二个版本的运行时间明显落后于第一个版本。例如,如果您将N设置为3500,则执行的时间差约为10秒。

此外,如果将工作程序数设置为1,则第二个程序开始运行更快。

为什么会发生?我在某个地方犯错了吗?如何使分布式计算加快程序速度?

转到版本: 1.14。但我也在版本1.13上检查了此代码。

ADD::我发现,如果为程序提供较大的矩阵大小,则并行版本开始赶上顺序版本。

编辑摘要:在第二个程序中,在计算kkmax的地方删除了具有并行性的段,以摆脱数据竞争。 >

2 个答案:

答案 0 :(得分:0)

您在这里为很少的工作设置了很多复杂性:

    for j := 0; j < len(a[i]); j++ {
        a[i][index[j]] /= r
    }

这需要N除法,因此在您的示例中约为1000。加上一些簿记。

您将其替换为:

            start := len(a[i]) / workers * w
            end := len(a[i]) / workers * (w + 1)

            if end > len(a[i]) {
                end = len(a[i])
            }

            for j := start; j < end; j++ {
                a[i][index[j]] /= r
            }

每行执行125个除法(1000/8),再加上2个额外的除法加上两个额外的乘法,再加上一个额外的加法和一个额外的减法(这就是>的方式)。其他簿记相同。因此,在开始之前,大约有3%的计算开销。在并发工作中这是正常的。我只是提醒您,并发工作总是始于必须从中挖掘出来的一个漏洞。

您将在每行中添加8个goroutine创建和16个WaitGroup操作(加上一个Wait)。那就是创建和销毁1000个goroutine。 Goroutine很便宜,但并不便宜。在运行一千次的循环中,它们绝对不便宜。

根据我的测量,这是为了加快一段代码,即您的循环的0.05%。您正在基于整个运行检查时间(包括首先创建矩阵)。但是,当我测试主外部循环(N = 1000)时,大约是20ms(串行)。除法环路约为10µs。除法循环并不是跨CPU分布的有趣部分。一个仅存在10µs的goroutine不能很好地利用资源。

您提到“在执行此操作之前,我使用C#代码打开了相应的文献,其中提供了并行化此程序的建议,并且结果表明速度显着提高。”在C#中看起来也不是那么快。如果您每行创建8个任务来进行划分,那么我希望它与Go中的问题相同。

这里可能还存在内存位置问题。从内存中分块地调出内存并按顺序对其进行操作要比在内存区域之间跳转,使CPU缓存无效并使缓存行的一部分浪费更为有效。 (但是我怀疑这会被产生和销毁8,000个goroutine的成本所淹没。)

我实际上对并发代码的速度感到惊讶。

您要做的是构造算法,而不是挑选单个的小比特跨内核并行运行,因此您可以将大量工作分配给少数工作人员,而不是将大量工作分配给很多工作人员工人。

答案 1 :(得分:0)

因此,感谢所有帮助我并向我解释了我的错的人。我看了一下代码,发现一个可以并行化计算进行优化的地方,由于这种方法在这个地方的有效性,因此速度得到了真正的提高。

在之前尝试进行并行化的所有时刻,我都返回了初始顺序状态。

我使用并行方法替换的代码段带有注释。

以下是代码:

package main

import (
    "fmt"
    "math"
    "os"
    "sync"
    "time"
)

const (
    workers = 8
)

var wg sync.WaitGroup

func main() {
    N := 3000

    a := CreateRandomMatrix(N)
    b := CreateRandomVector(N)
    start := time.Now()

    index := make([]int, len(a))
    for i := range index {
        index[i] = i
    }

    for i := 0; i < len(a); i++ {

        r := a[i][index[i]]

        var kk int
        var max float64

        for k := i; k < len(a); k++ {
            if math.Abs(a[i][index[k]]) > max {
                kk = k
                max = math.Abs(a[i][index[k]])
            }
        }

        index[i], index[kk] = index[kk], index[i]

        r = a[i][index[i]]

        if r == 0 {
            if b[i] == 0 {
                fmt.Println("a lot of solutions")
            } else {
                fmt.Println("no solutions")
            }
            os.Exit(1)
        }

        for j := 0; j < len(a[i]); j++ {
            a[i][index[j]] /= r
        }
        b[i] /= r

        //concurrency
        chunk := len(a) / workers
        for w := 0; w < workers; w++ {
            wg.Add(1)
            go func(start int) {
                end := start + chunk
                if end > len(a) {
                    end = len(a)
                }
                for k := start; k < end; k++ {
                    r := a[k][index[i]]

                    for j := 0; j < len(a[i]); j++ {
                        a[k][index[j]] = a[k][index[j]] - a[i][index[j]]*r
                    }
                    b[k] = b[k] - b[i]*r
                }
                wg.Done()
            }(w*chunk + i + 1)

        }
        wg.Wait()

    }

    var x vector = make(vector, len(b))

    for i := len(a) - 1; i >= 0; i-- {
        x[i] = b[i]

        for j := i + 1; j < len(a); j++ {
            x[i] = x[i] - (x[j] * a[i][index[j]])
        }
    }

    result := make([]string, len(x))
    for i, val := range index {
        result[val] = fmt.Sprintf("%.2f", x[i])
    }
    fmt.Println(time.Since(start))
}

具有并发性的程序的矩阵大小为N = 3000,它在 19秒内计算线性方程组,而普通版本花费 43秒! !