Резюме :
Как можно указать в своем коде, что OpenMP должен использовать потоки только для РЕАЛЬНЫХ ядер, то есть не считать гипер-многопоточность?
Детальный анализ : В течение многих лет я в свое свободное время кодировал рендерер с открытым исходным кодом (растеризатор / raytracer) только для SW. Код GPL и двоичные файлы Windows доступны здесь:
https://www.thanassis.space/renderer.html
Он компилируется и отлично работает под Windows, Linux, OS / X и BSD.
В прошлом месяце я ввел режим трассировки лучей - и качество сгенерированных снимков взлетело до небес. К сожалению, трассировка лучей на несколько порядков медленнее, чем растеризация. Чтобы увеличить скорость, так же, как и для растеризаторов, я добавил поддержку OpenMP (и TBB) к raytracer - чтобы легко использовать дополнительные ядра процессора. Как растеризация, так и трассировка лучей легко поддаются обработке потоков (работа на треугольник - работа на пиксель).
Дома, с моим Core2Duo, второе ядро помогло всем режимам - и режимы растеризации и трассировки лучей получили ускорение от 1,85 до 1,9х.
Проблема: Естественно, мне было любопытно увидеть максимальную производительность процессора (я также «играю» с графическими процессорами, предварительный порт CUDA ), поэтому я хотел прочную основу для сравнения. Я передал код моему хорошему другу, у которого есть доступ к «чудовищной» машине с 16-ядерным суперпроцессором Intel за 1500 долларов.
Он запускает его в "самом тяжелом" режиме, в режиме raytracer ...
... и он получает одну пятую скорости моего Core2Duo (!)
Удушье - ужас. Что сейчас произошло?
Мы начали пробовать разные модификации, патчи, ... и в конце концов мы поняли это.
Используя переменную среды OMP_NUM_THREADS, можно контролировать, сколько потоков OpenMP порождается.
По мере того, как количество потоков увеличивалось от 1 до 8, скорость увеличивалась (приближаясь к линейному увеличению).
В момент, когда мы пересекли восьмерку, скорость начала уменьшаться, пока она не упала на одну пятую скорости моего Core2Duo, когда использовались все 16 ядер!
Почему 8?
Потому что 8 было числом реальных ядер. Остальные 8 были ... гиперпоточными!
Теория: Теперь это была для меня новость - я видел, как гиперпоточность очень помогает (до 25%) в других алгоритмах, так что это было неожиданно. Очевидно, что хотя каждое ядро с гиперпоточностью поставляется со своими собственными регистрами (и блоком SSE?), Raytracer не может использовать дополнительную вычислительную мощность. Что заставляет меня думать ...
Вероятно, не вычислительная мощность истощается - это пропускная способность памяти.
raytracer использует структуру данных иерархии ограничивающих объемов для ускорения пересечений лучей и треугольников. Если используются многопоточные ядра, то каждое из «логических ядер» в паре пытается читать из разных мест в этой структуре данных (то есть в памяти) - и кэши ЦП (локальные на пару) полностью перебиваются. По крайней мере, это моя теория - любые предложения приветствуются.
Итак, вопрос: OpenMP обнаруживает количество «ядер» и порождает потоки, чтобы соответствовать ему, то есть включает в себя гиперпоточные «ядра» в расчете. В моем случае это, по-видимому, приводит к катастрофическим результатам в отношении скорости. Кто-нибудь знает, как использовать API OpenMP (если это возможно, переносимо), чтобы порождать потоки только для ядер REAL, а не для гиперпоточных?
P.S. Код открыт (GPL) и доступен по ссылке выше, не стесняйтесь воспроизводить на своем собственном компьютере - я предполагаю, что это произойдет во всех многопоточных процессорах.
P.P.S. Извините за длину поста, я подумал, что это образовательный опыт, и хотел поделиться.