Всем добрый день!
Назрел очередной вопрос для тех, кто занимается многопоточными программами: каки
образом вы улучшаете показатели быстродействия программы, в случае, если она не оправдала возложенных на нее в этом плане ожиданий?
Опишу проблему подробнее. После перевода проекта под FreeBSD в многопоточный вариант
итоговая скорость работы не понравилась. Логика исходного проекта должна была остатьс
практически идентичной, и разнести данные по потокам так, чтобы, например, один пото
работал только с одной структурой, и больше никуда не лез, не удалось. Соответственно
упор был сделан на "тонкую синхронизацию", - вышло множество блокировок и мьютексов, которые, теоретически, не должны захватываться кем-то надолго. Но теперь есть опасения, что наличие большого количества обращений к мьютексам выигрыш от использования многопоточки свело на нет. Поделитесь, пожалуйста, опытом, на что стоит обратить внимание, и есть ли какие-то еще возможности борьбы за скорость помимо полной переработки логики проекта?
UPDATE
Чтобы не было путаницы, опишу приближенную модель проекта. В рассматриваемой системе было 4 потока в расчете на 4 ядра:
1-й - основной поток, обслуживает новые соединения, обрабатывает прерывания, и выполняет несрочную работу во время простоя;
2-й - поток приема и обработки запросов от пользователей;
3-й и 4-й - потоки выполняющие обработку разных частей трафика.
Все потоки в недетерминированные относительно друг друга моменты времени периодическ
читают и записывают данные в различные структуры (порядка 10 штук, включают различны
счетчики, массивы с индексами), а так же читают и редактируют 4 различных списка. Основной идеей многопоточного варианта программы было отделить пользователей от обработки трафика, чтобы в случае высокой загрузки они не ждали очень долго, а так же увеличить скорость обработки трафика.
Ответы
Ответ 1
Имеющихся в вопросе данных, конечно, очень мало, но что могу добавить:
При решении вопроса о количестве
потоков стоит идти не от задач, а от
объёма работ. Т.е. я очень
подозреваю, что сейчас потоки 1 и 2
в основном простаивают. Так что
лучше, например, сделать ещё по
одному потоку, аналогичному 3 и 4, а
возможно и по 2-3. Таким образом,
если будет идти много трафика,
обрабатываемого потоком типа 3, всем
4-м физическим ядрам будет, чем
заняться.
Учитывая, что при текущем разделении по типу работ, возникает
большое количество взаимодействий,
гораздо лучше сделать несколько
однотипных потоков, которые будут
выбирать задания из общей очереди и
класть результаты в другую общую
очередь. Естественно, остаётся
задача выделения работы, которую
можно выполнять с минимумом
взаимодействий. Наверняка можно
что-то найти, парсинг запроса
какой-нибудь или тяжёлые расчёты.
Есть ещё различные приёмы для
уменьшения конфликтов по данным.
Например, создание пула памяти
отдельно для каждого потока, чтобы
они не толкались при выделении
памяти. Или, скажем, каждый поток
может подсчитывать какую-то
статистику (как раз счётчики) только
по тому, что он обработал, а потом,
изредка, эта информация может
объединяться в главном потоке.
Можно попробовать задать отдельный
вопрос на эту тему
Ответ 2
Куча мютексом может очень хорошо затормозить программу.
Ещё программу может затормозить промах кеша. К примеру, программа просто брала дв
массива и суммировала их в третий. Но массивы большие (сотни миллионов элементов)
После распаралеливания 8 потоков работают с 8 массивами. И скорее всего 8*3 = 24 куска разных памяти не будут так хорошо влазить в кеш, будет постоянная подгрузка данных в кеш, и выгрузка (так как другим потокам также нужно). В этом случае уменьшение кол-ва потоков может дать существенный прирост.
Есть алгоритмы, которые просто так параллелить сложно. Например, расчет по рекуррентным формулам (когда последующий элемент зависит от предыдущего).
каким образом вы улучшаете показатели быстродействия программы, в случае, если она не оправдала возложенных на нее в этом плане ожиданий?
для начала классическим способом - профайлинг. Потом, когда подозрительные мест
найдены, поиском хороших алгоритмов. Например, иногда пузырьковая сортировка может быт
значительно лучше quick sort - к примеру, известно, что данные почти всегда отсортированы и не больше 2-3 элементов стоит не на своих местах. quick sort в этом случае потратит много памяти, а по скорости вряд ли выиграет.
Что же делать?
уберите по максимуму мютексы и блокировки. Например, для разных счетчиков можно использовать atomic.
посмотрите на алгоритмы, возможно, применение sse даст прирост в 3-4 раза. Хотя иногда даже включение опции компилятора может все исправить.
пересмотр многопоточности. Возможно, OpenMP может сильно помочь. Он сильно може
помочь, если нужно распараллеливать небольшие циклы, которые имеют "независимое тело".
отключение отладочного вывода. Как это не смешно:). У меня был случай, когда, убра
одну строку вывода в лог, код ускорился в десятки раз. Просто логгер был блокирующий с принудительным сбрасыванием в файл. Самое смешное - эта строка выводила время обработки блока кода, но по факту работала значительно дольше, чем сам код.
Ответ 3
@margosh, опасения по поводу мьютексов совершенно правильные, а вот скорость на виртуалк
(особенно в однопоцессорном варианте) и реальном многопроцессорном сервере может различаться на порядок.
Причем именно скорость переключения между потоками и вообще скорость системных вызовов, а не только работа с мьютексами.
Хотя, от VM тоже сильно зависит. Например, серверная VMware (за деньги) на сервер
с Xeon-ами и бесплатный VirtualBox на Pentium (при сходных частотах) показывают существенно разные результаты.
Вообще-то, в потоки лучше направлять ту работу, которая требует минимальной синхронизаци
с остальными частями программы. Причем объем этой работы должен быть большим (или возможно неопределенное ожидание каких-либо событий).
Совершенно субъективно и навскидку: если работа займет меньше 1 миллисекунды, т
создавать отдельный поток не стоит. Если меньше 0.01 миллисекунды, то даже в пуле предварительн
запущенных потоков ее делать не надо. Помните, реальное перепланирование контекстов в ядре происходит (в отсутствии других событий) с частотой системного таймера (обычно 10 миллисекунд(!)).
Часто независимую часть обработки (длинной, конечно) лучше запускать вообще в отдельном процессе, а не потоке (все получается проще).
Сразу, если будете переходить на обработку по событиям, то не увлекайтесь сигналами
Отнеситесь к ним, именно как к исключительным ситуациям, а не средству управления в общем потоке действий алгоритма.
Ответ 4
Очень многое зависит от задачи и структур данных.
В одной из своих программ работающей с сокетами, для контроля активности нескольких потоков, использую всего два спаренных семафора.
1 - блокирует доступ к очереди задач, и его внутренний счётчик синхронизирован
количеством заданий в очереди, следовательно он начинает блокировать потоки когда заданий нет.
2 - блокирует непосредственно обращения к функции выборки задач. Когда поток забрал задание из очереди, он этот семафор освобождает.
Алгоритм следующий: в очередь поступило несколько задач, разблокируем (1) по числ
задач, потоки начинают просыпаться по очереди, забирать задачи и выполнять их. Если задач больше чем потоков, они не будут спать пока не выполнят все.
PS: в Linux использую семафоры System V - можно выполнять операции над группой семафоров, например пробуждаться по освобождению обоих семафоров.
Комментариев нет:
Отправить комментарий