Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.133.131.168] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Я тут на ЛОРе немного участвую в одном обсуждении, не связанном с темой, но этот вопрос был затронут, и я сделал небольшое сравнение "лёгких" тредов Racket и горутин:
#lang racket (define NCHANNELS 100) (define NTHREADS 100000) (define channels (for/vector ((i (in-range NCHANNELS))) (make-channel))) (define rand-gen (make-pseudo-random-generator)) (define (pick-channel) (let ((i (random NCHANNELS rand-gen))) (vector-ref channels i))) (define (mixer) (define out (make-channel)) (define n (- NCHANNELS 1)) (define (loop) (let loop ((i 0)) (when (channel-try-get (vector-ref channels i)) (channel-put out #t)) (loop (if (= i n) 0 (+ i 1))))) (thread loop) out) (define (start-thread done) (thread (lambda () (sleep 0.1) (channel-put done #t)))) (define (main) (define done (mixer)) (for ((i (in-range NTHREADS))) (start-thread done)) (for ((i (in-range NTHREADS))) (channel-get done)) (displayln 'done)) (time (main)) => c:\Data\Sources\Racket>threads.exe done cpu time: 28000 real time: 27931 gc time: 12344 package main import ( "fmt" "math/rand" "time" ) type signal struct{} const ( NCHANNELS = 100 NTHREADS = 100000 ) var channels = func() []chan signal { c := make([]chan signal, NCHANNELS) for i := range c { c[i] = make(chan signal) } return c }() func pickChannel() chan signal { return channels[rand.Intn(NCHANNELS)] } func mixer() chan signal { return mixall(channels[0], channels[1:]...) } func startThread(done chan signal) { time.Sleep(time.Millisecond * 100) done <- signal{} } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() realMain() fmt.Println(time.Since(start)) } func realMain() { done := make(chan signal) for i := 0; i < NTHREADS; i++ { go startThread(done) } for i := 0; i < NTHREADS; i++ { <-done } fmt.Println("done") } func mixall(ch chan signal, rest ...chan signal) chan signal { for _, ch2 := range rest { ch = mix(ch, ch2) } return ch } func mix(ch1 chan signal, ch2 chan signal) chan signal { out := make(chan signal) go func() { for { select { case msg := <-ch1: out <- msg case msg := <-ch2: out <- msg } } }() return out } => C:\Data\Sources\Go\src\cmd>go run test.go done 891.3125ms Разница почти в 30 раз в пользу Go. А какие результаты даст ваш любимый язык или библиотека? (Конечно, тест весьма синтетический, но, я надеюсь он в должной мере нивелирует влияние реализации базовых численных вычислений (Эрланг на числодробилках крайне плохо, насколько я знаю, например), типизации (в Racket --- динамическая, в Go --- статическая), ввода-вывода и акцентирует внимание исключительно на механизме выполнения и взаимодействия независимых "потоков" выполнения) |
Сообщ.
#2
,
|
|
|
korvin, а тебя в Go варианте все функции используются?
|
Сообщ.
#3
,
|
|
|
Цитата MyNameIsIgor @ korvin, а тебя в Go варианте все функции используются? В смысле? Вроде да. Что тебя смутило? Go-код немного отличается от Racket, т.к. в последнем нет select, но, с другой стороны, в Go-коде создаётся чуть больше каналов и горутин (на микширование). В Racket это тупо циклическим перебором делается, возможно от этого и такая сильная разница, попробую у того знатока Racket, с которым общаюсь на ЛОРе, попросить реализацию select, может более-менее сравняет счёт. |
Сообщ.
#4
,
|
|
|
Цитата korvin @ Что тебя смутило? Функция main запускает realMain, которая создаёт один канал и запускает NTHREADS go-routine на основе функции startThread, передав им созданный канал. Потом ждёт NTHREADS сообщений из канала. В свою очередь функция startThread просто засыпает, а проснувшись, отправляет сообщение в канал. А остальные функции? |
Сообщ.
#5
,
|
|
|
Цитата MyNameIsIgor @ Функция main запускает realMain, которая создаёт один канал и запускает NTHREADS go-routine на основе функции startThread, передав им созданный канал. Потом ждёт NTHREADS сообщений из канала. В свою очередь функция startThread просто засыпает, а проснувшись, отправляет сообщение в канал. А остальные функции? Так все же вызовы прописаны в исходниках остальных =) В Racket нет необходимости в функции main, там можно просто вызывать формы в (top-)module-level, т.е. Go-шная realMain соответствует Racket'овой main. функция mixer создаёт канал done, в который горутины startThread "отчитываются" о завершении, при этом она использует mixall (которая в свою очередь использует mix), чтобы собрать сообщения с 100 channels в один. Вообще можно было в startThread передавать... Пока писал этот текст, обнаружил косяк, о котором ты, видимо, говорил: горутины шлют ответ в один канал, а не в один из 100 channels, в отличие от. Поправил код: package main import ( "fmt" "math/rand" "time" ) type signal struct{} const ( NCHANNELS = 100 NTHREADS = 100000 ) var channels = makeChannels(NCHANNELS) func pickChannel() chan signal { return channels[rand.Intn(NCHANNELS)] } func startThread() { time.Sleep(time.Millisecond * 100) pickChannel() <- signal{} } func makeChannels(n int) []chan signal { channels := make([]chan signal, n) for i := range channels { channels[i] = make(chan signal) } return channels } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() realMain() fmt.Println(time.Since(start)) } func realMain() { done := mixall(channels[0], channels[1:]...) for i := 0; i < NTHREADS; i++ { go startThread() } for i := 0; i < NTHREADS; i++ { <-done } fmt.Println("done") } func mixall(ch chan signal, rest ...chan signal) chan signal { for _, ch2 := range rest { ch = mix(ch, ch2) } return ch } func mix(ch1 chan signal, ch2 chan signal) chan signal { out := make(chan signal) go func() { for { select { case msg := <-ch1: out <- msg case msg := <-ch2: out <- msg } } }() return out } => C:\Data\Sources\Go\src\cmd>go run test.go done 9.3667769s Итого разница максимум в 3 раза. Уже не так существенно. =) Впрочем топик этим не закрыт. =) |
Сообщ.
#6
,
|
|
|
Цитата korvin @ Пока писал этот текст, обнаружил косяк, о котором ты, видимо, говорил: горутины шлют ответ в один канал, а не в один из 100 channels, в отличие от Угу, отличие вот в этом done := mixall(channels[0], channels[1:]...) Цитата korvin @ Итого разница максимум в 3 раза. Хе-хе Подозреваю, что дело тут уже скорее в реализации Racket'а. Объясни алгоритм, а то лень курить go. И boost.fibers тоже лень собирать Но вон D_KEY собрал - он напишет аналог |
Сообщ.
#7
,
|
|
|
Переписал Go-код на перебор каналов из channels:
package main import ( "fmt" "math/rand" "time" ) type signal struct{} const ( NCHANNELS = 100 NTHREADS = 100000 ) var channels = makeChannels(NCHANNELS) func pickChannel() chan signal { return channels[rand.Intn(NCHANNELS)] } func startThread() { time.Sleep(time.Millisecond * 100) pickChannel() <- signal{} } func makeChannels(n int) []chan signal { channels := make([]chan signal, n) for i := range channels { channels[i] = make(chan signal) } return channels } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() realMain() fmt.Println(time.Since(start)) } func realMain() { done := mixer() for i := 0; i < NTHREADS; i++ { go startThread() } for i := 0; i < NTHREADS; i++ { <-done } fmt.Println("done") } func mixer() chan signal { out := make(chan signal) go func() { i := 0 n := NCHANNELS - 1 for { select { case <-channels[i]: out <- signal{} default: // to make select non-blocking } if i == n { i = 0 } else { i++ } } }() return out } => C:\Data\Sources\Go\src\cmd>go run test.go done 1.2368222s Как бы разница вернулась к примерно 30 раз. =) Добавлено Цитата MyNameIsIgor @ Объясни алгоритм, а то лень курить go. И boost.fibers тоже лень собирать Но вон D_KEY собрал - он напишет аналог Ок, попробую, пример искусственный, потому не могу привести адекватной аналогии из реального мира (по крайней мере прям сейчас навскидку): Есть 100(NCHANNELS) точек выдачи товара, каждая в один момент времени может хранить только одну единицу товара, и есть 100000(NTHREADS) поставщиков, каждому выдаётся рандомный адрес точки выдачи из этих 100, соответственно поставщики, получившие одинаковый адрес, соревнуются, кто из них сможет сдать товар. Есть менеджер, который последовательно, по очереди обходит эти 100 точек и на каждой рандомно выбирает одного из поставщиков, которому приехал на этот адрес, и забирает у него товар и отправляет его в одну общую очередь(done), которую обрабатывает realMain. Если не ошибаюсь, это демультиплексинг потоков сообщений. Фактически, можно было вместо сотни каналов channels использовать один, но этой сотней и диспетчером(mixer) так мы слегка распределяем нагрузку. Например каналы --- это Ethernet-кабели, а mixer --- это коммутатор, принимающий данные с разных "входных" Ethernet-портов и посылающий их на один "выходной" порт. |
Сообщ.
#8
,
|
|
|
Выглядит это примерно так (движение сигналов слева-направо):
Прикреплённая картинка
По сути вместо микшера и разных каналов(тред->микшер) можно использовать канал(треды->main), в который будут "писАть" все треды, но так чуть больше нагрузка на диспетчеризацию всего этого дела (что важно для теста) + это может быть вполне реальный, например аудио, микшер. Добавлено Цитата MyNameIsIgor @ лень курить go. И boost.fibers тоже лень собирать Но вон D_KEY собрал - он напишет аналог Ну это всяко интересней правил расстановки скобок и ромбонаследования =) |
Сообщ.
#9
,
|
|
|
Цитата korvin @ Ну это всяко интересней правил расстановки скобок и ромбонаследования =) Ну, мне просто лень собирать fibers |
Сообщ.
#10
,
|
|
|
Цитата MyNameIsIgor @ Ну, мне просто лень собирать fibers "И в этом весь С++"... =))) Комитет разве ещё не решился добавить лёгкие треды в стандарт? |
Сообщ.
#11
,
|
|
|
Цитата korvin @ "И в этом весь С++"... =))) В чём? C++ - единственный язык, в который ещё не добавили легковесные потоки? Цитата korvin @ Комитет разве ещё не решился добавить лёгкие треды в стандарт? Вот здесь надо говорить "в этом весь C++". Потому что в любом случае добавят не сами легковесные потоки, а что-то более низкоуровневое. |
Сообщ.
#12
,
|
|
|
Цитата MyNameIsIgor @ В чём? C++ - единственный язык, в который ещё не добавили легковесные потоки? В том, чтобы сделать что-то банальное, нужны существенные телодвижения. =) |
Сообщ.
#13
,
|
|
|
Цитата korvin @ В том, чтобы сделать что-то банальное, нужны существенные телодвижения. =) Ну, давай, без существенных телодвижений создадим банальные легковесные потоки в Java или C#... |
Сообщ.
#14
,
|
|
|
Цитата MyNameIsIgor @ Ну, давай, без существенных телодвижений создадим банальные легковесные потоки в Java или C#... Зачем нам эти быдлоязыки? Давай ConcurrentML, Occam? =)) |
Сообщ.
#15
,
|
|
|
Цитата korvin @ Зачем нам эти быдлоязыки? Чтобы работать, например |