Я следовал учебному пособию по программированию на встроенном С, а затем понял, что использование указателя для указания на переменную, а затем его использование для разыменования делает программу быстрее!

У меня есть базовые знания по сборке, но я не представлял, почему назначение адреса переменной указателю будет быстрее, мы не говорим о передаче по ссылке, по указателю или по значению!

Как я могу понять,

  • код без указателя: адрес памяти был назначен регистру R0, как именно то, что произошло в коде с указателем.
  • p_int стал псевдонимом для регистра R0, как это может помочь сделать программу быстрее?

Код без использования указателя:

int counter = 0;
int main() {
    while (counter < 6) {
        ++(counter);
    }
    return 0;
}

Тогда сборка будет такой, как в введите описание изображения здесь

И наоборот, вот код с указателем:

int counter = 0;
int main() {
    int *p;
    p = &counter;
    while (*p < 6) {
        ++(*p);
    }
    return 0;
}

Тогда сборка будет такой, как в введите описание изображения здесь


Обновить

Я связался с создателем курса, и он был любезен, чтобы повторить и разбить его для меня, ради помощи другим, кто, возможно, столкнулся с той же самой проблемой, я оставлю вопрос и Ответ

Чтобы получить доступ к переменной в памяти, ЦПУ необходим адрес этой переменной в одном из регистров. На самых низких уровнях оптимизации кода компилятор загружает этот адрес из памяти кода перед каждым доступом к переменной. Указатель ускоряет это, поскольку локальная переменная внутри функции main () назначается регистру. Это означает, что адрес находится в регистре (в данном случае R0), и его не нужно каждый раз загружать и перезагружать в регистр. На более высоких уровнях оптимизации компилятор генерирует более разумный код, а код без указателя работает так же быстро, как и с указателем. --MMS

0
Ahmad Magrabi 1 Май 2019 в 00:56

4 ответа

Лучший ответ

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

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

Что должны делать программисты и чему должны учить люди, пишущие курсы, так это писать код как можно более понятным и понятным. Это означает, что оба ваших примера плохие, поскольку они излишне неясны и являются примерами «преждевременной оптимизации». Лучше код будет:

  int counter;
  ...
  for(counter=0; counter < 6; counter++)
  {}

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

Сделай это:

  • Напишите наиболее читаемый код, который вы можете.
  • В релизе включить оптимизацию.
  • Если есть проблемы с производительностью, сравните результаты и найдите узкие места.
  • При необходимости вручную оптимизируйте узкие места. Возможно, с учетом конкретной системы.
3
Lundin 2 Май 2019 в 07:50

Это прямо противоположно (почти идентично, но еще одна инструкция :):

https://godbolt.org/z/xDYecQ

enter image description here

-2
P__J__ 30 Апр 2019 в 22:19

Оригинальное обновление дает хороший ответ. Кроме того, в оригинале «counter» является глобальной переменной, поэтому каждый доступ к микросхеме ARM требует сначала загрузки переменной в регистр. В зависимости от того, где находится переменная и уровня оптимизации, это как минимум одна инструкция LDR (может быть больше), тогда счетчик обновлений ++ требует инструкции add и обратной записи в глобальную переменную.

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

0
Richard at ImageCraft 3 Май 2019 в 08:56

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

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

1
Sean Houlihane 1 Май 2019 в 09:37