Сколько инструкций для доступа к указателю в C? - PullRequest
5 голосов
/ 01 мая 2010

Я пытаюсь выяснить, сколько тактов или полных инструкций требуется для доступа к указателю в C. Я не думаю, что знаю, как вычислить, например, p-> x = d-> a + f-> б

Я бы предположил две загрузки на указатель, просто предполагая, что будет нагрузка для указателя и нагрузка для значения. Таким образом, в этих операциях разрешение указателя будет гораздо большим фактором, чем фактическое добавление, если попытаться ускорить этот код, верно?

Это может зависеть от реализованного компилятора и архитектуры, но я на правильном пути?

Я видел некоторый код, в котором каждое значение, использованное, скажем, в 3 дополнениях, получено из

 f2->sum = p1->p2->p3->x + p1->p2->p3->a + p1->p2->p3->m

тип структуры, и я пытаюсь определить, насколько это плохо

Ответы [ 4 ]

8 голосов
/ 01 мая 2010

Это зависит от имеющейся архитектуры.

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

После того, как вы пройдете через них, каждая инструкция также будет варьироваться в зависимости от архитектуры. Честно говоря, это очень, очень минимальные накладные расходы.

Что касается вашего немедленного вопроса о разыменовании цепочки элементов, то медлительность будет заключаться в том, что, вероятно, существует плохая локация ссылок, чем дальше вы идете в цепочке разыменования. Это означает, что больше кешей пропадает, что означает больше обращений к основной памяти (или к диску!) Для получения данных. Основная память очень медленная по сравнению с процессором.

2 голосов
/ 01 мая 2010

Некоторые 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.
1 голос
/ 01 мая 2010

Где это возможно, компилятор удалит эти накладные расходы за вас, сохраняя в регистре многократно используемые базовые расположения (например, 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 и использует его три раза для извлечения трех значений из этой структуры.

1 голос

Зависит от того, что вы делаете, разыменование тривиального указателя 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.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...