Pull to refresh

Comments 18

А что у вас за задача такая, что вам нужно постоянно читать один и тот же блок данных размером строго больше 16 килобайт, но строго меньше 32, что вы смогли почувствовать замедление?
Как показано на графиках, мы читаем блоки размером от 128 байт до 96000 байт:
На графиках по оси X отложен размер блока

Почему возник вопрос именно о блоках строго больше 16 килобайт, но строго меньше 32?
Про графики мне все понятно. Мне не понятно практическое применение, где может просесть скорость. Где в практической задаче может понадобиться постоянно читать один и тот же блок данных размером от 16 до 32.

Мне-то кажется, что тест у вас сугубо синтетический и в реальности отражения не имеет, но вдруг я ошибаюсь.

Кстати, непонятно, почему вы мочите именно Hyper-Threading, можно же и без него создать еще столько же потоков на тех же ядрах. И сделать вывод несколько шире и умнее, чем просто «Hyper-Threading» зло.

Например, можно сделать вывод, что если суммарный размер одновременно обрабатываемых данных на всех ядрах не вмещается в суммарный кеш L1 на этих же ядрах, то производительность резко падает. И получить за это медальку от Капитана Очевидности.

Так при чем тут Hyper-Threading?
  • Мы не делаем глобальных выводов о вреде Hyper-Threading, а рассматриваем его эффект исключительно в описываемом контексте. Есть много задач где HT приносит пользу. В процессе исследования топологии CoD выяснилось, что HT уже не дает того положительного эффекта, который был на заре его восхождения. Стало интересно, насколько востребованным он остается на современных системах, которые «не дотягивают» до NUMA-платформ.
  • Повторные чтения могут быть например, при перемножении матриц A и B, когда строка матрицы A постоянно находится в кэш-памяти, а столбцы матрицы B загружаются последовательно. Кстати, при отсутствии повторных обращений к данным, вообще не было бы смысла в кэш-памяти.
  • Поскольку L1 свой для каждого из ядер, но общий для двух логических процессоров одного ядра, эффект от распараллеливания по ядрам (сравнение опытов 1 и 2) позитивный, а от распараллеливания по логическим процессорам (сравнение опытов 2 и 3) негативный, с точки зрения средней скорости, но не пиковой. Мы как раз сделали акцент на это различие.
По поводу второго пункта: имело бы, см. префетч.
Если гипотетически представить ситуацию, при которой кэш-память используется исключительно как буфер, в котором хранятся данные от момента их опережающего чтения (аппаратного Prefetch или программных Prefetch Hints) до использования, но чтение этих данных происходит однократно, то некая прибавка производительности конечно будет, хотя небольшая, по сравнению с эффектами от повторных чтений. В этом случае правильнее было бы говорить о буферизации, а мы говорим о кэшировании.

Отметим, что буферы чтения и записи есть в процессоре, отдельно от кэш-памяти.
HT позволяет не простаивать процессору ожидая данных из памяти для первого потока благодаря переключению на второй поток. Но если и второй запрашивает данные из памяти которых в кеше нет то оба потока будут ждать. Скорость работы с памятью это не сильно повысит а вот производительность ядра повышает.

Мы предполагаем, что позитивный эффект от Hyper-Threading будет в ситуации, когда два логических процессора одного ядра заняты разными задачами, например вычисления с плавающей запятой и копирование блоков памяти, в этом случае они используют разные ресурсы, а не «стоят в очереди» при доступе к одному ресурсу. Так это или нет — покажут дальнейшие опыты
Тогда позвольте озвучить некоторые «хотелки». Для многих, я думаю, они действительно будут полезны (для меня естественно тоже).

Первое — было бы просто замечательно, если бы Вы проделали эти тесты плавно изменяя кол-во потоков от 1-го до 4*N, где N = кол-во физических ядер. Сначала без HT, затем с ним. Зачем с потоками < N (количества ядер) замерять производительность с включенным и выключенным HT? Для полноты картины и проверки теории практикой.

Второе — было бы полезным плавно менять пересечение инструкций. Я имею ввиду, что полное отсутствие борьбы за исполнительные блоки, должно привести, по словам Intel, к выигрышу в скорости до 30%. Постепенное введение в смежные потоки одинаковых инструкций, должно замедлять нас. Пример на пальцах: в смежных потоках выполняется 9 разных инструкций и одна общая — вероятность пересечения (борьбы за исполнительный блок) 1/10. Постепенное увеличение степени пересечений инструкций, приведет нас к последовательному исполнению. Вопрос только в том, подтвердится ли это тестами.

Третье — мы уже знаем из этой статьи про промахи кэша. Значит необходимо все тесты произвести еще в двух комбинациях.

Повторюсь, это всего лишь «хотелки», которые, как мне кажется, более глубоко раскроют суть вопроса. В любом случае, жду с нетерпением.
По поводу использования различных инструкций в двух потоках, выполняемых одним ядром — мы планируем такой эксперимент, в частности один поток выполняет чтение AVX-инструкциями, а второй — строковыми инструкциями. Но предполагаю, что польза от HT будет больше при другом сценарии — один поток читает
кэш-память, а второй выполняет вычисления с плавающей точкой. Но это уже другой тип задач.

По поводу плавного увеличения количества ядер. Думаю, что поведение будет достаточно предсказуемым, точка перегиба на графике будет при обработке 32K на ядро, независимо от того, как эти 32K распределены по потокам. Нас и за проверку менее очевидных фактов подвергли критике. Хотя, в любом случае, в будущих версиях нашего теста планируется ввести возможность выбора количества потоков с дискретностью 1.
Статья больше про cache miss, чем про HT.
Суть HT в том, что загруженные в кэш данные (разных потоков) могут обрабатываться одновременно, если для этого им нужны разные исполнительные блоки. Или, если один поток ждет чего-либо, то второй исполняется. В тесте данные загружаются. Ни какой обработки. Так при чем тут HT? У Вас и получился ожидаемый результат — два потока кода делят один кэш.
Поправьте меня, если я не прав.

Вы же наверняка догадываетесь, как Ваш третий тест может иметь такой же результат как и во втором тесте? Сократим количество областей памяти с 16 до 8 — по два потока на участок.
Так может все же статья про промахи кэша?

Плюс, почему то нет теста с четырьмя потоками.
Абсолютно верно — падение производительности вызывается промахами кэша. Принципиально то, что когда мы делим потоки по логическим процессорам одного ядра, промахи начинаются в два раза раньше (при блоках 16K), чем тогда, когда делим потоки по ядрам. Причина — у каждого ядра свой кэш L1, а для двух логических процессоров одного ядра он общий. Вот на этой особенности Hyper-Threading мы и акцентировали внимание.
Принципиально то, что когда мы делим потоки по логическим процессорам одного ядра, промахи начинаются в два раза раньше
На раньше. Если у вас два потока пользуются одним кэшем, то суммарно падение начинается при том же объеме обрабатываемых данных.

Вот на этой особенности Hyper-Threading мы и акцентировали внимание.
Я все равно не понимаю, почему это именно Hyper-Threading. Узким местом в задаче является только кэш. Хоть вы используете HT, хоть не используете, как только на одном кэше у вас обрабатывается данных больше, чем в него влазит, скорость падает. И падает она одинаково, используете вы один поток на одном логическом процессоре, два потока на разных логических процессорах, или же десять потоков на одном логическом процессоре. Без разницы.
При любом сценарии скорость падает, когда суммарный размер блоков, обрабатываемых одним физическим ядром, превышает размер кэш-памяти (в нашем случае 32К). Это может быть 32K одного потока (без HT) или сумма размеров блоков, обрабатываемых двумя логическими процессорами одного ядра. Акцентируем, что при оптимизации, размеры блоков следует выбирать в зависимости от типа распараллеливания Hyper-Threading или Multi-Core.

Факт, пусть и очевидный, но требующий численной оценки, которую мы и попытались сделать.
Это может быть 32K одного потока (без HT) или сумма размеров блоков, обрабатываемых двумя логическими процессорами одного ядра.
Или же нескольких потоков на одном ядре без всякого гипертрейдинга, что вы продолжаете игнорировать. Еще раз: дело не в гипертрейдинге, а в количестве данных, обрабатываемых на одном экземпляре кэша (в данном случае на одном ядре). Способ достижения порога в 32Кб роли не играет.
Способ достижения порога в 32К роли не играет. Но для программиста, при назначении заданий потокам, нужно оптимизировать размеры блоков, обрабатываемых потоками. И для этой оптимизации, важно знать мультипроцессорную топологию, в частности, используется ли Hyper-Threading?

Одного знания о количестве логических процессоров недостаточно. В нашем примере одна и та же маска Affinity Mask = 11111111b может соответствовать двум ситуациям:
1) 4 ядра 8 потоков, используется HT.
2) 8 ядер 8 потоков, не используется HT.
Чтобы выбрать оптимальный размер блока (в простейшем примере 16K или 32K), надо дифференцировать между ситуациями 1 и 2. У нас это вариант 1. Но та же маска может быть при варианте 2.

Вот на этой тонкости мы и акцентировали внимание.
По оси X откладывается размер блока, обрабатываемого каждым потоком.
Only those users with full accounts are able to leave comments. Log in, please.