Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Аппаратное обеспечение

Что каждый программист должен знать о памяти. Часть 5.

Оригинал: What every programmer should know about memory. Memory part 5: What programmers can do.
Автор: Ulrich Drepper
Дата публикации: 23.10.2007
Перевод: Капустин С.В.
Дата перевода: 30.10.2009

6. Что могут делать программисты - оптимизация кэша

6.2 Доступ к кэшу

6.2.3 Оптимизация доступа к кэшу уровня 2 и выше

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

  • промахи кэша всегда очень дороги. В то время как промахи L1 (вероятно) часто будут найдены в L2 или более высоких уровнях кэша, ограничивая, следовательно, ущерб, у кэша последнего уровня такого резерва, очевидно, нет.
  • кэш уровня L2 и выше часто является общим для нескольких ядер и/или гиперпотоков. Следовательно эффективный размер кэша, доступный для каждого модуля исполнения, обычно меньше, чем общий размер кэша.

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

Программа должна делать свою работу, даже если набор данных очень велик. Это задача программиста - организовать работу так, чтобы минимизировать промахи кэша. Для кэшей последного уровня, так же как и для L1, это возможно, если разбивать задачу на маленькие кусочки. Это очень похоже на оптимизацию умножения матриц в таблице 6.2. Одно различие состоит в том, что блоки данных, над которыми нужно работать, могут быть больше. Код становится ещё сложнее, если оптимизация работы с L1 также нужна. Вообразите умножение матриц, при котором наборы данных (две исходные матрицы и матрица результата) не помещаются вместе в кэш последнего уровня. В этом случае возможно придется оптимизировать доступ к L1 и кэшу последнего уровня одновременно.

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

Для кэшей высокого уровня это не так, если предполагается, что программа будет универсальной. Размеры этих кэшей могут варьироваться в широких пределах. Различие в 8 раз не является чем-то необычным. Невозможно предположить большой размер кэша как значение по умолчанию, так как это будет означать, что код будет выполняться плохо на всех машинах, кроме тех, у которых действительно такой большой кэш. Противоположный выбор также плох - предполагая самый маленький кэш, мы отбросим 87% кэша или больше. Это плохо, как мы можем увидеть из рисунка 3.14, использование большого кэша может иметь огромный эффект на скорость программы.

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

Как только у нас есть формула требований памяти, мы можем сравнить её с размером кэша. Как упоминалось ранее, кэш может быть общим для нескольких ядер. В настоящее время {Определенно когда-нибудь будет лучшее решение!} единственный путь получить корректную информацию без знания тонкостей устройства аппаратуры - это через файловую систему /sys. В таблице 5.2 мы видим, что ядро сообщает об аппаратном обеспечении. Программа должна найти директорию:

/sys/devices/system/cpu/cpu*/cache

для кэша последнего уровня. Её можно узнать по наивысшему числовому значению в файле level этой директории. Когда директория идентифицирована, программа должна прочитать содержимое файла size в этой директории и разделить числовое значение на число бит, заданное в маске в файле shared_cpu_map.

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


Назад Оглавление Вперед