По умолчанию Go работает на одном потоке, используя свой шедулер и асинхронные вызовы. (У программиста создается ощущение многопоточности и параллельности.) В этом случае каналы работаю очень быстро. Но если указать Go использовать 2 и больше потока, то Go начинает использовать блокировки и производительность каналов может падать. Не хочется себя ограничивать в использовании каналов. Тем более, большинство сторонних библиотек при каждом удобном случае используют каналы. Поэтому часто эффективно запускать Go с одним потоком, как это сделано по умолчанию.
channel01.go
package main import "fmt" import "time" import "runtime" type Mes struct{ i int } func main() { numcpu := runtime.NumCPU() fmt.Println("NumCPU", numcpu) //runtime.GOMAXPROCS(numcpu) runtime.GOMAXPROCS(1) ch1 := make(chan int) ch2 := make(chan float64) go func() { for i := 0; i < 1000000; i++ { ch1 <- i } ch1 <- -1 ch2 <- 0.0 }() go func() { total := 0.0 for { t1 := time.Now().UnixNano() for i := 0; i < 100000; i++ { m := <-ch1 if m == -1 { ch2 <- total } } t2 := time.Now().UnixNano() dt := float64(t2 - t1) / 1000000.0 total += dt fmt.Println(dt) } }() fmt.Println("Total:", <-ch2, <-ch2) }
users-iMac:channel user$ go run channel01.go NumCPU 4 23.901 24.189 23.957 24.072 24.001 23.807 24.039 23.854 23.798 24.1 Total: 239.718 0
теперь давайте активируем все ядра, перекомментировав строки.
runtime.GOMAXPROCS(numcpu) //runtime.GOMAXPROCS(1)
users-iMac:channel user$ go run channel01.go NumCPU 4 543.092 534.985 535.799 533.039 538.806 533.315 536.501 533.261 537.73 532.585 Total: 5359.113 0
20 раз медленней? В чем подвох? Размер канала по умолчанию 1.
ch1 := make(chan int)
Поставим 100.
ch1 := make(chan int, 100)
результат 1 поток
users-iMac:channel user$ go run channel01.go NumCPU 4 9.704 9.618 9.178 9.84 9.869 9.461 9.802 9.743 9.877 9.756 Total: 0 96.848
результат 4 потока
users-iMac:channel user$ go run channel01.go NumCPU 4 17.046 17.046 16.71 16.315 16.542 16.643 17.69 16.387 17.162 15.232 Total: 0 166.77300000000002
Всего в два раза медленней, но не всегда можно это использовать.
Пример “канал каналов”
package main import "fmt" import "time" import "runtime" type Mes struct{ ch chan int } func main() { numcpu := runtime.NumCPU() fmt.Println("NumCPU", numcpu) //runtime.GOMAXPROCS(numcpu) runtime.GOMAXPROCS(1) ch1 := make(chan chan int, 100) ch2 := make(chan float64, 1) go func() { t1 := time.Now().UnixNano() for i := 0; i < 1000000; i++ { ch := make(chan int, 100) ch1 <- ch <- ch } t2 := time.Now().UnixNano() dt := float64(t2 - t1) / 1000000.0 fmt.Println(dt) ch2 <- 0.0 }() go func() { for i := 0; i < 1000000; i++ { ch := <-ch1 ch <- i } ch2 <- 0.0 }() <-ch2 <-ch2 }
результат 1 поток
users-iMac:channel user$ go run channel03.go NumCPU 4 1041.489
результат 4 потока
users-iMac:channel user$ go run channel03.go NumCPU 4 11170.616
Поэтому, если у вас 8 ядер и вы пишите сервер на Go, вам не стоит полностью полагаться на Go в распараллеливании программы, а может, запустить 8 однопоточных процессов, а перед ними балансировщик, который тоже можно написать на Go. У нас в продакшине был сервер, который при переходе с одно-ядерного сервера на 4х стал обрабатывать на 10% меньше запросов.
Что значат эти цифры? Перед нами стояла задача обрабатывать 3000 запросов в секунду в одном контексте (например, выдавать каждому запросу последовательно числа: 1, 2, 3, 4, 5… может, чуть сложней) и производительность 3000 запросов в секунду ограничивается в первую очередь каналами. С добавлением потоков и ядер производительность растет не так рьяно, как хотелось. 3000 запросов в секунду для Go — это некий предел на современном оборудовании.
ссылка на оригинал статьи http://habrahabr.ru/post/195574/
Добавить комментарий