Skip to content

Планировщик ОС: линии кэша и ложный обмен

Доступ к данным из основной памяти сопряжен с такой высокой задержкой (от 100 до 300 тактов), что процессоры и ядра имеют локальные кэши, чтобы хранить данные рядом с аппаратными потоками, которым они нужны. Доступ к данным из кэшей обходится гораздо дешевле (от ~3 до ~40 тактов) в зависимости от доступа к кэшу. Сегодня одним из аспектов производительности является то, насколько эффективно ты можешь передавать данные в процессор, чтобы уменьшить эти задержки доступа к данным. При написании многопоточных приложений, изменяющих состояние, необходимо учитывать механику системы кэширования.

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

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

Ложный обмен

Предположим, что ядро 0 обращается к A, а ядро 1 обращается к A+1.

  • Независимые фрагменты памяти; одновременный доступ безопасен.

  • Но A и A+1 обычно сопоставляются с одной и той же линией caсhe: 

  • если это так, ядро 0 записывает в A, проверяет линию caсhe A+1 в ядре 1.

  • и наоборот;

  • это ложный обмен.

Если один поток на этом ядре вносит изменения в свою копию строки кэша, то с помощью аппаратного волшебства все остальные копии той же строки кэша должны быть помечены как грязные. Когда поток пытается получить доступ для чтения или записи к грязной строке кэша, требуется доступ к основной памяти (от ~100 до ~300 тактов), чтобы получить новую копию строки кэша.

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

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

О проблеме когерентности кэша ты можешь узнать подробнее по ссылке.