Несколько streamов и кеш процессора

Я реализую операцию фильтрации изображений в C, используя несколько streamов и делая ее максимально оптимизированной. У меня есть один вопрос: если к памяти обращается stream-0, и одновременно, если к одной и той же памяти обращается stream-1, получит ли она ее из кеша? Этот вопрос связан с возможностью того, что эти два streamа могут работать в двух разных ядрах ЦП. Итак, еще один способ сделать это: все ли ядра имеют общую память?

Предположим, что у меня есть макет памяти, как показано ниже.

int output [100];

Предположим, что есть 2 ядра процессора, и поэтому я запускаю два streamа для совместной работы. Одна из схем может заключаться в том, чтобы разделить память на две части: 0-49 и 50-99, и каждый stream работает на каждом fragmentе. Другим способом может быть то, что thread-0 работает с четными индексами, например 0 2 4 и т. Д., Тогда как другой stream работает с нечетными индексами, такими как 1 3 5 …. Этот более поздний метод проще реализовать (специально для 3D данные), но я не уверен, могу ли я эффективно использовать кеш таким образом.

В общем, неплохо делиться перекрывающимися областями памяти, например, если один stream обрабатывает 0,2,4 … и другие процессы 1,3,5 … Хотя некоторые архитектуры могут поддерживать это, большинство архитектур не будет и вы, вероятно, не можете указать, на каких машинах будет работать ваш код. Кроме того, ОС может свободно назначать ваш код любому интересующему его ядру (один, два на одном физическом процессоре или два ядра на отдельных процессорах). Также каждый процессор обычно имеет отдельный кеш первого уровня, даже если он находится на одном процессоре.

В большинстве случаев 0,2,4 … / 1,3,5 … замедлят производительность чрезвычайно, возможно, медленнее, чем один процессор. Herb Sutters «Устранить ложное разделение» демонстрирует это очень хорошо.

Использование схемы [… n / 2-1] и [n / 2 … n] будет значительно улучшаться в большинстве систем. Это даже может привести к сверхлинейной производительности, поскольку размер кэша всех процессоров в сумме может быть, возможно, использован. Количество используемых streamов должно быть всегда настраиваемым и должно по умолчанию определять количество найденных ядер процессора.

Ответ на этот вопрос сильно зависит от архитектуры и уровня кэша, а также от того, где streamи фактически запущены.

Например, последние многоядерные процессоры Intel имеют кэши L1, которые относятся к одному ядру, и кэш L2, который распределяется между ядрами, находящимися в одном пакете ЦП; однако разные пакеты ЦП будут иметь свои собственные кэши L2.

Даже в случае, когда ваши streamи работают на двух ядрах в одном пакете, хотя, если оба streamа обращаются к данным в одной и той же строке, вы будете иметь эту кешлинку, отскакивающую между двумя кэшами L1. Это очень неэффективно, и вы должны разработать свой алгоритм, чтобы избежать этой ситуации.


Несколько комментариев спросили о том, как избежать этой проблемы.

В глубине души это не особенно сложно – вы просто хотите избежать двух streamов одновременно, пытаясь получить доступ к данным, находящимся в одной и той же строке кэша, где по крайней мере один stream записывает данные. (Пока все streamи только считывают данные, нет проблем – на большинстве архитектур данные, доступные только для чтения, могут присутствовать в нескольких кешах).

Для этого вам нужно знать размер строки кеша – это зависит от архитектуры, но в настоящее время большинство чипов семейства x86 и x86-64 используют строку с байтом в 64 байта (обратитесь к руководству по архитектуре для других архитектур). Вам также нужно знать размер ваших структур данных.

Если вы попросите своего компилятора выровнять интересующую структуру разделяемых данных с границей в 64 байта (например, output вашего массива), то вы знаете, что она начнется в начале строки кэша, и вы также можете вычислить, где последующая границы строки кэша. Если ваш int равен 4 байтам, то каждая строка кэша будет содержать ровно 8 значений int . Пока массив начинается с границы кешины, output[0] через output[7] будет находиться в одной строке кэша и output[8] на output[15] на следующем. В этом случае вы создадите свой алгоритм таким образом, чтобы каждый stream работал над блоком смежных значений int который кратен 8.

Если вы храните сложные типы struct а не plain int , полезность pahole будет pahole . Он проанализирует типы struct в скомпилированном двоичном файле и покажет вам макет (включая дополнение) и общий размер. Затем вы можете настроить свою struct с помощью этого вывода – например, вы можете вручную добавить некоторое дополнение, чтобы ваша struct была кратной размеру строки кеша.

В системах POSIX функция posix_memalign() полезна для выделения блока памяти с заданным выравниванием.

Возможно, я ошибаюсь, но зависит от того, общий кэш ядра или нет, зависит от реализации ЦП. Вам нужно будет найти технические листы на странице производителя, чтобы проверить, имеет ли каждый kernel ​​вашего процессора свой собственный кеш или общий доступ кэш.

Я также работал над обработкой изображений для охранной компании, и иногда у нас появились поврежденные изображения после запуска пакетных операций с streamами. После долгих исследований мы пришли к выводу, что кэш был разделен между CPU Core, и в редких случаях данные были перезаписаны или заменены неверными данными.

Будь то что-то, чтобы учесть или это редкое событие, которое я не могу ответить.