Подтвердить что ты не робот

Насколько дорого стоит разыменовать указатель?

Насколько дорого стоит выполнить операцию разыменования указателя?

Я могу себе представить, что передача памяти как-то пропорциональна размеру объекта, но я хочу знать, насколько дорогой является операция операции разыменования.

4b9b3361

Ответ 1

Выделение имен при переходе на машинный код может означать разные вещи в зависимости от того, что вы делаете с разыменованным объектом. Доступ к одному члену класса с помощью указателя обычно дешевый. Например, если c является указателем на экземпляр class C с элементом int n, то что-то вроде этого:

int n = c->n;

Можно перевести на одну или две машинные инструкции и загрузить регистр с одним доступом к памяти.

С другой стороны, это подразумевает создание полной копии объекта, на который указывает c:

C d = *c;

Стоимость этого будет зависеть от размера C, но обратите внимание, что именно копия является основным расходом, а часть "разыменования" на самом деле просто "использует" адрес указателя в инструкциях копирования.

Обратите внимание, что для доступа к элементам больших объектов обычно требуется вычисление смещения указателя и доступ к памяти, независимо от того, является ли объект локальным объектом или нет. Как правило, только очень маленькие объекты оптимизированы для работы только в регистрах.

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

Ответ 2

Это зависит от того, что вы делаете с разыменованным указателем. Просто операция разыменования ничего не делает сама по себе. Он просто получает lvalue типа T, который представляет ваш объект, если ваш указатель является T*

struct a {
    int big[42];
};

void f(a * t) {
    // does nothing. Only interesting for standard or compiler writers.
    // it just binds the lvalue to a reference t1. 
    a & t1 = *t; 
}

Если вы действительно получаете значение из этого объекта, обозначенного lvalue, возвращаемое операцией разыменования, компилятор должен скопировать данные, содержащиеся в этом объекте. Для простого POD это просто просто memcpy:

a aGlobalA;
void f(a * t) {
    // gets the value of of the object denoted by *t, copying it into aGlobalA
    aGlobalA = *t; 
}

Мой gcc-порт выводит этот код для f:

    sub     $29, $29, 24       ; subtract stack-pointer, creating this frame
    stw     $31, $29, 20       ; save return address
    add     $5, $0, $4         ; copy pointer t into $5 (src)
    add     $4, $0, aGlobalA   ; load address of aGlobalA into $4 (dst)
    add     $6, $0, 168        ; put size (168 bytes) as 3rd argument
    jal     memcpy             ; call memcpy
    ldw     $31, $29, 20       ; restore return address
    add     $29, $29, 24       ; add stack-pointer, destroying this frame
    jr      $31

Оптимизированный машинный код будет использовать встроенный код вместо вызова memcpy, но это действительно просто деталь реализации. Важно то, что просто *t не выполняет какой-либо код, но доступ к значению этого объекта действительно необходимо скопировать.

Должны ли мы иметь дело с типом, имеющим определенный пользователем оператор присваивания копии, дела сложнее:

struct a {
    int big[42];
    void operator=(a const&) { }
};

Код для той же функции f теперь выглядит следующим образом:

    sub     $29, $29, 8
    add     $29, $29, 8
    jr      $31

Хах. Но это не было таким сюрпризом, не так ли? В конце концов, компилятор должен называть наш operator=, и если он ничего не делает, вся функция также ничего не делает!

Заключение

Я думаю, что вывод, который мы можем сделать, зависит от того, как используется возвращаемое значение operator*. Если у нас есть только указатель, который мы разыскиваем, мы видим выше, что генерируемый код во многом зависит от обстоятельств. Я не показал, как он ведет себя, если мы разыграем тип класса с перегруженным operator*. Но, по сути, он просто ведет себя так, как мы видели с помощью operator=. Все измерения выполнялись с помощью -O2, поэтому компилятор правильно встраивал вызовы:)

Ответ 3

Наиболее важным фактором в указателях разыменования обычных систем является то, что вы, вероятно, создадите пропущенную кеш. Случайный доступ в памяти SDRAM стоит десятки наносекунд (например, 64). На процессорах gigaherz это означает, что ваш процессор простаивает сотни (или > тысяч) циклов, не имея возможности делать что-либо еще в то же время.

Только в системах на базе SRAM (которые вы найдете только в встроенном программном обеспечении) или когда оптимизируется ваше программное обеспечение, факторы, обсуждаемые в других сообщениях, вступают в игру.

Ответ 4

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

Ответ 5

Процессы разыменования (многократного) ценообразования.

Вместо записи:

string name = first->next->next->next->name;
int age = first->next->next->next->age;

this is O(n)


Напишите его как:

node* billy_block = first->next->next->next;

string name = billy_block->name;
int age = billy_block->age;

this is O(1)

Таким образом, ваш код не будет "запрашивать" каждый блок, чтобы попасть в четвертый блок.

Несколько разыменований похожи на соседство, которое знает только соседний сосед.

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

Ответ 6

Разыменование указателя не должно быть намного больше, чем копирование адреса в регистр (адрес). Вот и все.