Я пытаюсь выяснить, сколько тактов или общих инструкций требуется для доступа к указателю в C. Я не думаю, что знаю, как выяснить, например, p-> x = d-> + f-> b
я принял бы две загрузки на указатель, просто предположив, что будет загрузка для указателя и загрузка для значения. Таким образом в этом операции, разрешение указателя было бы намного большим фактором, чем фактическое дополнение, до попытки ускорить этот код, правильно?
Это может зависеть от компилятора и реализованной архитектуры, но является мной на правильном пути?
Я видел некоторый код, где каждое значение, используемое в, говорит, 3 дополнения, прибыл из a
f2->sum = p1->p2->p3->x + p1->p2->p3->a + p1->p2->p3->m
тип структуры, и я пытаюсь определить, как плохо это
Это зависит от используемой архитектуры.
Некоторые архитектуры могут ссылаться на память / разыменовать память для инструкции без предварительной загрузки ее в регистр, другие - нет. Некоторые архитектуры не имеют понятия инструкций, которые вычисляют смещения для вас для разыменования и заставят вас загрузить адрес памяти, добавить к нему ваше смещение, а затем позволить вам разыменовать местоположение в памяти. Я уверен, что есть больше отклонений от чипа к чипу.
Как только вы их преодолеете, каждая инструкция также займет разное количество времени в зависимости от архитектуры. Честно говоря, это очень и очень минимальные накладные расходы.
Что касается вашего непосредственного вопроса о разыменовании цепочки элементов, то медлительность будет заключаться в том, что чем дальше вы продвигаетесь в цепочке разыменования, тем вероятнее будет плохая локализация ссылки. Это означает большее количество промахов в кэше, что означает большее количество обращений в основную память (или на диск!) Для получения данных. Основная память очень медленная по сравнению с процессором.
Зависит от того, что вы делаете, тривиальное разыменование указателя y = * z;
где
int x = 1;
int* z = &x;
int y;
может собираться в нечто подобное на x86:
mov eax, [z]
mov eax, [eax]
mov [y], eax
и y = x
все равно потребуют разыменования памяти:
mov eax, [x]
mov [y], eax
Инструкции перемещения в память занимают около 2-4 циклов IIRC.
Хотя, если вы загружаете память из совершенно случайных мест, вы будете вызывать множество ошибок страниц, что приведет к потере сотен тактовых циклов.
Там, где это возможно, компилятор удалит эти накладные расходы, сохраняя повторно используемые базовые местоположения в регистре (например, p1-> p2-> p3
в вашем примере).
Однако иногда компилятор не может определить, какие указатели могут быть псевдонимами других указателей, используемых в вашей функции - это означает, что он должен вернуться в очень консервативное положение и часто перезагружать значения из указателей.
Здесь может помочь ключевое слово C99 restrict
. Он позволяет сообщать компилятору, когда определенные указатели никогда не имеют псевдонимов других указателей в области действия функции, что, следовательно, может улучшить оптимизацию.
Например, возьмем эту функцию:
struct xyz {
int val1;
int val2;
int val3;
};
struct abc {
struct xyz *p2;
};
int foo(struct abc *p1)
{
int sum;
sum = p1->p2->val1 + p1->p2->val2 + p1->p2->val3;
return sum;
}
В gcc 4.3.2 с уровнем оптимизации -O1
он компилируется в этот код x86:
foo:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
movl (%eax), %edx
movl 4(%edx), %eax
addl (%edx), %eax
addl 8(%edx), %eax
popl %ebp
ret
Как вы можете видеть, он только учитывает ] p1
один раз - он сохраняет значение p1-> p2
в регистре % edx
и использует его трижды для выборки трех значений из этой структуры.
Некоторые IDE, такие как VisualStudio, позволяют просматривать сгенерированную сборку вместе с исходным кодом.
Как просмотреть сборку, стоящую за кодом, с помощью Visual C ++?
Тогда вы сможете увидеть, как это выглядит для вашей точной архитектуры и реализации.
Если вы используете GDB (linux, mac), используйте disassemble
(gdb) disas 0x32c4 0x32e4
Dump of assembler code from 0x32c4 to 0x32e4:
0x32c4 <main+204>: addil 0,dp
0x32c8 <main+208>: ldw 0x22c(sr0,r1),r26
0x32cc <main+212>: ldil 0x3000,r31
0x32d0 <main+216>: ble 0x3f8(sr4,r31)
0x32d4 <main+220>: ldo 0(r31),rp
0x32d8 <main+224>: addil -0x800,dp
0x32dc <main+228>: ldo 0x588(r1),r26
0x32e0 <main+232>: ldil 0x3000,r31
End of assembler dump.