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

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

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

int output [100];

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

Ответы

Ответ 1

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

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

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

Ответ 2

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

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

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


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

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

Документация Intel

Intel публикует таблицы данных для каждого поколения, которые могут содержать такую информацию.

Например, для процессора i5-3210M, который был у меня на моем старом компьютере, я смотрю в третьем поколении - Техническое описание тома 1 3.3 "Технология Intel Hyper-Threading (технология Intel HT)" гласит:

Процессор поддерживает технологию Intel Hyper-Threading (Intel HT Technology), которая позволяет исполнительному ядру функционировать как два логических процессора. Хотя некоторые ресурсы выполнения, такие как кэши, исполнительные блоки и шины, являются общими, каждый логический процессор имеет свое собственное архитектурное состояние с собственным набором регистров общего назначения и управляющих регистров.

который подтверждает, что кэши являются общими в данной гиперпотоке для этого поколения процессоров.

Смотрите также: