Существуют различные аспекты многопоточности, требующие разных подходов.
Например, на веб-сервере широко используется использование потоковых пулов, поскольку оно предположительно "хорошо" для производительности.Такие пулы могут содержать сотни потоков, ожидающих запуска.Использование такого количества потоков приведет к тому, что планировщик будет работать сверхурочно, что отрицательно сказывается на производительности, но его нельзя избежать в системах Linux.Для Windows предпочтительным методом является механизм IOCP, который рекомендует количество потоков, не превышающее количество установленных ядер.Это приводит к тому, что приложение становится (завершение ввода / вывода) управляемым событием, что означает, что при опросе не теряются циклы.Несколько задействованных потоков сводят работу планировщика до минимума.
Если целью является реализация масштабируемой функциональности (большее количество ядер <=>, более высокая производительность), то основной проблемой будет насыщение шины памяти.Насыщение будет происходить из-за выборки кода, чтения данных и записи данных.Неправильно реализованный код будет работать медленнее с двумя потоками, чем с одним.Единственный способ избежать этого - активно работать с шиной памяти:
- , приспосабливая код к минимальному объему памяти (= помещается в кэш кода) и не вызывая другие функции или переходыповсюду.
- настройка чтения и записи памяти до минимального размера.
- информирование механизма предварительной выборки предстоящих операций чтения из ОЗУ.
- настройка работы таким образом, чтобы отношениеРабота, выполняемая внутри собственных кешей ядра (L1 и L2), настолько велика, насколько это возможно, по сравнению с работой вне их (L3 и RAM).
Чтобы выразить это иначе: соответствовать применимому кодуи данные разбиваются на как можно меньшее количество строк кэша (по 64 байта каждая), потому что в конечном итоге именно это будет определять масштабируемость.Если система кэш / память способна выполнять x операций со строками кэша каждую секунду, ваш код будет работать быстрее, если его требования составляют пять строк кэша на единицу работы (=> x / 5), а не одиннадцать (x / 11) или пятьдесят два(x / 52).
Достижение этого не является тривиальным, поскольку каждый раз требует более или менее уникального решения.Некоторые компиляторы хорошо справляются с упорядочением команд, чтобы использовать преимущества конвейерной обработки хост-процессора.Это не обязательно означает, что это будет хороший порядок для нескольких ядер.
Эффективная реализация масштабируемого кода не обязательно будет привлекательной.Рекомендуемые методы и стили кодирования могут, в конце концов, препятствовать выполнению кода.
Мой совет - проверить, как это работает, написав простое многопоточное приложение на языке низкого уровня (например, C), которыйможет быть настроен для работы в однопоточном или многопоточном режиме, а затем профилировать код для различных режимов.Вам нужно будет проанализировать код на уровне инструкций.Затем вы экспериментируете с использованием различных (C) конструкций кода, организации данных и т. Д. Возможно, вам придется мыслить нестандартно и переосмыслить алгоритм, чтобы сделать его более удобным для кэширования.
Первый раз потребует много работы,Вы не узнаете, что будет работать для всех многопоточных решений, но, возможно, вы получите представление о том, чего не следует делать и какие указания следует искать при анализе профилированного кода.