Рекомендации по предварительной выборке кеша обычно испускают специальную инструкцию prefetch
, которая советует prefetcher, что эта часть памяти понадобится в ближайшем будущем. Префект может (или не может) принять этот совет. Таким образом, в этом смысле предварительная выборка программного обеспечения не связана с «управлением кешем» или «управлением кешем».
Насколько мне известно, ни одна широко распространенная архитектура набора инструкций не содержит инструкций по выходу определенной строки кэша или, например, для приведения определенной части памяти в строку кэша. Точка кэша в большинстве современных архитектур должна быть прозрачной для программиста.
Вы можете, однако, писать программы, которые не относятся к кешированию. Это все о пространственной и временной локальности обоих данных и инструкции:
Пространственная локальность в отношении данных означает, что вы должны стремиться к последовательным доступ к памяти, не слишком далеко друг от друга , Это самая естественная вещь для оптимизации.
Пространственная локальность в отношении инструкций означает, что прыжки и ветви не должны заходить слишком далеко в код. Составители и линкеры должны попытаться это сделать.
Временная локальность относительно данных означает доступ к одним и тем же ячейкам памяти (хотя, возможно, не близко друг к другу) за один временной срез. Определение того, как долго этот фрагмент времени может быть сложным;
Временная локальность в отношении инструкций означает, что даже если код перескакивает на большие расстояния, он перескакивает через одни и те же места в одном временном фрагменте. Это, как правило, очень неинтуитивно и не очень полезно для оптимизации.
Как правило, вы должны оптимизировать данные и не столько инструкции местности. В большинстве программ, чувствительных к производительности, объем данных намного превышает объем кода.
Кроме того, в отношении нескольких сердечников вы должны стараться избегать ложного обмена и максимально использовать поточное локальное хранилище. Имейте в виду, что каждое ядро ЦП имеет свой собственный выделенный кеш, и такие вещи, как перехват строки кэша между кешами ядер, могут иметь очень отрицательный эффект.
Чтобы проиллюстрировать ложное разделение, рассмотрим следующий код:
int counts[NUM_THREADS]; // a global array where each thread writes to its slot
...
for (int i = 0; i < NUM_THREADS; ++i) {
spawn_thread(thread_start);
}
...
void thread_start(void)
{
for (a_large_number_of_iterations) {
int some_condition = some_calculation();
if (some_condition) {
counts[THREAD_ID]++;
}
}
}
Каждая из нитей модифицирует элемент массива counts
с высокой частотой. Проблема состоит в том, что отдельные элементы массива смежны, а большие группы из них будут падать на одну линию кэша. При типичной кэш-строке 64 байта, а типичный размер int
составляет 4 байта, это означает, что одна строка кеша может освободить место для 16 элементов. Когда несколько ядер обновляют только их 4-байтовый счет, они также недействительны для соответствующей строки кэша в других ядрах, что приведет к отскоку линии кэша между ядрами, хотя потоки, по-видимому, используют независимые ячейки памяти.
Привет, спасибо большое! Об этом _false sharing_: Я полагаю, что то, что мы можем сделать на уровне программирования, связано с механизмами синхронизации ... Можете ли вы дать мне пример с ложным совместным использованием? – franco
Что касается вашей второй пули: программист может помочь компилятору/компоновщику, написав кэш-код, компактный и, по существу, встроенный в него с несколькими прыжками и вызовами внутри него. Что касается четвертой пули: это непросто, но это отнюдь не невозможно. –