当前位置: 首页 > 面试题库 >

并发:Chudnovky的算法,比同步慢

席弘图
2023-03-14
问题内容

我刚刚在朋友的推荐下开始学习go。到目前为止,我很喜欢它,但是我写了(我想会是)轻量级并发功能的完美例子,并且得到了令人惊讶的结果……所以我怀疑我做错了,或者误解了goroutine是多么昂贵。我希望这里的一些地鼠能提供见识。

我在Go中使用goroutine和简单的同步执行编写了Chudnovsky的算法。我假设,每次计算都独立于其他计算,因此并发运行至少要快一点。

注意 :我正在第5代i7上运行此程序,因此,如果按照我的要求,将goroutines多路复用到线程上,则应同时进行并发 并行处理。

 package main

import (
  "fmt"
  "math"
  "strconv"
  "time"
)

func main() {
  var input string
  var sum float64
  var pi float64
  c := make(chan float64)

  fmt.Print("How many iterations? ")
  fmt.Scanln(&input)
  max,err := strconv.Atoi(input)

  if err != nil {
    panic("You did not enter a valid integer")
  }
  start := time.Now() //start timing execution of concurrent routine

  for i := 0; i < max; i++ {
    go chudnovskyConcurrent(i,c)
  }

  for i := 0; i < max; i++ {
    sum += <-c
  }
  end := time.Now() //end of concurrent routine
  fmt.Println("Duration of concurrent calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)

  start = time.Now() //start timing execution of syncronous routine
  sum = 0
  for i := 0; i < max; i++ {
    sum += chudnovskySync(i)
  }
  end = time.Now() //end of syncronous routine
  fmt.Println("Duration of synchronous calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)
}

func chudnovskyConcurrent(i int, c chan<- float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  c <- numerator/denominator
}

func chudnovskySync(i int) (r float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  r = numerator/denominator
  return
}

func factorial(n uint64) (res uint64) {
  if ( n > 0 ) {
    res = n * factorial(n-1)
    return res
  }

  return 1
}

这是我的结果:

How many iterations? 20
Duration of concurrent calculation:  573.944µs
3.1415926535897936
Duration of synchronous calculation:  63.056µs
3.1415926535897936

问题答案:

您正在进行的计算太简单了,无法在单独的goroutine中进行每个计算。与实际计算相比,您在运行时中浪费的时间更多(创建goroutine,复用,调度等)。例如,您尝试做的事情更适合于GPU,因为您拥有大量并行执行单元,这些单元可以立即进行一次简单的计算。但是您将需要其他语言和API来做到这一点。

您可以做的是为每个执行硬件线程创建执行软件线程。您想将max变量拆分为大块并并行执行。这是一个非常简单的例子,仅用于说明这个想法:

package main

import (
  "fmt"
  "math"
  "strconv"
  "time"
  "runtime"
)

func main() {
  var input string
  var sum float64
  var pi float64
  c := make(chan float64, runtime.GOMAXPROCS(-1))
  fmt.Print("How many iterations? ")
  fmt.Scanln(&input)
  max,err := strconv.Atoi(input)

  if err != nil {
    panic("You did not enter a valid integer")
  }
  start := time.Now() //start timing execution of concurrent routine

  for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
    go func(i int){
      var sum float64
      for j := 0; j < max/runtime.GOMAXPROCS(-1); j++  {
        sum += chudnovskySync(j + i*max/runtime.GOMAXPROCS(-1))
      }
      c <- sum
    }(i)
  }

  for i := 0; i < runtime.GOMAXPROCS(-1); i++ {
    sum += <-c
  }
  end := time.Now() //end of concurrent routine
  fmt.Println("Duration of concurrent calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)

  start = time.Now() //start timing execution of syncronous routine
  sum = 0
  for i := 0; i < max; i++ {
    sum += chudnovskySync(i)
  }
  end = time.Now() //end of syncronous routine
  fmt.Println("Duration of synchronous calculation: ",end.Sub(start))
  pi = 1/(12*sum)
  fmt.Println(pi)
}

func chudnovskySync(i int) (r float64) {
  var numerator float64
  var denominator float64
  ifloat := float64(i)
  iun := uint64(i)
  numerator = math.Pow(-1, ifloat) * float64(factorial(6*iun)) * (545140134*ifloat+13591409)
  denominator = float64(factorial(3*iun)) * math.Pow(float64(factorial(iun)),3) * math.Pow(math.Pow(640320,3),ifloat+0.5)
  r = numerator/denominator
  return
}

func factorial(n uint64) (res uint64) {
  if ( n > 0 ) {
    res = n * factorial(n-1)
    return res
  }

  return 1
}

这是结果

$ go version
go version go1.5.2 windows/amd64

$ go run main.go
GOMAXPROCS = 4
How many iterations? 10000
Duration of concurrent calculation:  932.8916ms
NaN
Duration of synchronous calculation:  2.0639744s
NaN


 类似资料:
  • 同步(Synchronization) 线程间的通信主要是通过共享访问字段以及其字段所引用的对象来实现的。这种形式的通信是非常有效的,但可能导致2种可能的错误:线程干扰(thread interference)和内存一致性错误(memory consistency errors)。同步就是要需要避免这些错误的工具。 但是,同步可以引入线程竞争(thread contention),当两个或多个线程

  • 主要内容:实例下面是一个具有同步功能的多线程示例,这是和上篇文章同样的例子,它依次打印计数器值,每次运行它时,它产生相同的结果。 实例 每次运行此程序时都会产生相同的结果 -

  • 当使用并发hashmap时,我是否需要将put环绕在一个同步块上,是否存在竞争条件:

  • 典型的UNIX系统都支持一个进程创建多个线程(thread)。在Linux进程基础中提到,Linux以进程为单位组织操作,Linux中的线程也都基于进程。尽管实现方式有异于其它的UNIX系统,但Linux的多线程在逻辑和使用上与真正的多线程并没有差别。 多线程 我们先来看一下什么是多线程。在Linux从程序到进程中,我们看到了一个程序在内存中的表示。这个程序的整个运行过程中,只有一个控制权的存在。

  • 问题内容: 我想了解如何在Java中对静态方法进行锁定。 假设我有以下课程: 据我了解,当我调用时,线程获得了对象上的锁,而当我这样做时,线程获得了类上的锁。 我的问题是,这两个呼叫如何彼此同步?调用静态方法是否还获得了所有实例的锁定,或者相反(似乎更合理)? 编辑: 我的问题不是确切如何工作,而是静态和非静态方法如何彼此同步。即,我不希望两个线程同时调用和,但是这些方法获取不同的锁。我的问题是如

  • 问题内容: 我已经开始学习线程同步。 同步方法: 同步块: 什么时候应该使用方法和块? 为什么块比方法更好? 问题答案: 这不是更好的问题,只是有所不同。 同步方法时,实际上是在与对象本身进行同步。对于静态方法,您正在同步到对象的类。因此,以下两段代码以相同的方式执行: 就像您写的一样。 如果要控制到特定对象的同步,或者只想将方法的 一部分 同步到该对象,则指定一个块。如果在方法声明上使用关键字,