【问题标题】:Why this Go program is so slow?为什么这个 Go 程序这么慢?
【发布时间】:2019-10-15 11:09:21
【问题描述】:

我刚刚阅读了一些 Go 的简短教程并编写了一个简单的程序筛。 Sieve 使用 sieve 算法打印所有小于 10000 的素数,从而创建了很多 go 例程。我得到了正确的结果,但程序很慢(在我的机器上 5 秒)。我还编写了 lua 脚本和 python 脚本,它们实现了相同的算法,并且运行速度更快(在我的机器上都大约 1 秒)。

请注意,目的是了解 goroutine 的性能与其他语言中的协程相比,例如 lua。实施效率非常低,一些 cmets 指出实施埃拉托色尼筛法的方法不正确。是的,这是故意的。其他一些回复指出,缓慢是由打印 I/O 引起的。所以我注释掉了打印行。

我的问题是为什么我用 Go 实现的筛子程序这么慢? 代码如下:

package main

import (
  "fmt"
  "sync"
)


type Sieve struct {
  id int;
  msg_queue chan int;
  wg *sync.WaitGroup;
}


func NewSieve(id int) *Sieve {
  sieve := new(Sieve)
  sieve.id = id
  sieve.msg_queue = make(chan int)
  sieve.wg = new(sync.WaitGroup)
  sieve.wg.Add(1)
  return sieve
}


func (sieve *Sieve) run() {
  defer sieve.wg.Done()

  myprime := <-sieve.msg_queue
  if myprime == 0 {
    return
  }
  // fmt.Printf("Sieve (%d) is for prime number %d.\n", sieve.id, myprime)

  next_sieve := NewSieve(sieve.id + 1)
  go next_sieve.run()
  for {
    number := <-sieve.msg_queue
    if number == 0 {
      next_sieve.msg_queue <- number;
      next_sieve.wg.Wait()
      return
    } else if number % myprime != 0 {
      // fmt.Printf("id: %d, number: %d, myprime: %d, number mod myprime: %d\n", sieve.id, number, myprime, number % myprime)
      next_sieve.msg_queue <- number
    }
  }
}


func driver() {
  first := NewSieve(2)
  go first.run()
  for n := 2; n <= 10000; n++ {
    first.msg_queue <- n
  }
  first.msg_queue <- 0
  first.wg.Wait()
}


func main() {
  driver()
}

作为对比,这里是sieve.lua的代码

function sieve(id)
  local myprime = coroutine.yield()
  // print(string.format("Sieve (%d) is for prime number %d", id, myprime))

  local next_sieve = coroutine.create(sieve)
  coroutine.resume(next_sieve, id + 1)

  while true do
    local number = coroutine.yield()
    if number % myprime ~= 0 then
      // print(string.format("id: %d, number: %d, myprime: %d, number mod myprime: %d", id, number, myprime, number % myprime))
      coroutine.resume(next_sieve, number)
    end
  end
end


function driver()
  local first = coroutine.create(sieve)
  coroutine.resume(first, 2)
  local n
  for n = 2, 10000 do
    coroutine.resume(first, n)
  end
end

driver()

【问题讨论】:

  • 顺便说一句,this is not the Sieve of Eratosthenes。它通常被误认为是埃拉托色尼筛法,但它实际上是试除法,并且是一种特别低效的试除法形式,因为即使它通过了数字的平方根,它也会继续测试除数。
  • 如果您删除 fmt.Print 语句,您的代码几乎会立即运行。
  • 感谢您指出此实现的效率问题。但这是故意的。我想看的是 goroutine 与其他语言的协程相比的表现。
  • 我会稍微编辑一下这个问题。谢谢大家。

标签: go lua goroutine


【解决方案1】:

无意义的微基准会产生无意义的结果。

您正在计时打印 I/O。

您会因少量工作而产生常规和渠道开销。


这是一个 Go 中的素数筛程序。

输出:

$ go version
go version devel +46be01f4e0 Sun Oct 13 01:48:30 2019 +0000 linux/amd64
$ go build sumprimes.go && time ./sumprimes
5736396
29.96µs   
real    0m0.001s
user    0m0.001s
sys     0m0.000s

sumprimes.go:

package main

import (
    "fmt"
    "time"
)

const (
    prime    = 0x00
    notprime = 0xFF
)

func oddPrimes(n uint64) (sieve []uint8) {
    sieve = make([]uint8, (n+1)/2)
    sieve[0] = notprime
    p := uint64(3)
    for i := p * p; i <= n; i = p * p {
        for j := i; j <= n; j += 2 * p {
            sieve[j/2] = notprime
        }
        for p += 2; sieve[p/2] == notprime; p += 2 {
        }
    }
    return sieve
}

func sumPrimes(n uint64) uint64 {
    sum := uint64(0)
    if n >= 2 {
        sum += 2
    }
    for i, p := range oddPrimes(n) {
        if p == prime {
            sum += 2*uint64(i) + 1
        }
    }
    return sum
}

func main() {
    start := time.Now()

    var n uint64 = 10000
    sum := sumPrimes(n)
    fmt.Println(sum)

    fmt.Println(time.Since(start))
}

【讨论】:

  • 感谢您指出大多数缓慢是由打印 I/O 引起的。我按时间运行程序 ./sieve > log。问题是 lua 脚本也有打印 I/O。这是否意味着 lua 的打印 I/O 更快?
  • 程序故意使用goroutine来模拟实体之间的消息交换。目的是测试goroutine的性能,对比其他语言的协程。
【解决方案2】:

大部分时间都花在fmt.Printf

取出线:

fmt.Printf("id: %d, number: %d, myprime: %d, number mod myprime: %d\n", sieve.id, number, myprime, number%myprime)

在我运行的一项测试中将运行时间从 ~5.4 秒减少到 ~0.64 秒。

去掉不必要的sync.WaitGroups 可以将时间进一步缩短到 0.48 秒左右。 See the version without sync.WaitGroup here. 你仍然在做很多通道操作,这些带有 yield-value-from-coroutine 运算符的语言不需要(尽管它们有自己的问题)。这不是实施素数测试的好方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-11
    • 2010-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    相关资源
    最近更新 更多