Страница 2 из 4 Когерентность кэш-памяти Да, текущие двухъядерные процессоры AMD и Intel не имеют «общей» кэш-памяти, используя которую одно ядро могло бы увеличивать свою производительность за счёт другого. Можно даже встретить мнение, что, мол, ничего принципиально интересного поэтому в двухъядерниках AMD нету. Но процессоры архитектуры K8 способны эффективно «лазить» в кэш-память своих соседей, не обращая внимания на то, что кэши у них раздельные! Просто нужно обратить внимание на скромную строчку в перечне характеристик кэша – где в графе «протокол поддержания когерентности кэшей» у процессоров AMD записано «MOESI». Что вообще такое «протокол поддержания когерентности кэшей»? Пусть, например, у нас процессор CPU0 произвел какие-то вычисления и записал полученный результат в оперативную память. Поскольку у CPU0, разумеется, есть «своя», персональная кэш-память (хотя бы первого уровня), то запись данных производится не в «тормозную» оперативную память, а в кэш. В случае однопроцессорных систем эта схема замечательно работает… но что будет, если в многопроцессорной системе та же самая ячейка памяти, которую изменил процессор CPU0, для каких-то целей понадобится и процессору CPU1? В «лучшем» случае CPU1 прочитает эту ячейку из оперативной памяти, куда, если повезет, процессор CPU0 уже успеет сохранить её новое значение. В худшем – эта ячейка (с устаревшими данными) окажется в его кэше и CPU1 даже не будет пытаться выяснить, изменилось ли что-нибудь с тех пор, как он в последний раз эту ячейку из оперативной памяти прочитал. Всё вместе называется «проблемой когерентности кэшей», а методы её решения – как раз и называют соответствующими «протоколами». Как эту проблему решают? Простейший протокол поддержания когерентности – это так называемый Write-Through, когда любые изменения сразу же записываются (write through cache) не только в кэш-память, но и в оперативную память компьютера; причем остальные процессоры как-то об этом знаменательном событии информируются. Например, если используется общая шина, то другие процессоры просто «подслушивают» (snoop), что текущий «владелец» шины по ней пересылает и, зарегистрировав, что CPU0 выполняет операцию записи в память, обновляют «свои» записи в кэше. Обычно, чтобы сэкономить время - просто помечают, что соответствующие строки в кэше отныне «неправильные» (Invalid) и при обращении к ним данные необходимо брать не из кэша, а из оперативной памяти. Схема достаточно простая... но неэффективная: запись данных в оперативную память – далеко не быстрый процесс. Но зачем разбазаривать зря ресурсы компьютера (пропускную способность шины и оперативной памяти), сохраняя малейшие изменения в медленной оперативной памяти, если вся эта «обновленная» информация может еще десять раз обновиться, прежде чем она понадобится кому-то кроме «владеющего» этой информацией процессора? А ведь если поразмыслить, то работа нескольких процессоров одновременно с одним и тем же участком оперативной памяти – явление исключительное: в подавляющем большинстве случаев каждый процессор занимается обработкой «своего» участка и лишь изредка – обращается к участку «чужому»2. Именно эта идея положена в основу протокола MESI. Использующий его процессор, четко различает кэш-строки, которых заведомо нет в кэшах других процессоров (они помечаются как Exclusive) и «общие», присутствующие более чем в одном кэше (они помечаются как Shared). Если изменяется Shared-строка, то «соседям» по компьютеру передается сигнал-требование проверить свои кэши и, если что, сделать напротив своих записей в кэшах пометку «неправильно» (Invalid); если изменяется Exclusive-строка (а как уже говорилось, вероятность этого события очень велика), то делать эту достаточно трудоемкую операцию нет необходимости. В любом случае строка помечается как Modified, но попыток эту самую строку немедленно записать в оперативную память не предпринимается. Зато все процессоры бдительно наблюдают за всеми операциями чтения данных из памяти и если один из процессоров замечает, что его сосед пытается прочитать строчку памяти, которая в его кэше помечена, как «Modified», то он прерывает эту операцию, сохраняет изменения в оперативной памяти, снимает со «своей» кэш-строки «галочку» Modified и только после этого разрешает вызвавшему «проблему» процессору завершить операцию чтения. В итоге «типовые» операции с кэш-памятью в MESI происходят практически с той же скоростью, что и в single-системе. А что же AMD? AMD использует всё тот же MESI, но только доработанный таким образом, чтобы процессоры могли эффективно использовать данные из кэш-памяти друг друга. В MESI процессоры почти не используют кэши своих соседей: в лучшем случае, чтение обновленных данных из памяти производится при выгрузке этих данных в память «соседом». В протоколе же MOESI любая операция чтения сопровождается проверкой кэшей соседей – если нужные данные находятся в одном из них, то и читаются они прямо оттуда; причем сохранение этих данных в оперативную память при этом не производится. Просто «владелец» этой памяти делает у себя «зарубку» Owner3 напротив соответствующей строки кэш-памяти. Мы убиваем двух зайцев одним выстрелом: не производим ненужных записей в оперативную память и эффективно используем память «соседа». В итоге в двухядерных процессорах кэш-память первого и второго уровней «соседнего ядра» может работать как кэш-память третьего уровня (L3); а кэш-память «чужих» процессоров в многопроцессорной системе – как кэши четвертого уровня. Конечно, это не «полноценный» кэш L3/L4 и уж точно – не полноценный разделяемый L2 (поскольку самостоятельно загрузить «соседа» своими данными ядро процессора не может – оно может только надеяться, что «сосед» тоже эти данные будет использовать), однако свою прибавку к производительности, безусловно, MOESI должен обеспечивать. Только не следует думать, что в Intel не могут реализовать аналог протокола, предложенного еще аж в 1986 году (и определенные варианты таки присутствует в Itanium-системах и некоторых других ядрах) – просто для систем с общей системной шиной никаких преимуществ по сравнению с MESI он не предоставляет. Что просто перебросить по этой медленной общей шине данные от одного процессора к другому, что эти данные заодно и в оперативную память записать – разницы почти никакой. А вот для процессоров AMD, у которых ядра и процессоры связаны между собой интерфейсами, работающими гораздо быстрее обычной оперативной памяти, разница есть и порой она становится весьма ощутимой. | Система на основе Pentium 4 supporting Hyper-Threading | Чтобы всё не выглядело настолько неприглядно для Intel, заметим, что у этой корпорации тоже есть очень сильный козырь в кармане – и называется он Hyper-Threading. Технология виртуальной многопоточности (когда одно физическое ядро изображает из себя несколько «виртуальных») позволяет гораздо эффективнее загрузить исполнительные блоки этих процессоров работой. Мало кто знает, что IPC (количество инструкций, которые можно выполнить за один такт) для этих процессоров в устоявшемся режиме может доходить до четырех. Это не просто много: это очень много даже по современным меркам. Для сравнения, процессоры AMD архитектур K7 и K8 выполняют по 3 (в пике – до 6) инструкций за такт, то есть теоретическая эффективность в пересчете на один мегагерц у Pentium 4 должна быть выше, чем у Athlon 64 и Opteron. Почему КПД архитектуры NetBurst в ряде приложений получается «очень скромным» - вопрос для немаленькой отдельной статьи (да и написано по этому поводу уже очень много), отметим только, что сверхвысокие IPC для Pentium 4 вполне можно увидеть на практике – на тщательно оптимизированных приложениях и тестах типа Linpack-а или Prime 95. Главное – это то, что Hyper-Threading позволяет существующий КПД процессора на 10-25% увеличить: подобная прибавка вполне способна перекрыть и выигрыш от более быстрой подсистемы памяти, и от более быстрой шины и от «более продвинутого» протокола MOESI. |