Що робить 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 з його нестабільністю регістрів, більшість з яких зарезервовано компілятором для власних цілей.
Власне, регістр повідомляє компілятору, що змінна не має псевдоніму ні з чим іншим у програмі (навіть із символами char).
Це може бути використане сучасними компіляторами в різних ситуаціях, і може допомогти компілятору трохи в складному коді - у простому коді компілятори можуть зрозуміти це самостійно.
В іншому випадку він не виконує мети і не використовується для розподілу реєстру. Зазвичай він не зазнає пониження продуктивності, щоб вказати його, якщо ваш компілятор є досить сучасним.
register
забороняє приймати адресу взагалі, оскільки в іншому випадку може бути корисним повідомляти компіляторам про випадки, коли компілятор зможе застосувати оптимізацію реєстру, незважаючи на те, що адреса змінної передається зовнішній функції (змінна повинна мати бути очищеним в пам'яті для цього конкретного виклику , але як тільки функція повернеться, компілятор знову може трактувати її як змінну, адреса якої ніколи не приймалася).
bar
це register
змінна, компілятор може в своєму дозвіллі замінити foo(&bar);
з int temp=bar; foo(&temp); bar=temp;
, але приймаючи адреса bar
буде заборонений в більшості інших контекстах не могли б здатися надмірно складного правилом. Якщо змінна могла б інакше зберігатися в регістрі, підміна зменшить код. Якщо змінну потрібно було б зберегти в оперативній пам'яті, заміщення зробить код більшим. Якщо залишити питання про те, чи потрібно робити заміну до компілятора, це призведе до кращого коду в обох випадках.
register
кваліфікації на глобальних змінних, незалежно від того, чи дозволяє компілятор приймати адресу, дозволить отримати деякі приємні оптимізації у випадках, коли вбудована функція, яка використовує глобальну змінну, повторно викликається в циклі. Я не можу придумати жодного іншого способу, щоб ця змінна була збережена в реєстрі між ітераціями циклу - ви можете?
Я прочитав, що він використовується для оптимізації, але чітко не визначений у жодному стандарті.
Насправді це буде чітко визначено стандартом C. Цитуючи проект пункту 6 проекту N1570 (інші версії мають те ж формулювання):
Декларація ідентифікатора для об'єкта із специфікатором класу зберігання
register
дозволяє припустити, що доступ до об'єкта є максимально швидким. Ступінь ефективності таких пропозицій визначається впровадженням.
Одинарний &
оператор не може застосовуватися до об'єкта, визначеного за допомогою register
, і register
не може використовуватися у зовнішній декларації.
Є кілька інших (досить незрозумілих) правил, характерних для register
-кваліфікованих об'єктів:
register
не визначеною поведінкою. register
, але ви не можете зробити нічого корисного з таким об’єктом (індексування в масив вимагає отримання адреси його початкового елемента)._Alignas
(новий у C11) може не застосовуватися до такого об’єкта.va_start
макросу, - register
кваліфіковане, поведінка не визначена.Може бути ще декілька інших; завантажте проект стандарту та пошукайте "зареєструватися", якщо вас цікавить.
Як випливає з назви, первісне значення register
полягало в тому, щоб вимагати зберігання об'єкта в регістрі процесора. Але з удосконаленням оптимізації компіляторів це стало менш корисним. Сучасні версії стандарту C не посилаються на регістри процесора, оскільки вони більше (не повинні) припускати, що таке існує (є архітектури, які не використовують регістри). Поширена думка полягає в тому, що застосування register
до об’єктного декларації швидше погіршить сформований код, оскільки це заважає власному розподілу регістра компілятора. Можливо, ще є кілька випадків, коли це корисно (скажімо, якщо ви дійсно знаєте, як часто буде доступна змінна, і ваші знання кращі за те, що може зрозуміти сучасний оптимізуючий компілятор).
Основним відчутним ефектом register
є те, що він запобігає будь-якій спробі прийняти адресу об'єкта. Це не особливо корисно як натяк на оптимізацію, оскільки він може бути застосований лише до локальних змінних, а оптимізуючий компілятор може сам переконатися, що адреса такого об’єкта не взята.
register
кваліфікованого об’єкта масиву, якщо це ви думаєте.
register
об’єктів масиву; дивіться оновлений перший пункт у моїй відповіді. Легально визначати такий об’єкт, але ви нічого не можете з ним зробити. Якщо ви додасте register
до визначення s
у своєму прикладі , програма є незаконною (порушення обмеження) у C. C ++ не встановлює однакових обмежень register
, тому програма буде дійсною C ++ (але використання register
було б безглуздим).
register
Ключове слово може слугувати корисною метою, якби було законним прийняти адресу такої змінної, але лише у випадках, коли семантику не вплине, скопіювавши змінну до тимчасової, коли її адреса взята, та перезавантаживши її з тимчасової у наступній точці послідовності Це дозволило б компіляторам припустити, що змінна може бути безпечно збережена в реєстрі через усі доступні покажчики, за умови, що вона розмитається в будь-яких місцях, де її адреса взята.
Час історій!
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;
}
Я перевірив ключове слово регістр під 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
Реєстр повідомив би компілятор, що кодер вважає, що ця змінна буде записана / прочитана достатньо, щоб виправдати її зберігання в одному з небагатьох регістрів, доступних для змінного використання. Читання / запис з регістрів, як правило, швидше і може зажадати менший набір оп-коду.
Сьогодні це не дуже корисно, оскільки оптимізатори більшості компіляторів краще, ніж ви, визначаючи, чи слід використовувати реєстр для цієї змінної та як довго.
Протягом сімдесятих років, на самому початку мови С, було введено ключове слово регістр для того, щоб програміст міг дати підказки компілятору, кажучи, що змінна буде використовуватися дуже часто, і що слід розумно збережіть його значення в одному з внутрішніх реєстрів процесора.
В даний час оптимізатори набагато ефективніші, ніж програмісти, для визначення змінних, які, швидше за все, зберігатимуться в регістрах, і оптимізатор не завжди враховує підказку програміста.
Тому багато людей помилково рекомендують не використовувати ключове слово реєстрації.
Подивимось, чому!
Ключове слово регістр має пов'язаний побічний ефект: ви не можете посилатися (отримати адресу) змінної типу регістру.
Люди, які радять іншим не використовувати регістри, неправильно сприймають це як додатковий аргумент.
Однак простий факт знання того, що ви не можете прийняти адресу змінної реєстру, дозволяє компілятору (та його оптимізатору) знати, що значення цієї змінної не можна змінювати опосередковано через покажчик.
Коли в певному пункті потоку інструкцій змінна регістру має своє значення, присвоєне в регістрі процесора, і регістр не використовувався, оскільки для отримання значення іншої змінної, компілятор знає, що її не потрібно повторно завантажувати значення змінної в цьому регістрі. Це дозволяє уникнути дорогого марного доступу до пам'яті.
Зробіть власні тести, і ви отримаєте значне поліпшення продуктивності в самих внутрішніх петлях.
На підтримуваних компіляторах C він намагається оптимізувати код так, щоб значення змінної зберігалося у фактичному регістрі процесора.
Компілятор Visual C ++ від Microsoft ігнорує register
ключове слово, коли включена глобальна оптимізація розподілу реєстру (прапор компілятора / Oe).
Дивіться реєструвати ключове слово на MSDN.
Ключове слово "Зареєструвати" повідомляє компілятору зберігати певну змінну в регістрах процесора, щоб вона могла бути доступною швидко. З точки зору програміста, ключове слово реєстру використовується для змінних, які широко використовуються в програмі, щоб компілятор міг пришвидшити код. Хоча від компілятора залежить, чи зберігати змінну в регістрах процесора чи основній пам'яті.
Регістр вказує компілятору для оптимізації цього коду, зберігаючи цю конкретну змінну в регістрах, а потім у пам'яті. це запит до компілятора, компілятор може або не може розглянути цей запит. Ви можете скористатися цим засобом у випадку, коли до деяких змінних ви звертаєтесь дуже часто. Наприклад: Цикл.
Ще одне, що якщо ви оголосите змінну як реєстр, ви не можете отримати її адресу, оскільки вона не зберігається в пам'яті. він отримує своє розподілення в регістрі процесора.
Вихід Asm g.3 9.3, не використовуючи прапори оптимізації (все у цій відповіді стосується стандартної компіляції без оптимізаційних прапорів):
#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 / Ws, якби обчислення було зроблено esi
, що відбувається з використанням C ++ const register
). Не використовуючи register
причини 2 запису та 1 читання (хоча зберігання для завантаження переадресації відбудеться при прочитанні). Це тому, що значення має бути присутнім та оновлюватися безпосередньо в стеку, щоб правильне значення можна було прочитати за адресою (вказівником). register
не має цієї вимоги і на неї не можна звернути увагу. const
і register
в основному є протилежністю volatile
та використаннямvolatile
змінить оптимізацію const у файлі та блоці області та register
оптимізації в області блоку. const register
і register
дасть однакові виходи, тому що const нічого не робить на C в області блоку, тому застосовуються лише register
оптимізації.
На кланг, register
ігнорується, але const
оптимізації все ще відбуваються.