Политики записи и продержка когерентности
Если бы ячейки памяти были доступны только на чтение, то их скэшированная копия всегда совпадала бы с оригиналам. Возможность записи (ну какая же программа обходится без операций записи?) рождает следующие проблемы: во-первых, кэш-контроллер должен отслеживать модификацию ячеек кэш-памяти, выгружая в основную память модифицированные ячейки при их замещении, а, во-вторых, необходимо как-то отслеживать обращения всех периферийных устройств (включая остальные микропроцессоры в многопроцессорных системах) к основной памяти. В противном случае, мы рискуем считать совсем не то, что записывали!
Кэш-контроллер обязан обеспечивать когерентность (coherency) – согласованность кэш-памяти с основной памятью. Допустим, к некоторой ячейке памяти, уже модифицированной в кэше, но еще не выгруженной в основную память, обращается периферийное устройство (или другой процессор) – кэш-контроллер должен немедленно обновить основную память, иначе оттуда почитаются "старые" данные. Аналогично, если периферийное устройство (другой процессор) модифицирует основную память, например посредством DMA, кэш-контроллер должен выяснить – загружены ли в модифицированные ячейки в его кэш-память, и если да – обновить их.
Поддержка когерентности – задача серьезная. Самое простое (но не самое лучшее) решение, мгновенно приходящее на ум, – кэшировать ячейки основной памяти только для чтения, а запись осуществлять напрямую, минуя кэш, сразу в основную память. Это, так называемая, сквозная (Write True write policy) политика. Сквозная политика легка в аппаратной реализации, но крайне неэффективна.
Частично компенсировать задержки обращения к памяти помогает буферизация. Записываемые данные на первом этапе попадают не в основную память, а в специальный буфер записи (store/write buffer), размером порядка 32-байт. Там они накапливаются до тех пор, пока буфер целиком не заполниться или не освободиться шина, а затем все содержимое буфера записывается в память "одним скопом". Такой режим сквозной записи с буферизацией (Write Combining write policy) значительно увеличивает производительность системы, но решает далеко не все проблемы.
В частности, значительная часть процессорного времени по-прежнему расходуется именно на выгрузку буфера в основную память. Тем более обидно, что в подавляющем большинстве компьютеров установлен всего один процессор и именно он, а не периферия, интенсивнее всех работает с памятью – не слишком ли дорого обходится поддержка когерентности?
Более сложный (но и совершенный!) алгоритм реализует обратная политика записи (Write Back write policy), до минимума сокращающая количество обращений к памяти. Для отслеживания операций модификации с каждой ячейкой кэш-памяти связывается специальный флаг, называемый флагом состояния. Если кэшируемая ячейка была модифицирована, то кэш-контроллер устанавливает соответствующий ей флаг в грязное (dirty) состояние. Когда периферийное устройство обращается к памяти, кэш-контроллер проверяет – находится ли соответствующий адрес в кэш-памяти и если да, тогда он, глядя на флаг, определяет: грязная она или нет? Грязные ячейки выгружаются в основную память, а их флаг устанавливается в состояние "чисто" (clear). Аналогично – при замещении старых кэш-строк новыми, кэш-контроллер в первую очередь стремится избавиться от чистых кэш-строк, т.к. они могут быть мгновенно удалены из кэша без записи в основную память. И только если все строки грязные – выбирается одна, наименее ценная (с точки зрения политики замещения данных) и "сбрасывается" в основную память, освобождая место для новой, "чистой" строки.
Таким образом, операция записи ячейки занимает от 1 такта процессора до 14-16 тактов системной шины. Такой разнобой объясняется очень просто – если кэш-контроллер поддерживает обратную политику записи, а к записываемой ячейке долгое время никто не обращается и не вытесняет ее из кэша, - кэш-контроллер "сбросит" ее в основную память во время простоя шины, нисколько не отнимая процессорного времени. И всего-то потребуется один такт на запись ячейки в кэш-память. В противном случае (если одно из вышеперечисленных условий не выполняется) расходуется 4-1-1-1 (или 5-1-1-1) тактов системной шины на запись ячейки в основную память и потом еще столько же на загрузку ее в кэш.