Что делает register
ключевое слово на языке Си? Я читал, что он используется для оптимизации, но не четко определен ни в одном стандарте. Это все еще актуально и если да, то когда бы вы его использовали?
register
переменной.
Что делает register
ключевое слово на языке Си? Я читал, что он используется для оптимизации, но не четко определен ни в одном стандарте. Это все еще актуально и если да, то когда бы вы его использовали?
register
переменной.
Ответы:
Это подсказка компилятору, что переменная будет интенсивно использоваться, и вы порекомендуете сохранить ее в регистре процессора, если это возможно.
Большинство современных компиляторов делают это автоматически и лучше выбирают их, чем мы, люди.
register
переменной; это единственный обязательный эффект register
ключевого слова. Даже этого достаточно для улучшения оптимизации, потому что становится тривиально сказать, что переменная может быть изменена только внутри этой функции.
Я удивлен, что никто не упомянул, что вы не можете получить адрес переменной регистра, даже если компилятор решает сохранить переменную в памяти, а не в регистре.
Таким образом, если register
вы ничего не выиграете (в любом случае компилятор сам решит, куда поместить переменную) и потеряет &
оператор - нет причин его использовать.
register
это полезно, даже если компилятор не помещает их в регистр.
const
также бесполезна, так как она ничего не выигрывает, вы только теряете возможность изменять переменную. register
может быть использован, чтобы убедиться, что никто не заберет адрес переменной в будущем, не задумываясь. У меня никогда не было причин использовать register
все же.
Он говорит компилятору попытаться использовать регистр ЦП вместо ОЗУ для хранения переменной. Регистры находятся в центральном процессоре и имеют гораздо более быстрый доступ, чем оперативная память. Но это всего лишь предложение для компилятора, и оно может не исполниться.
Я знаю, что этот вопрос касается C, но тот же вопрос для C ++ был закрыт как точная копия этого вопроса. Таким образом, этот ответ может не относиться к C.
Последний вариант стандарта C ++ 11, N3485 , говорит об этом в 7.1.1 / 3:
register
Спецификатор намек на реализацию , что переменная так объявлено будет в значительной степени используются. [ примечание: подсказка может быть проигнорирована и в большинстве реализаций она будет игнорироваться, если адрес переменной взят. Это использование устарело ... - примечание ]
В C ++ (но не в C) стандарт не устанавливает, что вы не можете взять адрес объявленной переменной register
; однако, поскольку переменная, хранящаяся в регистре ЦП в течение всего времени ее существования, не имеет связанной с ней ячейки памяти, попытка получить ее адрес будет недопустимой, и компилятор проигнорирует register
ключевое слово, чтобы разрешить получение адреса.
Это не актуально как минимум 15 лет, так как оптимизаторы принимают лучшие решения по этому поводу, чем вы. Даже когда это было уместно, в архитектуре ЦП с большим количеством регистров было гораздо больше смысла, чем в SPARC или M68000, чем в Intel с его небольшим количеством регистров, большинство из которых зарезервированы компилятором для собственных целей.
Фактически, регистр сообщает компилятору, что переменная не имеет псевдонима с чем-либо еще в программе (даже с символами).
Это может быть использовано современными компиляторами в самых разных ситуациях и может помочь компилятору в сложном коде совсем немного - в простом коде компиляторы могут понять это самостоятельно.
В противном случае он не имеет смысла и не используется для распределения регистров. Обычно это не приводит к снижению производительности, если ваш компилятор достаточно современен.
register
, что вообще запрещает брать адрес, так как в противном случае было бы полезно сообщить компиляторам случаи, когда компилятор мог бы применить оптимизацию регистра, несмотря на то, что адрес переменной передавался во внешнюю функцию (переменная должна была бы быть сброшен в память для этого конкретного вызова , но как только функция вернется, компилятор может снова обработать ее как переменную, адрес которой никогда не был взят).
bar
это register
переменная, компилятор может в своем досуге заменить foo(&bar);
с int temp=bar; foo(&temp); bar=temp;
, но принимая адрес bar
будет запрещен в большинстве других контекстах не могли бы показаться чрезмерно сложному правилу. Если бы переменная могла быть сохранена в регистре, подстановка сделала бы код меньше. Если переменную все равно нужно будет хранить в ОЗУ, подстановка сделает код больше. Если оставить вопрос о том, делать ли подстановку до компилятора, в обоих случаях код будет лучше.
register
глобальных переменных, независимо от того, позволяет ли компилятор брать адрес, позволит провести приятную оптимизацию в тех случаях, когда встроенная функция, использующая глобальную переменную, неоднократно вызывается в цикле. Я не могу придумать какой-либо другой способ сохранить эту переменную в регистре между итерациями цикла - не так ли?
Я читал, что он используется для оптимизации, но не четко определен ни в одном стандарте.
На самом деле это будет четко определено стандартом C. Цитируя черновой вариант 6.7.1 раздела 6 N1570 (другие версии имеют такую же формулировку):
Объявление идентификатора для объекта со спецификатором класса хранилища
register
предполагает, что доступ к объекту должен быть максимально быстрым. Степень эффективности таких предложений определяется реализацией.
Унарный &
оператор не может применяться к объекту, определенному с помощью register
, и register
не может использоваться во внешнем объявлении.
Есть несколько других (довольно непонятных) правил, специфичных для register
-качественных объектов:
register
неопределенным поведением. register
, но с таким объектом ничего полезного сделать нельзя (для индексации массива требуется адрес его начального элемента)._Alignas
(новый в C11) не может быть применен к такому объекту.va_start
макросу, является register
-qualified, поведение не определено.Там может быть несколько других; скачайте черновик стандарта и поищите «зарегистрироваться», если вам интересно.
Как следует из названия, первоначальный смысл register
состоял в том, чтобы требовать сохранения объекта в регистре процессора. Но с улучшениями в оптимизации компиляторов это стало менее полезным. Современные версии стандарта C не ссылаются на регистры ЦП, потому что они больше (не должны) предполагать, что есть такая вещь (есть архитектуры, которые не используют регистры). Общепринятым является то, что применение register
к объявлению объекта с большей вероятностью ухудшит сгенерированный код, поскольку это мешает распределению регистров собственного компилятора. Еще может быть несколько случаев, когда это полезно (скажем, если вы действительно знаете, как часто к переменной обращаются, и ваши знания лучше, чем то, что может вычислить современный оптимизирующий компилятор).
Основным ощутимым эффектом register
является то, что он предотвращает любые попытки получить адрес объекта. Это не особенно полезно в качестве подсказки по оптимизации, поскольку может применяться только к локальным переменным, и оптимизирующий компилятор может убедиться, что адрес такого объекта не взят.
register
-оквалифицированного объекта массива, если вы об этом думаете.
register
объектов массива; см обновленную первую точку пули в моем ответе. Законно определить такой объект, но вы ничего не можете с ним сделать. Если вы добавите register
к определению s
в вашем примере , программа является недопустимой (нарушение ограничений) в C. C ++ не накладывает те же ограничения register
, поэтому программа будет действительной C ++ (но использование register
будет бессмысленным).
register
Ключевое слово могло бы служить полезной цели, если было бы законно брать адрес такой переменной, но только в тех случаях, когда на семантику не повлияло бы копирование переменной во временную, когда берется ее адрес, и перезагрузка ее из временной в следующей точке последовательности. Это позволило бы компиляторам предполагать, что переменная может быть безопасно сохранена в регистре при всех обращениях к указателю при условии, что она сбрасывается в любом месте, где взят ее адрес.
Время историй!
C, как язык, является абстракцией компьютера. Он позволяет вам делать то, что делает компьютер, то есть манипулировать памятью, выполнять математические операции, печатать вещи и т. Д.
Но C это только абстракция. И, в конечном счете, от вас извлекается язык ассемблера. Сборка - это язык, который читает процессор, и если вы используете его, вы делаете вещи с точки зрения процессора. Что делает процессор? По сути, он читает из памяти, выполняет математику и пишет в память. Процессор не просто выполняет математические операции с числами в памяти. Во-первых, вы должны переместить число из памяти в память внутри процессора, называемое регистром, Когда вы закончите делать с этим номером все, что вам нужно, вы можете переместить его обратно в обычную системную память. Зачем вообще использовать системную память? Количество регистров ограничено. Вы получаете только около ста байтов в современных процессорах, а старые популярные процессоры были еще более фантастически ограничены (у 6502 было 3 8-битных регистра для бесплатного использования). Итак, ваша средняя математическая операция выглядит так:
load first number from memory
load second number from memory
add the two
store answer into memory
Многое из этого ... не математика. Эти операции загрузки и хранения могут занимать до половины вашего времени обработки. C, будучи абстракцией компьютеров, избавил программиста от беспокойства по поводу использования и манипулирования регистрами, а поскольку число и тип различаются для разных компьютеров, C возлагает ответственность за распределение регистров исключительно на компилятор. За одним исключением.
Когда вы объявляете переменную register
, вы говорите компилятору: «Да, я намерен использовать эту переменную много и / или быть недолгим. На вашем месте я бы попытался сохранить ее в реестре». Когда стандарт C говорит, что компиляторы на самом деле ничего не должны делать, это потому, что стандарт C не знает, для какого компьютера вы компилируете, и это может быть похоже на 6502 выше, где все 3 регистра необходимы просто для работы и нет запасного регистра, чтобы сохранить ваш номер. Однако, когда он говорит, что вы не можете взять адрес, это потому, что регистры не имеют адресов. Это руки процессора. Поскольку компилятору не нужно давать вам адрес, и поскольку он вообще не может иметь адреса, для компилятора теперь доступно несколько оптимизаций. Это может, скажем, сохранить номер в реестре всегда. Это не не нужно беспокоиться о том, где он хранится в памяти компьютера (кроме необходимости вернуть его обратно). Он может даже поместить его в другую переменную, передать другому процессору, изменить местоположение и т. Д.
tl; dr: недолговечные переменные, которые делают много математики. Не объявляй слишком много сразу.
Вы возитесь со сложным алгоритмом раскраски графа компилятора. Это используется для распределения регистров. Ну, в основном. Это действует как подсказка для компилятора - это правда. Но не игнорируется полностью, поскольку вам не разрешено брать адрес переменной регистра (помните, что компилятор, теперь по вашему желанию, будет пытаться действовать по-другому). Что в некотором смысле говорит вам не использовать его.
Ключевое слово использовалось давно, давно. Когда было так мало регистров, которые могли бы подсчитать их все, используя указательный палец.
Но, как я уже сказал, устаревший не означает, что вы не можете его использовать.
Просто небольшая демонстрация (без каких-либо реальных целей) для сравнения: при удалении register
ключевых слов перед каждой переменной этот фрагмент кода занимает 3,41 секунды на моем i7 (GCC), а register
тот же код завершается за 0,7 секунды.
#include <stdio.h>
int main(int argc, char** argv) {
register int numIterations = 20000;
register int i=0;
unsigned long val=0;
for (i; i<numIterations+1; i++)
{
register int j=0;
for (j;j<i;j++)
{
val=j+i;
}
}
printf("%d", val);
return 0;
}
Я проверил ключевое слово register в QNX 6.5.0, используя следующий код:
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>
int main(int argc, char *argv[]) {
uint64_t cps, cycle1, cycle2, ncycles;
double sec;
register int a=0, b = 1, c = 3, i;
cycle1 = ClockCycles();
for(i = 0; i < 100000000; i++)
a = ((a + b + c) * c) / 2;
cycle2 = ClockCycles();
ncycles = cycle2 - cycle1;
printf("%lld cycles elapsed\n", ncycles);
cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
printf("This system has %lld cycles per second\n", cps);
sec = (double)ncycles/cps;
printf("The cycles in seconds is %f\n", sec);
return EXIT_SUCCESS;
}
Я получил следующие результаты:
-> 807679611 прошедших циклов
-> Эта система имеет 3300830000 циклов в секунду
-> Циклы в секундах ~ 0,244600
А теперь без регистрации int:
int a=0, b = 1, c = 3, i;
Я получил:
-> 1421694077 прошедших циклов
-> Эта система имеет 3300830000 циклов в секунду
-> Циклы в секундах ~ 0,430700
Регистр уведомит компилятор о том, что кодер полагал, что эта переменная будет записана / прочитана достаточно, чтобы оправдать ее хранение в одном из немногих регистров, доступных для использования переменной. Чтение / запись из регистров обычно быстрее и может потребовать меньшего набора кода операции.
В настоящее время это не очень полезно, так как оптимизаторы большинства компиляторов лучше, чем вы, определяете, следует ли использовать регистр для этой переменной и как долго.
В семидесятые годы, в самом начале языка Си, было введено ключевое слово register, чтобы позволить программисту давать подсказки компилятору, сообщая ему, что переменная будет использоваться очень часто, и что следует сохранить его значение в одном из внутренних регистров процессора.
В настоящее время оптимизаторы гораздо эффективнее, чем программисты, определяют переменные, которые с большей вероятностью будут храниться в регистрах, и оптимизатор не всегда учитывает подсказку программиста.
Поэтому многие люди ошибочно рекомендуют не использовать ключевое слово register.
Посмотрим почему!
Ключевое слово register имеет связанный побочный эффект: вы не можете ссылаться (получить адрес) на переменную типа регистра.
Люди, советующие другим не использовать регистры, ошибочно воспринимают это как дополнительный аргумент.
Однако тот простой факт, что вы не можете получить адрес переменной регистра, позволяет компилятору (и его оптимизатору) знать, что значение этой переменной нельзя изменить косвенно через указатель.
Когда в определенной точке потока команд переменная регистра имеет свое значение, назначенное в регистре процессора, и регистр не использовался, поскольку для получения значения другой переменной компилятор знает, что ему не нужно повторно загружать значение переменной в этом регистре. Это позволяет избежать дорогостоящего бесполезного доступа к памяти.
Проведите собственные тесты, и вы получите значительное улучшение производительности в самых внутренних циклах.
Компилятор Microsoft Visual C ++ игнорирует register
ключевое слово, когда включена глобальная оптимизация выделения регистров (флаг компилятора / Oe).
Смотрите регистр ключевых слов на MSDN.
Ключевое слово Register указывает компилятору сохранять конкретную переменную в регистрах ЦП, чтобы она была доступна быстро. С точки зрения программиста, ключевое слово register используется для переменных, которые интенсивно используются в программе, так что компилятор может ускорить код. Хотя от компилятора зависит, будет ли переменная храниться в регистрах ЦП или основной памяти.
Регистр указывает компилятору оптимизировать этот код, сохраняя эту конкретную переменную в регистрах, а затем в памяти. это запрос к компилятору, компилятор может или не может рассмотреть этот запрос. Вы можете использовать эту возможность в случае, если к некоторым из ваших переменных обращаются очень часто. Например: цикл.
Еще одна вещь: если вы объявите переменную как регистр, вы не сможете получить ее адрес, поскольку она не хранится в памяти. он получает свое размещение в регистре процессора.
Вывод gcc 9.3 asm без использования флагов оптимизации (все в этом ответе относится к стандартной компиляции без флагов оптимизации):
#include <stdio.h>
int main(void) {
int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 3
add DWORD PTR [rbp-4], 1
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret
#include <stdio.h>
int main(void) {
register int i = 3;
i++;
printf("%d", i);
return 0;
}
.LC0:
.string "%d"
main:
push rbp
mov rbp, rsp
push rbx
sub rsp, 8
mov ebx, 3
add ebx, 1
mov esi, ebx
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
add rsp, 8
pop rbx
pop rbp
ret
Это заставляет ebx
быть использовано для вычисления, то есть его нужно поместить в стек и восстановить в конце функции, потому что он сохранен. register
производит больше строк кода и 1 запись в память и 1 чтение в память (хотя реально это можно было бы оптимизировать до 0 R / W, если бы вычисление было выполнено esi
, что и происходит с использованием C ++ const register
). Неиспользование register
вызывает 2 записи и 1 чтение (хотя сохранение при загрузке будет происходить при чтении). Это связано с тем, что значение должно присутствовать и обновляться непосредственно в стеке, чтобы правильное значение можно было прочитать по адресу (указателю). register
не имеет этого требования и не может быть указано const
и register
в основном противоположность volatile
и использованиеvolatile
переопределит оптимизацию const в области видимости файлов и блоков, а также register
оптимизации в области видимости блоков. const register
и register
будет выдавать идентичные выходные данные, потому что const ничего не делает с C на уровне блока, поэтому register
применяются только оптимизации.
На clang register
игнорируется, но const
оптимизация все же происходит.