Почему каналы в Go более эффективны для параллельных вычислений, чем общая память?
Пример взят из книги "100 Ошибок Go"
Не могу понять почему использование каналов настолько выгоднее чем простое добавление массива? Они же должны нести расходы не только на создание, но и на операционную деятельность, т.к. за ними лежит структра.
Небольшие пояснения:
Count
и Count3
работают со структурой типа Result
Count2
работает со структурой типа Result2
type Input struct {
a int64
b int64
}
type Result struct {
sumA int64
sumB int64
}
//[56]byte используется для расположение sumA и SumB в разных сегментах кеша
type Result2 struct {
sumA int64
_ [56]byte
sumB int64
}
// 1048576 - 2^20 для наглядности
func CreateInput() []Input {
input := make([]Input, 0, 1048576)
for i := int64(0); i < 1048576; i++ {
input = append(input, Input{
a: i,
b: i,
})
}
return input
}
func Count(inputs []Input) Result {
wg := sync.WaitGroup{}
wg.Add(2)
result := Result{}
go func() {
for i := 0; i < len(inputs); i++ {
result.sumA += inputs[i].a
}
wg.Done()
}()
go func() {
for i := 0; i < len(inputs); i++ {
result.sumB += inputs[i].b
}
wg.Done()
}()
wg.Wait()
return result
}
func Count2(inputs []Input) Result2 {
wg := sync.WaitGroup{}
wg.Add(2)
result := Result2{}
go func() {
for i := 0; i < len(inputs); i++ {
result.sumA += inputs[i].a
}
wg.Done()
}()
go func() {
for i := 0; i < len(inputs); i++ {
result.sumB += inputs[i].b
}
wg.Done()
}()
wg.Wait()
return result
}
func Count3(inputs []Input) Result {
var result Result
sumAChan := make(chan int64)
sumBChan := make(chan int64)
go func() {
var sumA int64
for i := 0; i < len(inputs); i++ {
sumA += inputs[i].a
}
sumAChan <- sumA
}()
go func() {
var sumB int64
for i := 0; i < len(inputs); i++ {
sumB += inputs[i].b
}
sumBChan <- sumB
}()
result.sumA = <-sumAChan
result.sumB = <-sumBChan
return result
}
Update: результат сильно зависит от параметра -coverprofile
Count2
и Count3
очень близки, но почему -coverprofile
оказывает такую сильную разницу, только на Count2
?
без него средние результаты такие:
│ stats.txt │
│ sec/op │
Count-12 18.24m ± 2%
Count2-12 677.0µ ± 7%
Count3-12 689.8µ ± 12%
geomean 2.042m
│ stats.txt │
│ B/op │
Count-12 162.5 ± 19%
Count2-12 192.0 ± 6%
Count3-12 288.0 ± 0%
geomean 207.9
│ stats.txt │
│ allocs/op │
Count-12 4.000 ± 0%
Count2-12 4.000 ± 0%
Count3-12 4.000 ± 0%
geomean 4.000
Вот получившиеся результаты по бенчмаркам при 1048576 элементах c -coverprofile
cpu: AMD Ryzen 5 5600G with Radeon Graphics
BenchmarkCount-12 49 25359128 ns/op 237 B/op 4 allocs/op
BenchmarkCount2-12 295 3661261 ns/op 300 B/op 4 allocs/op
BenchmarkCount3-12 1946 644967 ns/op 288 B/op 4 allocs/op
Вот получившиеся результаты по бенчмаркам без -coverprofile
BenchmarkCount-12 67 18467245 ns/op 209 B/op 4 allocs/op
BenchmarkCount2-12 1833 597416 ns/op 211 B/op 4 allocs/op
BenchmarkCount3-12 1938 734245 ns/op 288 B/op 4 allocs/op
код бенчмарка
var res test.Result
var res2 test.Result2
var res3 test.Result
// BenchmarkResult placeholder.
func BenchmarkCount(b *testing.B) {
var sum test.Result
b.StopTimer()
input := test.CreateInput()
b.StartTimer()
for i := 0; i < b.N; i++ {
sum = test.Count(input)
}
res = sum
}
func BenchmarkCount2(b *testing.B) {
var sum test.Result2
b.StopTimer()
input := test.CreateInput()
b.StartTimer()
for i := 0; i < b.N; i++ {
sum = test.Count2(input)
}
res2 = sum
}
func BenchmarkCount3(b *testing.B) {
var sum test.Result
b.StopTimer()
input := test.CreateInput()
b.StartTimer()
for i := 0; i < b.N; i++ {
sum = test.Count3(input)
}
res3 = sum
}
команда для запуска бенчмарка, запускал через графический интерфейс в vscode
go test -benchmem -run=^$ -coverprofile=/tmp/vscode-govayH6A/go-code-cover -bench . alice/effective/internal/test
Ответы (1 шт):
На изначальный вопрос, ответ я получил. В данном случае разница между каналами и расположением в разных сегментах кэша практически отсутствует. Влияние на результаты бенчмарков оказывает флаг -coverprofile
Вот получившиеся результаты по бенчмаркам при 1048576 элементах c -coverprofile
cpu: AMD Ryzen 5 5600G with Radeon Graphics
BenchmarkCount-12 49 25359128 ns/op 237 B/op 4 allocs/op
BenchmarkCount2-12 295 3661261 ns/op 300 B/op 4 allocs/op
BenchmarkCount3-12 1946 644967 ns/op 288 B/op 4 allocs/op
Вот получившиеся результаты по бенчмаркам без -coverprofile
BenchmarkCount-12 67 18467245 ns/op 209 B/op 4 allocs/op
BenchmarkCount2-12 1833 597416 ns/op 211 B/op 4 allocs/op
BenchmarkCount3-12 1938 734245 ns/op 288 B/op 4 allocs/op