Выход из кэша второго уровня (мнимый)
Придерживаясь самых общих рассуждений о природе кэша, попытаемся определить его размер по характеру изменения скоростного показателя с ростом размера обрабатываемого блока. Первое, что сразу бросается в глаза – внезапный скачек кривой удельной скорости записи при пересечении отметки в ~128 Кб. Синхронно с ней изменяется и кривая чтения, пускай ее излом и менее ярко выражен, но он все-таки есть. Выходит, размер кэша второго уровня составляет 128 Кб? Но это не согласуется с показаниями BIOS, которая оценивает его размер в 512 Кб, что в четверо больше! Нас надули?! Или кэш работает неправильно? Можно, например, предположить, что кэш состоит из нескольких микросхем статической памяти с различным временем доступа…
Но не спешите возвращать материнскую плату обратно к продавцу! Она вполне исправна и полностью соответствует заявленным характеристикам. Полученный же результат объясняется тем, что ячейки кэшируемой памяти могут соответствовать не любым, а строго определенным кэш-линейкам. Несмотря на то, что свободное место в кэше еще есть, обрабатываемый блок по мере его роста начинает претендовать на кэш-линейки, занятые другим "хозяйствующим субъектов". Это может быть и код (впрочем, в нашем случае основной цикл целиком помещается в сверхоперативной памяти первого уровня), и стек, и интенсивно используемые переменные.
Наша программа, действительно, интенсивно использует стек вызывая каждый раз функции A1 и A2 для замера временных интервалов выполнения цикла, сохраняя результат в локальной переменной buff (см. исходный текст программы). Наконец, около шести килобайт требует для своей работы функция printf, не говоря уже о системе ввода-вывода операционной системы и переключении задач. Если размер обрабатываемого блока превышает размер кэш-памяти первого уровня, то содержимое стека вместе с локальными переменными неизбежно вытиснится в кэш-память второго уровня, причем, в данном случае сложилось так, что стек вкупе с локальными переменными и обрабатываемые данные отображаются на один и те же кэш линейки, что приводит к их постоянным замещениям.
Выходит, мало уложится кэш второго уровня, необходимо еще и ухитрится эффективно распределить свободное пространство между обрабатываемыми данными, стеком и кодом! Это – одна из сложнейших задач оптимизации, в общем виде не имеющая решения. Не прибегая к анализу кода операционной системы и всех запущенных задач, невозможно определить интенсивность использования тех или иных кэш-линеек, а, значит, невозможно спланировать и оптимальную стратегию размещения обрабатываемых данных. Эффективная емкость кэша второго уровня только в исключительных случаях совпадает с его физической емкостью и чем меньше ассоциативность кэша, тем выше вероятность возникновения взаимных конфликтов.
Планируя размеры структур данных, многие программисты часто забывают, что в их распоряжении не весь кэш второго уровня, а только часть его. Другая же часть принадлежит коду программы. Если интенсивно используемые циклы не умещаются в кодовом кэше первого уровня и постоянно вытесняются обрабатываемыми ими данными из второго – производительность не замедлит упасть так, что домкратном не поднимаешь!
Впрочем, в данном случае цикл обработки вращается глубоко в кэше первого уровня, и ступенька принадлежит… стеку! Да, раз размер обрабатываемого блока превышает размер кэша данных первого уровня, ячейки памяти, принадлежащие стеку, вынуждены постоянно вытесняться в кэш второго, а, затем, по мере "распухания" обрабатываемого блока, и вовсе отправляться в основную память! Но что обозначает горизонтальная верхушка ступеньки?
Давайте подумаем: раз скоростной показатель не меняется с ростом размера обрабатываемого блока, значит, соответствующие кэш-линейки никто не использует, в противном случае наблюдалось бы нарастающие падание производительности.