Еще о многопоточности

Три недели в углу лежал незакрытый гештальт, надо закрывать.

Если кто помнит, то в прошлой серии мы дошли до того, что Qt-шная система signal-slot плохо масштабируется в многопоточном случае и нужно для передачи данных между потоками использовать что-то еще. Ну, к примеру, lock-free очередь (из TBB или вот эту, такой уж большой разницы я пока не обнаружил, хотя и должна быть).

Ну что ж, берем делаем приложение:

  • пачка потоков (1...32) делает какую-то простую работу (копирует строку)
  • складывает результат в очередь
  • и выгребатор, в одной очереди, результаты выгребает.

Достаточно ожидаемо упираемся в выгребатор (а так же в malloc):

Да, с ростом количества потоков становится незначительно быстрее (40% при переходе от 1 рабочего потока к 32).

В-общем и так понятно, что если единичная задача очень маленькая, то оверхед по раскидыванию задач по потокам и сбору обратно - велик и делать так не надо.

Поэтому перейдем к реальной задаче. В качестве реальной задачи возьмем SHA512, но в качестве данных будем передавать конечно указатель (и размер), а не сами данные, иначе мы (100%) упремся в копирование туда-сюда.

Итак у нас есть:

  • lock-free очередь заданий
  • Рабочие threads которые из нее берут и работают
  • Маленький (относительно задачи) результат, который мы складываем в выходную очередь
  • И еще один поток-выгребатор, который разбирает выходную очередь.

При вменяемом размере единичной задачи (я пробовал два размера, 10 и 100 килобайт, один поток их обрабатывает со скоростью ~22000 и 2200 заданий в секунду, соответственно) получается картинка близкая к идеальной:

  • Линейный рост до 16 параллельных задач (физических CPU cores у меня как раз 16). На 16-ти ядрах ускорение практически ровно в 16 раз для большой (100кб) задачи и в 15.15 раз для маленькой.
  • И медленный рост (еще процентов 20 от 16 до 32 потоков) за счет hyper-threading.

Понятно что к Qt данная конструкция не имеет никакого отношения (ну я потоки запускал через QThread::start) и если нужно взаимодействие с Qt-шной программой, то его нужно делать уже отдельно каким-то способом (вариантов просматривается более одного), кроме того отдельный прикол - это завершение работы worker threads по команде из Qt же, но все эти задачи представляются решаемыми.