Комментарии 20
Что касается кода на C++, то он плох. Все потоки блокируются на единственном thread_pool::mutex_, защищающем единственную очередь. Так как полезной работы нет, а вся работа с очередью строго последовательна, то все потоки будут работать последовательно. Сделайте неблокирующую очередь или отдельную очередь к каждому потоку.
В одном случае имеем многозадачность, в другом — многопоточность. Вывод напрашивается даже без результатов ;)
Немножко расшифрую.
При запуске программы на Go создается главный Go-routine. Когда вы используете ключевое слово go, создается новый Go-routine, но! go-routines, которые вы создаете в коде выполняются в одном потоке в пределах процесса, а не в разных потоках, следовательно выполнение происходит псевдопараллельно, то есть ресурс ЦП квантуется по go-рутинах, поочередно выделяя время каждому из них. Разница между потоками в том, что под новый go-routine не выделяются доп. ресурсы, как в случае создания «родных» тредов ОС, в том числе стек.
При запуске программы на Go создается главный Go-routine. Когда вы используете ключевое слово go, создается новый Go-routine, но! go-routines, которые вы создаете в коде выполняются в одном потоке в пределах процесса, а не в разных потоках, следовательно выполнение происходит псевдопараллельно, то есть ресурс ЦП квантуется по go-рутинах, поочередно выделяя время каждому из них. Разница между потоками в том, что под новый go-routine не выделяются доп. ресурсы, как в случае создания «родных» тредов ОС, в том числе стек.
>Кстати, если установить переменную GOMAXPROCS = 8 [...] то скорость программы на Go дико возрастает
Я получал противоположный результат.
Я получал противоположный результат.
%erlang
run([]) ->
ok;
run([F | Others]) ->
spawn(?MODULE, compile, [F]),
run(Others).
Концепция легких процессов весьма актуальна в задачах с широкой параллелизацией. Это достаточно высокий уровень абстракции, который несмотря на все свои преимущества, все же остается абстракцией.
Я пишу на Erlang и кучу раз видел профит, как в легкости написания кода, так и в перфомансе.
Но тем не менее всегда есть вещи, которые разгоняют розовых слоников и единорогов. Например, SMP, от которого появляются локи, и никакого линейного роста производительности не видно. Вот и начинаешь думать, где еще паблик ETS висят, как настроить планировщики, а может проще всунуть по ноде на ядро?
Это только мне одному странно от фразы «нас не было четкого регрессионного тестирования по каким-то странным причинам...» от r@google.com (как я понимаю) и *.google.com в целом?
Эм, какая связь между Расом Коксом и Бегуном?
Первый вот: research.swtch.com/ ( swtch.com/~rsc/ )
Второй (если не ошибаюсь) вот: easy-coding.blogspot.com/
Первый в Америке с Пайком, второй в Великобритании без Блумберга =)
Второй (если не ошибаюсь) вот: easy-coding.blogspot.com/
Первый в Америке с Пайком, второй в Великобритании без Блумберга =)
Я так понял, что отличие от простейшей концепции Erlang только в возможности легко ограничить длину очереди...?
Для полноты картины нехватает сравнения с обычным makefile (-j 1 и -j 8).
Для распараллеливания по разным процессорам потоки в любом случае будут системными, и никакого преимущества их легковесность не дает, кроме удобства, мало что будет от рантайма зависить, другое дело качество самого кода.
Мьютекс в случайном месте конечно способен убить параллельность. Но что там лочить, я не понимаю, все же независимо. Разве для такой задачи не достаточно #pragma omp parallel for?
Советую перенести тар на рамдрайв, такая загрузка для полностью параллельной задачи по-моему не предел
Мьютекс в случайном месте конечно способен убить параллельность. Но что там лочить, я не понимаю, все же независимо. Разве для такой задачи не достаточно #pragma omp parallel for?
Советую перенести тар на рамдрайв, такая загрузка для полностью параллельной задачи по-моему не предел
Рад, что языку Go находится практическое применение. Много-«поточность» через go-routines в нем довольно таки удобно реализована.
Не совсем понятно, чем make -j не угодил. Хотелось бы увидеть сравнение вашей задачи выполненой make и go-программой. Думаю что вы съэкономили от силы несколько секунд, зато потратили кучу времени, включая статью на хабр ;)
Кстати в вашем случае ни о каком много-поточном программировании речи вообще не должно быть. Есть пускач, который запускает n задач в параллель, его паралелить — себе дороже, как показали ваши эксперименты с go. Код на c++ должен был быть примерно такой:
Жду результатов тестирования скорости. ;)
system("tar -xv ..."); maxtasks = 32; bool end = false; tasks = 0; while (true) { char *cmd = next_cmd(); if (next_cmd) { execve(cmd); tasks++; } else { end = true; } if (tasks == max_tasks || end) { wait(...); // wait for one task to finish tasks--; if (end && tasks == 0) break; } }
Жду результатов тестирования скорости. ;)
Код runner можно упростить:
При этом не надо передавать пустые сообщения в канал. Надо его просто закрыть:
// Поток-runner.
func runner(tasks <-chan string, done chan<- bool) {
// Цикл, ожидающий сообщения
for name := range tasks{
// Компилируем файл.
compile(name)
}
// Посылаем сообщение, что поток завершился.
done <- true
}
При этом не надо передавать пустые сообщения в канал. Надо его просто закрыть:
for i := 0; i < tests.Len(); i++ {
tasks <- tests.At(i)
}
close(tasks)
Да. И канал done не нужен, для такого есть sync.WaitGroup. Да и runner не нужен, хватит лямбды.
И длина очереди задач совсем не должна быть равна количеству раннеров.
Но это придирки и спасибо за интересный бенчмарк))
var wg sync.WaitGroup
for i := 0; i < *jobs; i++ {
go func(){
for name := range tasks{
compile(name)
}
wg.Done()
}()
wg.Add(1)
}
for i := 0; i < tests.Len(); i++ {
tasks <- tests.At(i)
}
close(tasks)
wg.Wait()
И длина очереди задач совсем не должна быть равна количеству раннеров.
Но это придирки и спасибо за интересный бенчмарк))
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Многопоточное программирование в Go