Я пытаюсь создать интуитивное представление о том, как написать эффективный код, который минимизирует CPI (количество циклов на инструкцию) и минимизирует потери в кэш-памяти и производительность с привязкой к серверу. Я хочу понять, как взаимодействуют локальность данных и конвейерная обработка.
Я понимаю, что многие из этих вещей зависят от конкретного оборудования, и невозможно ответить с уверенностью. Тем не менее, я надеюсь, что некоторые разумные рекомендации о том, что «вероятно» произойдет на «типичном» настольном компьютере, используют программу, скомпилированную с помощью общего компилятора, такого как gcc или icpc и -O2.
Рассмотрим следующий (надуманный) код. Целью этого кода является создание различных сценариев для иллюстрации вопроса. Давайте предположим, что строка кэша составляет 64 байта. (edit) - Для пояснения, давайте предположим, что ни одна из этих переменных не находится ни в одном уровне кэша во время выполнения calc. В ответе правильно указано, что если они уже кэшированы, это повлияет на результат.
class MyClass {
public:
MyClass() {};
inline void calc(const double in);
private:
double x,y[10],z[32],a,b;
};
inline void MyClass:calc(const double in)
{
x = 5 + in;
y[0] = 10 + in;
z[0] = 25 + in;
a = 50 + in;
q = 100 + in;//q is a variable from global scope that is not already in the cache
*pq = 200 + in;//*pq is a pointer from global scope that is not already in the cache
q2 = 300 + in;//q2 is a variable from global scope that is not already in the cache
b = 400 + in;
cout << x << ", " << y[0] << ", " << z[0] << ", " << a << ", " << q << ", " << *pq << ", " << q2 << "," << b;
}
Когда запустится calc, x и y [0], вероятно, находятся в одной строке кэша, поэтому y [0] будет доступен с попаданием в кэш? z [0] находится на следующей строке кэша. Тем не менее, он может извлечь выгоду из предварительной выборки «следующая строка кэша», а также стать хитом кэша? a находится на расстоянии нескольких строк кэша, а затем q является переменной из глобальной области видимости, которая находится в некотором удаленном месте в памяти. Даже если a - это несколько строк кэша от z [0], следует ли ожидать, что он будет загружен в процессор быстрее, чем q? Возможно, будет какая-то предварительная выборка на более высоком уровне кеша, которая, возможно, предотвратит полное отсутствие кеша? q, безусловно, потребует извлечения из основной памяти, так как это из удаленного места в памяти. * pq и q также потребуют отдельного извлечения из основной памяти.
Так что я ожидаю, что что-то подобное произойдет: y [0] будет загружаться с попаданием в кэш L1, z [0] может загружаться с попаданием в кэш L1 или L2, может быть или не быть попаданием в кэш L2, и q определенно будет отсутствовать в кеше. Что если q настолько далеко, что это также приводит к пропаданию кэша TLB? Тогда это будет еще медленнее? Правильно ли мое понимание всего этого?
Как конвейер влияет на это? Процессор может передавать серию загрузок памяти, передавая q из основной памяти в кэш до завершения предыдущей строки кода. Таким образом, на практике, мы бы наблюдали замедление от использования переменной q, которая находится в удаленном месте в памяти?
Обратите внимание, что calc встроен, поэтому его инструкции могут составлять часть большей цепочки операций в вызывающей его функции, которая, как я полагаю, помогла бы с конвейерной обработкой.
Как переменная * pq влияет на конвейеризацию? Компилятор не знает, является ли * pq указателем, указывающим на q2 или на b. Повлияет ли это на эффективность конвейерной обработки?
Наконец, мы приходим к б. Он находится на той же строке кэша, что и. Нам пришлось сделать несколько вещей с тех пор, как мы в последний раз использовали a, но, надеюсь, он все еще находится в кеше L1 и является хитом? Опять же, может ли использование указателя * pq (которое может указывать на b) повлиять на оптимизацию здесь?