Performance 围棋的加速问题

Performance 围棋的加速问题,performance,parallel-processing,go,Performance,Parallel Processing,Go,我在go中编写了一个非常简单的程序来测试并行程序的性能。我写了一个非常简单的程序,通过除法试验分解一个大的半素数。由于不涉及任何通信,我希望能有一个近乎完美的加速。然而,该计划的规模似乎非常糟糕 我使用systemtime命令,在一台8核(real,而非HT)计算机上运行1、2、4和8个进程,对程序进行计时。我分解的数字是“28808539627864609”。以下是我的结果: cores time (sec) speedup 1 60.0153 1 2 47.358

我在go中编写了一个非常简单的程序来测试并行程序的性能。我写了一个非常简单的程序,通过除法试验分解一个大的半素数。由于不涉及任何通信,我希望能有一个近乎完美的加速。然而,该计划的规模似乎非常糟糕

我使用system
time
命令,在一台8核(real,而非HT)计算机上运行1、2、4和8个进程,对程序进行计时。我分解的数字是“28808539627864609”。以下是我的结果:

cores time (sec) speedup 1 60.0153 1 2 47.358 1.27 4 34.459 1.75 8 28.686 2.10 核心时间(秒)加速比 1 60.0153 1 2 47.358 1.27 4 34.459 1.75 8 28.686 2.10 如何解释如此糟糕的加速?这是我的程序中的错误,还是go运行时的问题?我怎样才能获得更好的表现?我不是在谈论算法本身(我知道有更好的算法可以分解半素数),而是在谈论我并行化它的方式

以下是我的程序的源代码:

package main

import (
    "big"
    "flag"
    "fmt"
    "runtime"
)

func factorize(n *big.Int, start int, step int, c chan *big.Int) {

    var m big.Int
    i := big.NewInt(int64(start))
    s := big.NewInt(int64(step))
    z := big.NewInt(0)

    for {
        m.Mod(n, i)
        if m.Cmp(z) == 0{
            c <- i
        }
        i.Add(i, s)
    }
}

func main() {
    var np *int = flag.Int("n", 1, "Number of processes")
    flag.Parse()

    runtime.GOMAXPROCS(*np)

    var n big.Int
    n.SetString(flag.Arg(0), 10) // Uses number given on command line
    c := make(chan *big.Int)
    for i:=0; i<*np; i++ {
        go factorize(&n, 2+i, *np, c)
    }
    fmt.Println(<-c)
}
主程序包
进口(
“大”
“旗帜”
“fmt”
“运行时”
)
func分解(n*big.Int,start Int,step Int,c chan*big.Int){
var m big.Int
i:=big.NewInt(int64(start))
s:=big.NewInt(int64(步长))
z:=big.NewInt(0)
为了{
m、 Mod(n,i)
如果m.Cmp(z)=0{

c我不读
go
,所以这可能是对一个问题的答案,而这个问题不是你所问的。如果是这样的话,请根据你的意愿进行否决或删除

如果你要绘制一个“分解整数n的时间”与“n”的曲线图,你会得到一个随机上升和下降的曲线图。对于你选择的任何n,都会有一个在1..n范围内的整数,在一个处理器上分解时间最长。如果你的并行化策略是将n个整数分布在p个处理器上,那么e处理器将至少花时间分解最难的整数,然后再分解其负载的其余部分


也许您也做过类似的事情?

Big.Int方法通常必须分配内存,通常用来保存计算结果。问题是只有一个堆,所有内存操作都是序列化的。在这个程序中,数字非常小,并且(可并行化)与重复分配所有微小内存位的不可并行操作相比,Mod和Add等操作所需的计算时间很短

就加快速度而言,有一个明显的答案:如果不必使用大整数,就不要使用大整数。您的示例数恰好适合64位。但是,如果您计划使用真正的大整数,问题将自行解决。您将花费更多的时间进行计算,并且在堆中花费的时间将与更不用说了


顺便说一句,您的程序中有一个bug,尽管它与性能无关。当您在通道上找到一个因子并返回结果时,您会发送一个指向局部变量i的指针。这很好,只是您当时没有跳出循环。goroutine中的循环继续递增i,直到主goroutine出现时t将指针从通道中取出并跟随它,值几乎肯定是错误的。

通过通道发送
i
后,
i
应替换为新分配的
big.Int

if m.Cmp(z) == 0 {
    c <- i
    i = new(big.Int).Set(i)
}

多核加速比小的另一个原因是内存分配。该函数在内部使用,每次调用时都会创建一个新的
big.Int
。要避免内存分配,请直接使用
QuoRem

func factorize(n *big.Int, start int, step int, c chan *big.Int) {
    var q, r big.Int
    i := big.NewInt(int64(start))
    s := big.NewInt(int64(step))
    z := big.NewInt(0)

    for {
        q.QuoRem(n, i, &r)
        if r.Cmp(z) == 0 {
            c <- i
            i = new(big.Int).Set(i)
        }
        i.Add(i, s)
    }
}
func分解(n*big.Int,start Int,step Int,c chan*big.Int){
var q,r big.Int
i:=big.NewInt(int64(start))
s:=big.NewInt(int64(步长))
z:=big.NewInt(0)
为了{
q、 QuoRem(n、i和r)
如果r.Cmp(z)=0{
c=2
总的来说可能比使用
n=1运行程序消耗更多的CPU时间


为了了解要做多少额外工作,您可能希望(以某种方式)在程序退出函数
main()时打印出所有goroutine中所有
i
的值

不完全一样。对于给定的半素数
n
,有两个因子,
p
q
。在一个处理器上,我会尝试将
n
除以a,a从2到
p
。在
n
处理器上,我将以
a
,=2,
a
=3,
a开始每个进程=
2+N
,然后按
N
的步骤递增
a
。然后在其中一个进程上达到
p
应该快
N
倍。在Go实现中,“所有内存(分配)操作都是序列化的”不是真的.你有关于Go中串行内存分配的参考资料吗?这可能有道理,但我想看看事实。有相关的评论。因此,我站出来更正。有一个堆,但goroutines看起来可以一次获得一堆内存,然后从中重复进行小的分配。不管怎样,分配显然是个问题我在你的程序中,正如Atom正确识别的那样,特别是在Mod方法中分配。我尝试了他建议的用QuoRem替换Mod的改变,你的程序不仅可以很好地扩展内核,而且运行得更快。(就是说,在最新的一周。)你有关于你提到的运行时调度程序错误的参考资料吗?我没有。我的判断是基于我在电脑上观察到的情况。
func factorize(n *big.Int, start int, step int, c chan *big.Int) {
    var q, r big.Int
    i := big.NewInt(int64(start))
    s := big.NewInt(int64(step))
    z := big.NewInt(0)

    for {
        q.QuoRem(n, i, &r)
        if r.Cmp(z) == 0 {
            c <- i
            i = new(big.Int).Set(i)
        }
        i.Add(i, s)
    }
}