Как стать автором
Обновить

Комментарии 20

Что касается кода на C++, то он плох. Все потоки блокируются на единственном thread_pool::mutex_, защищающем единственную очередь. Так как полезной работы нет, а вся работа с очередью строго последовательна, то все потоки будут работать последовательно. Сделайте неблокирующую очередь или отдельную очередь к каждому потоку.
В одном случае имеем многозадачность, в другом — многопоточность. Вывод напрашивается даже без результатов ;)
Немножко расшифрую.

При запуске программы на Go создается главный Go-routine. Когда вы используете ключевое слово go, создается новый Go-routine, но! go-routines, которые вы создаете в коде выполняются в одном потоке в пределах процесса, а не в разных потоках, следовательно выполнение происходит псевдопараллельно, то есть ресурс ЦП квантуется по go-рутинах, поочередно выделяя время каждому из них. Разница между потоками в том, что под новый go-routine не выделяются доп. ресурсы, как в случае создания «родных» тредов ОС, в том числе стек.
>Кстати, если установить переменную GOMAXPROCS = 8 [...] то скорость программы на Go дико возрастает
Я получал противоположный результат.
Прошу прощения за неверную формулировку, я имел ввиду, что при 8 программа стала работать медленее.
Исправил в тексте.

%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/

Первый в Америке с Пайком, второй в Великобритании без Блумберга =)
Я так понял, что отличие от простейшей концепции Erlang только в возможности легко ограничить длину очереди...?
Для полноты картины нехватает сравнения с обычным makefile (-j 1 и -j 8).
Для распараллеливания по разным процессорам потоки в любом случае будут системными, и никакого преимущества их легковесность не дает, кроме удобства, мало что будет от рантайма зависить, другое дело качество самого кода.
Мьютекс в случайном месте конечно способен убить параллельность. Но что там лочить, я не понимаю, все же независимо. Разве для такой задачи не достаточно #pragma omp parallel for?
Советую перенести тар на рамдрайв, такая загрузка для полностью параллельной задачи по-моему не предел
Рад, что языку Go находится практическое применение. Много-«поточность» через go-routines в нем довольно таки удобно реализована.
Это многозадачность, а не многопоточность!!! Запомните наизусть, и не путайте эти два термина.

И это не практическое применение Go.
Не совсем понятно, чем 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()

И длина очереди задач совсем не должна быть равна количеству раннеров.
Но это придирки и спасибо за интересный бенчмарк))
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации