Это заставило меня задуматься, насколько важна многопоточность в текущем отраслевом сценарии?
В критических областях производительности, где производительность не зависит от кода сторонних производителей, выполняющего тяжелую работу, а от нашей собственной, я бы склонялся к рассмотрению вещей в таком порядке важности с точки зрения ЦП (GPU - это символ подстановки, который я выиграл не вдаваться):
- Эффективность памяти (например, местность ссылки).
- алгоритмический
- Многопоточность
- SIMD
- Другие оптимизации (например, подсказки статического предсказания ветвления)
Обратите внимание, что этот список основан не только на важности, но и на другой динамике, такой как влияние, которое они оказывают на обслуживание, насколько они прямолинейны (если нет, стоит подумать заранее), их взаимодействие с другими в списке и т. Д.
Эффективность памяти
Многие могут быть удивлены моим выбором эффективности памяти по сравнению с алгоритмическим. Это связано с тем, что эффективность использования памяти взаимодействует со всеми четырьмя другими элементами в этом списке, а также потому, что ее рассмотрение зачастую относится скорее к категории «дизайн», чем к категории «реализация». Здесь, по общему признанию, есть небольшая проблема с курицей или яйцом, поскольку понимание эффективности памяти часто требует рассмотрения всех 4 пунктов в списке, в то время как все 4 других элемента также требуют рассмотрения эффективности памяти. Все же это в основе всего.
Например, если нам нужна структура данных, которая предлагает последовательный доступ по линейному времени и вставки в постоянное время сзади, и ничего более для небольших элементов, наивным выбором здесь будет связанный список. Это без учета эффективности памяти. Когда мы рассматриваем эффективность использования памяти в миксе, мы в конечном итоге выбираем более смежные структуры в этом сценарии, такие как растущие структуры на основе массива или более смежные узлы (например, один хранит 128 элементов в узле), соединенные вместе или, по крайней мере, связанный список, поддерживаемый распределителем пула. Они имеют существенное преимущество, несмотря на одинаковую алгоритмическую сложность. Аналогично, мы часто выбираем быструю сортировку массива вместо сортировки слиянием, несмотря на низкую алгоритмическую сложность просто из-за эффективности памяти.
Аналогично, у нас не может быть эффективной многопоточности, если наши шаблоны доступа к памяти настолько гранулированы и разбросаны по своей природе, что мы в конечном итоге максимизируем количество ложного совместного использования при блокировке на самых гранулярных уровнях в коде. Таким образом, эффективность использования памяти увеличивает эффективность многопоточности. Это является обязательным условием для получения максимальной отдачи от темы.
Каждый элемент выше в списке имеет сложное взаимодействие с данными, и фокусирование на том, как данные представлены, в конечном счете находится в русле эффективности памяти. Каждое из вышеперечисленных может оказаться узким местом с неподходящим способом представления или доступа к данным.
Еще одна причина, по которой эффективность памяти так важна, заключается в том, что она может применяться во всей кодовой базе. Обычно, когда люди воображают, что неэффективность накапливается в маленьких и маленьких разделах работы здесь и там, это признак того, что им нужно получить профилировщик. Тем не менее, поля с малой задержкой или поля с очень ограниченным оборудованием на самом деле найдут, даже после профилирования, сеансы, которые не обозначают четких горячих точек (просто разнесенных по всему месту) в кодовой базе, которая явно неэффективна с тем, как она выделяется, копируется и доступ к памяти. Как правило, это единственный случай, когда вся кодовая база может быть восприимчива к проблемам производительности, которые могут привести к появлению нового набора стандартов, применяемых в кодовой базе, и эффективность памяти часто лежит в основе этого.
алгоритмический
Это в значительной степени само собой разумеющееся, поскольку выбор в алгоритме сортировки может сделать разницу между массивным вводом, который занимает месяцы для сортировки, и секундами для сортировки. Это оказывает наибольшее влияние из всех, если выбор между, скажем, действительно неподобающим квадратичным или кубическим алгоритмами и линейным, или между линейным и логарифмическим или постоянным, по крайней мере, до тех пор, пока у нас не будет 1 000 000 базовых машин (в этом случае память эффективность станет еще важнее).
Тем не менее, он не находится в начале моего личного списка, поскольку любой компетентный в своей области знает, как использовать ускоряющую структуру для отбраковки усеченного конуса, например, мы насыщены алгоритмическими знаниями и понимаем такие вещи, как использование варианта дерева, такого как основополагающее дерево для поиска по префиксу - это детское дело. Не имея такого рода базовых знаний в области, в которой мы работаем, алгоритмическая эффективность, безусловно, поднимется до вершины, но зачастую алгоритмическая эффективность тривиальна.
Кроме того, в некоторых областях может быть необходимо изобретать новые алгоритмы (например: при обработке ячеек мне приходилось изобретать сотни, поскольку их либо не существовало раньше, либо реализации подобных функций в других продуктах были собственными секретами, а не опубликованы в статье). ). Однако, как только мы пройдем часть, посвященную решению проблем, и найдем способ получить правильные результаты, и как только эффективность станет целью, единственный способ добиться ее - рассмотреть, как мы взаимодействуем с данными (памятью). Без понимания эффективности памяти новый алгоритм может стать излишне сложным из-за тщетных усилий по его ускорению, когда единственное, что ему нужно, это немного больше подумать об эффективности памяти, чтобы получить более простой и элегантный алгоритм.
Наконец, алгоритмы, как правило, больше относятся к категории «реализации», чем к эффективности памяти. Их часто проще улучшить задним числом, даже с изначально неоптимальным алгоритмом. Например, алгоритм обработки изображений низкого качества часто просто реализуется в одном локальном месте в кодовой базе. Это может быть заменено лучшим позже. Однако, если все алгоритмы обработки изображений привязаны к Pixel
интерфейсу, который имеет неоптимальное представление в памяти, но единственный способ исправить это - изменить способ представления нескольких пикселей (а не одного), то мы часто SOL и придется полностью переписать кодовую базу в сторонуImage
интерфейс. То же самое касается замены алгоритма сортировки - обычно это детали реализации, в то время как полное изменение базового представления сортируемых данных или способа их передачи через сообщения может потребовать перепроектирования интерфейсов.
Многопоточность
Многопоточность - сложная задача с точки зрения производительности, поскольку это микроуровневая оптимизация, играющая на характеристиках оборудования, но наше оборудование действительно масштабируется в этом направлении. У меня уже есть сверстники, у которых 32 ядра (у меня только 4).
Тем не менее, многопоточность является одной из самых опасных микрооптимизаций, которые, вероятно, известны профессионалам, если цель используется для ускорения работы программного обеспечения. Состояние гонки в значительной степени является самой смертельной возможной ошибкой, поскольку она очень неопределенна по своей природе (может быть, появляется только раз в несколько месяцев на компьютере разработчика в самое неудобное время вне контекста отладки, если это вообще происходит). Таким образом, он, вероятно, имеет самое негативное ухудшение удобства сопровождения и потенциальной правильности кода среди всех этих, особенно потому, что ошибки, связанные с многопоточностью, могут легко попасть под радар даже самого тщательного тестирования.
Тем не менее, это становится настолько важным. Хотя это может все еще не всегда превосходить что-то вроде эффективности памяти (которая иногда может сделать вещи в сто раз быстрее), учитывая количество ядер, которые мы имеем сейчас, мы видим все больше и больше ядер. Конечно, даже на 100-ядерных машинах я бы по-прежнему ставил эффективность памяти на первое место в списке, поскольку эффективность потоков без нее вообще невозможна. Программа может использовать сотню потоков на такой машине и все еще работать медленно, не имея эффективного представления памяти и шаблонов доступа (которые будут привязаны к шаблонам блокировки).
SIMD
SIMD также немного неуклюжий, поскольку регистры на самом деле становятся шире, а планы становятся еще шире. Первоначально мы видели 64-битные регистры MMX, за которыми следовали 128-битные регистры XMM, способные к 4 параллельным операциям SPFP. Теперь мы видим 256-битные регистры YMM, способные к 8 параллельно. И уже есть планы для 512-битных регистров, которые позволили бы 16 параллельно.
Они будут взаимодействовать и умножаться с эффективностью многопоточности. Тем не менее, SIMD может ухудшить удобство обслуживания так же, как и многопоточность. Даже несмотря на то, что связанные с ними ошибки не обязательно так сложно воспроизвести и исправить, как тупиковая ситуация или состояние гонки, переносимость неудобна, и обеспечение того, что код может выполняться на всех компьютерах (и использование соответствующих инструкций на основе их аппаратных возможностей) неловко.
Другое дело, что хотя современные компиляторы обычно не справляются с искусно написанным SIMD-кодом, они легко преодолевают наивные попытки. Они могут улучшиться до такой степени, что нам больше не придется делать это вручную или, по крайней мере, не становясь настолько ручным, чтобы писать встроенные или прямые ассемблерные коды (возможно, просто немного человеческого руководства).
Опять же, без макета памяти, который эффективен для векторизованной обработки, SIMD бесполезен. В итоге мы просто загрузим одно скалярное поле в широкий регистр, чтобы выполнить одну операцию над ним. В основе всех этих элементов лежит зависимость от макетов памяти, чтобы быть действительно эффективной.
Другие оптимизации
Это часто то, что я бы посоветовал нам начать называть «микро» в наши дни, если слово предлагает не только выйти за рамки алгоритмического фокуса, но и внести изменения, которые оказывают минимальное влияние на производительность.
Часто попытка оптимизации для прогнозирования ветвлений требует изменения алгоритма или эффективности памяти, например, если это делается с помощью простых подсказок и реорганизации кода для статического прогнозирования, то это только улучшает первоначальное выполнение такого кода, делая эффекты сомнительными, если не часто пренебрежимо мало.
Вернуться к многопоточности для производительности
Так или иначе, насколько важна многопоточность в контексте производительности? На моем 4-ядерном компьютере он в идеале может делать вещи примерно в 5 раз быстрее (что я могу получить с помощью гиперпоточности). Это было бы гораздо важнее для моего коллеги, который имеет 32 ядра. И это будет становиться все более важным в ближайшие годы.
Так что это очень важно. Но бесполезно просто набросать кучу потоков на проблему, если эффективность памяти не позволяет экономно использовать блокировки, уменьшать ложное совместное использование и т. Д.
Многопоточность вне производительности
Многопоточность - это не всегда просто производительность в прямом смысле пропускной способности. Иногда он используется для балансировки нагрузки даже при возможной стоимости пропускной способности, чтобы улучшить скорость отклика для пользователя или позволить пользователю выполнять больше многозадачности, не дожидаясь завершения (например, продолжить просмотр при загрузке файла).
В этих случаях я бы предположил, что многопоточность поднимается еще выше (возможно, даже выше эффективности памяти), поскольку тогда речь идет о дизайне на уровне пользователя, а не о том, чтобы извлечь максимальную пользу из аппаратного обеспечения. Это часто будет доминировать в дизайне интерфейсов и в том, как мы структурируем всю нашу кодовую базу в таких сценариях.
Когда мы не просто распараллеливаем узкий цикл доступа к массивной структуре данных, многопоточность переходит в действительно жесткую категорию «дизайн», и дизайн всегда превосходит реализацию.
Поэтому в этих случаях я бы сказал, что предварительная многопоточность абсолютно необходима, даже больше, чем представление и доступ к памяти.