Канальная версия, вероятно, медленнее, потому что работа (вычисление расстояния) очень мала по сравнению с накладными расходами на планирование и обмен данными между горутинами.
Лучше разделить пары точек на более крупные куски, как вы сделал во втором примере.
Вот упрощенный c подход, чтобы увидеть, что происходит:
var sum float64
var count int64
for i, p := range points {
for _, p2 := range points[i+1:] {
sum += p.distanceTo(p2)
count++
}
}
fmt.Printf("%d points, %d pairs, avg %f\n", len(points), count, sum/float64(count))
Когда я пробую это, мой ноутбук нагревается, а затем выводит:
100000 points, 4999950000 pairs, avg 0.660843
duration 22.623835855s
Каждое вычисление расстояния занимает около 5 наносекунд, это очень быстро и слишком мало для разделения работы.
Только одно ядро процессора выполняет работу:
Since my laptop has 8 cores, I tried to create an example with 8 workers working on equal chunks of the point pairs.
8 workers, 100000 points, 4999950000 pairs, avg 0.660843
duration 7.481476421s
It's not 8 x faster, but it's an improvement, and puts those idle cores to work:
Example:
package main
import (
"fmt"
"math"
"math/rand"
"runtime"
"time"
)
const nPoints = 100000
var points [nPoints]*Point
type Point struct {
x, y, z float64
}
func (p *Point) distanceTo(p2 *Point) float64 {
a := p.x - p2.x
b := p.y - p2.y
c := p.z - p2.z
return math.Sqrt(a*a + b*b + c*c)
}
func main() {
initPoints()
start := time.Now()
run()
fmt.Printf("duration %v\n", time.Since(start))
}
func initPoints() {
for i := 0; i < nPoints; i++ {
points[i] = &Point{rand.Float64(), rand.Float64(), rand.Float64()}
}
}
func run() {
requests := make(chan Request)
results := make(chan Result)
nWorkers := runtime.NumCPU() //change, eg: 1 or 2
for n := 0; n < nWorkers; n++ {
go worker(requests, results)
}
go func() {
nPairs := nPoints * (nPoints - 1) / 2
batchSize := nPairs / nWorkers
req := Request{iStart: 0, jStart: 1, count: 0}
for i := 0; i < nPoints; i++ {
for j := i + 1; j < nPoints; j++ {
req.count++
if req.count == batchSize {
requests <- req
req = Request{iStart: i, jStart: j, count: 0}
}
}
}
if req.count > 0 {
requests <- req
}
close(requests)
}()
var sum, avg float64
var count int64
for n := 0; n < nWorkers; n++ {
r := <-results
fmt.Printf("worker %d: %v\n", n+1, r)
sum += r.sum
count += r.count
}
close(results)
if count > 0 {
avg = sum / float64(count)
}
fmt.Printf("%d workers, %d points, %d pairs, avg %f\n", nWorkers, len(points), count, avg)
}
func worker(requests chan Request, results chan Result) {
r := Result{}
for req := range requests {
req.work(&r)
}
results <- r
}
func (req *Request) work(result *Result) {
count := 0
jStart := req.jStart
for i := req.iStart; i < nPoints; i++ {
p := points[i]
if i > req.iStart {
jStart = i + 1
}
for j := jStart; j < nPoints; j++ {
p2 := points[j]
result.sum += p.distanceTo(p2)
result.count++
count++
if count == req.count {
return
}
}
}
}
type Request struct {
iStart int
jStart int
count int
}
type Result struct {
sum float64
count int64
}
func (r Result) String() string {
avg := 0.0
if r.count > 0 {
avg = r.sum / float64(r.count)
}
return fmt.Sprintf("%d comparisons, avg distance %f", r.count, avg)
}
The Сообщение в блоге pprof profiling - отличная демонстрация методов, позволяющих увидеть, на что программа тратит свое время.