В чем смысл когерентности кэша?

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

6 ответов


представьте, что вы делаете это:

lock(); //some synchronization primitive e.g. a semaphore/mutex
globalint = somevalue;
unlock();

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

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


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

Если кэши не когерентны," явные шаги " должны были бы обеспечить когерентность - явные шаги обычно являются такими вещами, как критические разделы/мьютексы(например, volatile в C/C++ достаточно редко) . Это довольно сложно, если не невозможно для таких услуг, как мьютексы для отслеживания только памяти, которая имеет изменения и должна быть обновлена во всех кэшах-вероятно, ей придется обновить всю память, и это если бы она могла даже отслеживать, какие ядра имеют какие части этой памяти в своих кэшах.

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

и, представьте себе процесс, работающий на core 1, и получает preempted. Когда он снова запланирован, он запланирован на core 2.

Это было бы довольно фатально, если бы кэши не были выбраны, так как в противном случае могли бы быть остатки данных процесса в кэше core 1, который не существует в кэше core 2. Хотя, для систем, работающих таким образом, ОС должна была бы обеспечить согласованность кэша по мере планирования потоков , что, вероятно, было бы операцией "обновить всю память в кэшах между всеми ядрами", или, возможно, она могла бы отслеживать грязные страницы с помощью MMU и только синхронизировать страницы памяти, которые были изменены-опять же, оборудование, вероятно, держать кэши когерентным в более тонко и эффективным способом.


есть некоторые нюансы, не охваченные большими ответами от других авторов.

во-первых, подумайте, что процессор не имеет дело с памятью байт за байтом, а с линиями кэша. Линия может иметь 64 байта. Теперь, если я выделить 2 байта кусок памяти в положения P, и другой процессор выделяет 8 байт часть памяти на месте П + 8, и оба P и P + 8 живем на одной кэш-линии, отмечают, что без кэш-когерентности двух процессоров не может одновременно обновлять P и P + 8 без избиения друг друга меняется! Поскольку каждый процессор делает чтение-изменение-запись в строке кэша, они могут оба выписать копию строки, которая не включает изменения другого процессора! Последний писатель победит, и одна из ваших модификаций памяти "исчезнет"!

другое, что нужно иметь в виду, - это различие между последовательностью и последовательностью. Поскольку даже производные процессоры x86 используют буферы хранилища, нет гарантий, которые можно было бы ожидать инструкции, которые уже были изменены в памяти таким образом, что другие процессоры могут видеть эти изменения, даже если компилятор решил записать значение в память (возможно, из-за volatile?). Вместо этого моды могут сидеть в буферах магазина. Почти все процессоры в общем использовании являются когерентными, но очень немногие процессоры имеют модель согласованности, которая так же простительна, как x86. Проверьте, например, http://www.cs.nmsu.edu / ~pfeiffer/classes/573/notes/consistency.html для получения дополнительной информации по этой теме.

надеюсь, это поможет, и кстати, я работаю в Corensic, компании, которая создает отладчик параллелизма, который вы можете проверить. Это помогает собрать кусочки, когда предположения о параллелизме, когерентности и согласованности оказываются необоснованными :)


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


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


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

Он также убеждается что код наследия многопоточный работает как на новых моделях процессоров / мулти процессорные системы, без внесения каких-либо изменений в код для обеспечения согласованности данных.