"Зареєструвати" ключове слово в C?


272

Що робить registerключове слово мовою С? Я прочитав, що він використовується для оптимізації, але чітко не визначений у жодному стандарті. Чи вона все ще актуальна і якщо так, то коли б ви її використали?


41
Що робить ключове слово регістр у C? ігнорується :)
bestsss

18
@bestsss Не повністю ігнорується. Спробуйте отримати адресу registerзмінної.
qrdl

4
Цей код, який ви читаєте, старий youtube.com/watch?v=ibF36Yyeehw#t=1827
Полковник Паніка

Відповіді:


340

Компілятору це натяк на те, що змінна буде широко використовуватися і рекомендуєте зберігати її в регістрі процесорів, якщо це можливо.

Більшість сучасних компіляторів роблять це автоматично, і краще їх вибирати, ніж ми, люди.


17
Що ж, я експериментував з реєстром, щоб підправити свої подання ACM, а іноді це справді допомагало. Але ви дійсно повинні бути обережними, оскільки поганий вибір погіршує продуктивність.
ypnos

80
Важлива причина, щоб не використовувати "регістр": ви не можете прийняти адресу змінної, оголошеної "зареєструватися"
Adam Rosenfield

22
Зауважте, що деякі / багато компіляторів повністю ігнорують ключове слово регістр (що цілком законно).
Євро Міцеллі

4
ypnos: насправді швидкість вирішення проблем ACM ICPC значно більше залежить від вибору алгоритму, ніж від таких мікрооптимізацій. 5-секундного часового обмеження зазвичай достатньо для правильного рішення, особливо при використанні C замість Java.
Джої

65
@Euro: Ви, мабуть, знаєте це, але для того, щоб бути явним, компілятор необхідний, щоб запобігти взяттю адреси registerзмінної; це єдиний обов'язковий ефект registerключового слова. Навіть цього достатньо для поліпшення оптимізації, оскільки стає тривіальним сказати, що змінна може бути змінена лише в рамках цієї функції.
Дейл Хагглунд

69

Я здивований, що ніхто не згадував, що ви не можете прийняти адресу змінної реєстру, навіть якщо компілятор вирішив зберігати змінну в пам'яті, а не в регістрі.

Таким чином, використовуючи registerнічого, ви не виграєте (все одно компілятор вирішить для себе, де розмістити змінну) і програє &оператору - немає причин використовувати його.


94
Насправді є причина. Сам факт, що ви не можете прийняти адресу змінної, дає певні можливості оптимізації: компілятор може довести, що змінна не буде псевдонімом.
Олександр К.

8
Компілятори, як відомо, жахливо доказують, що згладжування не відбувається в нетривіальних випадках, тому registerкорисно для цього, навіть якщо компілятор не занесе його до реєстру.
Майлз Рут

2
@AlexandreC, Майлз, компілятори абсолютно добре, коли перевіряється, чи є інша змінна. Тож незалежно від інших труднощів щодо виявлення дозволу, перезавантажуючи, що нічого не купує. Коли K + R вперше створив C, було дійсно корисно заздалегідь знати, що & не буде використовуватися, оскільки цей компілятор фактично прийняв рішення про розподіл реєстру про перегляд декларації, перш ніж переглянути наступний код. Саме тому діє заборона. Ключове слово "зареєструватися" зараз фактично застаріло.
greggo

25
Ця логіка constтакож є марною, оскільки вона нічого не виграє, ви лише втрачаєте можливість зміни змінної. registerможе бути використаний для того, щоб переконатися, що ніхто не прийме адресу змінної в майбутньому, не замислюючись. У мене ніколи не було підстав користуватися register.
Tor Klingberg

34

Він повідомляє компілятору спробувати використовувати регістр процесора замість оперативної пам'яті для зберігання змінної. Реєстри знаходяться в процесорі і набагато швидше отримують доступ, ніж оперативна пам'ять. Але це лише пропозиція для компілятора, і вона може не переслідувати.


8
Варто додати для людей, які використовують C ++, C ++ дозволяє взяти адресу змінної реєстру
буде

5
@Will: ... але компілятор, швидше за все, ігнорує ключове слово. Дивіться мою відповідь.
bwDraco

Так, здається, що "зареєструватися" - це плацебо в C ++, він просто існує, щоб дозволити компілювати код C як C ++. І не було б особливого сенсу забороняти & var, дозволяючи передавати його за посиланням або const-посиланням, і без проходження посилання ви серйозно зламали C ++.
greggo

22

Я знаю, що це питання стосується C, але те саме питання для C ++ було закрите, як точний дублікат цього питання. Тому ця відповідь може не застосовуватись до C.


Останній проект стандарту C ++ 11, N3485 , говорить про це у 7.1.1 / 3:

registerСпецифікатор натяк на реалізацію , що змінна так оголошено буде в значній мірі використовуються. [ Примітка: Підказка може бути проігнорована, і в більшості реалізацій вона буде ігнорована, якщо взята адреса змінної. Це використання застаріло ... —закінчити примітку ]

У C ++ (але не в C) стандарт не зазначає, що ви не можете прийняти адресу оголошеної змінної register; однак, оскільки змінна, що зберігається в регістрі процесора протягом усього життя, не має пов'язаного з нею місця пам'яті, спроба взяти її адресу була б недійсною, і компілятор ігнорує registerключове слово, щоб дозволити приймати адресу.


17

Це не було актуальним принаймні 15 років, оскільки оптимізатори приймають про це кращі рішення, ніж ви можете. Навіть коли це було актуально, це мало набагато більше сенсу в архітектурі процесора з великою кількістю регістрів, таких як SPARC або M68000, ніж це було в Intel з його нестабільністю регістрів, більшість з яких зарезервовано компілятором для власних цілей.


13

Власне, регістр повідомляє компілятору, що змінна не має псевдоніму ні з чим іншим у програмі (навіть із символами char).

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

В іншому випадку він не виконує мети і не використовується для розподілу реєстру. Зазвичай він не зазнає пониження продуктивності, щоб вказати його, якщо ваш компілятор є досить сучасним.


"каже компілятору .." ні, це не так. Усі автоматичні змінні мають це властивість, якщо ви не приймаєте його адресу та не використовуєте її способами, що перевищують певні можливості аналізу. Отже, компілятор знає це з коду, незалежно від того, використовуєте ви ключове слово регістр. Так трапляється, що ключове слово "зареєструвати" робить незаконне писати таку конструкцію, але якщо ви не використовуєте ключове слово і не приймаєте адресу таким чином, тоді компілятор все-таки знає, що це безпечно. Така інформація має вирішальне значення для оптимізації.
greggo

1
@greggo: Надто погано registerзабороняє приймати адресу взагалі, оскільки в іншому випадку може бути корисним повідомляти компіляторам про випадки, коли компілятор зможе застосувати оптимізацію реєстру, незважаючи на те, що адреса змінної передається зовнішній функції (змінна повинна мати бути очищеним в пам'яті для цього конкретного виклику , але як тільки функція повернеться, компілятор знову може трактувати її як змінну, адреса якої ніколи не приймалася).
supercat

@supercat Я думаю, що все-таки буде дуже складна розмова з компілятором. Якщо ви хочете сказати компілятору, ви можете зробити це, скопіювавши першу змінну у другу, у якій немає '&', а потім більше ніколи не використовуючи першу.
greggo

1
@greggo: Кажуть , що , якщо barце registerзмінна, компілятор може в своєму дозвіллі замінити foo(&bar);з int temp=bar; foo(&temp); bar=temp;, але приймаючи адреса barбуде заборонений в більшості інших контекстах не могли б здатися надмірно складного правилом. Якщо змінна могла б інакше зберігатися в регістрі, підміна зменшить код. Якщо змінну потрібно було б зберегти в оперативній пам'яті, заміщення зробить код більшим. Якщо залишити питання про те, чи потрібно робити заміну до компілятора, це призведе до кращого коду в обох випадках.
supercat

1
@greggo: Дозволення registerкваліфікації на глобальних змінних, незалежно від того, чи дозволяє компілятор приймати адресу, дозволить отримати деякі приємні оптимізації у випадках, коли вбудована функція, яка використовує глобальну змінну, повторно викликається в циклі. Я не можу придумати жодного іншого способу, щоб ця змінна була збережена в реєстрі між ітераціями циклу - ви можете?
суперкат

13

Я прочитав, що він використовується для оптимізації, але чітко не визначений у жодному стандарті.

Насправді це буде чітко визначено стандартом C. Цитуючи проект пункту 6 проекту N1570 (інші версії мають те ж формулювання):

Декларація ідентифікатора для об'єкта із специфікатором класу зберігання registerдозволяє припустити, що доступ до об'єкта є максимально швидким. Ступінь ефективності таких пропозицій визначається впровадженням.

Одинарний &оператор не може застосовуватися до об'єкта, визначеного за допомогою register, і registerне може використовуватися у зовнішній декларації.

Є кілька інших (досить незрозумілих) правил, характерних для register-кваліфікованих об'єктів:

  • Визначення об’єкта масиву з registerне визначеною поведінкою.
    Виправлення: Законно визначати об’єкт масиву за допомогою register, але ви не можете зробити нічого корисного з таким об’єктом (індексування в масив вимагає отримання адреси його початкового елемента).
  • Специфікатор _Alignas(новий у C11) може не застосовуватися до такого об’єкта.
  • Якщо ім'я параметра, передане va_startмакросу, - registerкваліфіковане, поведінка не визначена.

Може бути ще декілька інших; завантажте проект стандарту та пошукайте "зареєструватися", якщо вас цікавить.

Як випливає з назви, первісне значення registerполягало в тому, щоб вимагати зберігання об'єкта в регістрі процесора. Але з удосконаленням оптимізації компіляторів це стало менш корисним. Сучасні версії стандарту C не посилаються на регістри процесора, оскільки вони більше (не повинні) припускати, що таке існує (є архітектури, які не використовують регістри). Поширена думка полягає в тому, що застосування registerдо об’єктного декларації швидше погіршить сформований код, оскільки це заважає власному розподілу регістра компілятора. Можливо, ще є кілька випадків, коли це корисно (скажімо, якщо ви дійсно знаєте, як часто буде доступна змінна, і ваші знання кращі за те, що може зрозуміти сучасний оптимізуючий компілятор).

Основним відчутним ефектом registerє те, що він запобігає будь-якій спробі прийняти адресу об'єкта. Це не особливо корисно як натяк на оптимізацію, оскільки він може бути застосований лише до локальних змінних, а оптимізуючий компілятор може сам переконатися, що адреса такого об’єкта не взята.


так чи справді поведінка цієї програми не визначена відповідно до стандарту C? Чи добре це визначено в C ++? Я думаю, що це добре визначено в C ++.
Деструктор

@Destructor: Чому це було б не визначено? Немає registerкваліфікованого об’єкта масиву, якщо це ви думаєте.
Кіт Томпсон

Пробачте, я забув записати ключове слово зареєструватись у оголошенні масиву main (). Чи добре це визначено в C ++?
Деструктор

Я помилявся щодо визначення registerоб’єктів масиву; дивіться оновлений перший пункт у моїй відповіді. Легально визначати такий об’єкт, але ви нічого не можете з ним зробити. Якщо ви додасте registerдо визначення sу своєму прикладі , програма є незаконною (порушення обмеження) у C. C ++ не встановлює однакових обмежень register, тому програма буде дійсною C ++ (але використання registerбуло б безглуздим).
Кіт Томпсон

@KeithThompson: registerКлючове слово може слугувати корисною метою, якби було законним прийняти адресу такої змінної, але лише у випадках, коли семантику не вплине, скопіювавши змінну до тимчасової, коли її адреса взята, та перезавантаживши її з тимчасової у наступній точці послідовності Це дозволило б компіляторам припустити, що змінна може бути безпечно збережена в реєстрі через усі доступні покажчики, за умови, що вона розмитається в будь-яких місцях, де її адреса взята.
supercat

9

Час історій!

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: короткочасні змінні, які займаються великою кількістю математики. Не заявляйте занадто багато одразу.


5

Ви псуєтесь із складним алгоритмом розфарбовування графіків. Це використовується для розподілу реєстру. Ну, переважно. Це діє як натяк на компілятор - це правда. Але не ігнорується в повному обсязі, оскільки вам заборонено приймати адресу змінної реєстру (пам’ятайте, компілятор, тепер на вашу милість, спробує діяти інакше). Що певним чином говорить вам не користуватися цим.

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

Але, як я вже сказав, застаріле не означає, що ви не можете його використовувати.


13
Деякі з старих апаратних засобів мали більше регістрів, ніж сучасні машини Intel. Кількість реєстрів не має нічого спільного з віком і всім пов'язаним з архітектурою процесора.
ПРОСТО МОЕ правильне ДУМКУ

2
@JUSTMYcorrectOPINION Дійсно, у X86 в основному шість, що залишає щонайменше 1 або 2 для присвячення "реєстрації". Насправді, оскільки стільки коду написано або перенесено на машину, яка не працює в регістрі, я підозрюю, що це значною мірою сприяло тому, що ключове слово "регістр" стало плацебо - немає сенсу натякати на регістри, коли таких немає. Ось ми вже через 4 роки і, на щастя, x86_64 збільшив його до 14, і ARM - це теж велика річ.
greggo

4

Трохи демо (без будь-якої цілі в реальному світі) для порівняння: при видаленні 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;
}

2
Що стосується gcc 4.8.4 та -O3, я не отримую різниці. Без ітерацій -O3 та 40000 я отримую, можливо, на 50 мс менше за загальний час у 1,5 секунди, але я не запускав його достатньо разів, щоб знати, чи було це навіть статистично значущим.
zstewart

З CLANG 5.0 немає різниці, платформа - AMD64. (Я перевірив вихід ASM.)
ern0

4

Я перевірив ключове слово регістр під 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


2

Реєстр повідомив би компілятор, що кодер вважає, що ця змінна буде записана / прочитана достатньо, щоб виправдати її зберігання в одному з небагатьох регістрів, доступних для змінного використання. Читання / запис з регістрів, як правило, швидше і може зажадати менший набір оп-коду.

Сьогодні це не дуже корисно, оскільки оптимізатори більшості компіляторів краще, ніж ви, визначаючи, чи слід використовувати реєстр для цієї змінної та як довго.


2

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

В даний час оптимізатори набагато ефективніші, ніж програмісти, для визначення змінних, які, швидше за все, зберігатимуться в регістрах, і оптимізатор не завжди враховує підказку програміста.

Тому багато людей помилково рекомендують не використовувати ключове слово реєстрації.

Подивимось, чому!

Ключове слово регістр має пов'язаний побічний ефект: ви не можете посилатися (отримати адресу) змінної типу регістру.

Люди, які радять іншим не використовувати регістри, неправильно сприймають це як додатковий аргумент.

Однак простий факт знання того, що ви не можете прийняти адресу змінної реєстру, дозволяє компілятору (та його оптимізатору) знати, що значення цієї змінної не можна змінювати опосередковано через покажчик.

Коли в певному пункті потоку інструкцій змінна регістру має своє значення, присвоєне в регістрі процесора, і регістр не використовувався, оскільки для отримання значення іншої змінної, компілятор знає, що її не потрібно повторно завантажувати значення змінної в цьому регістрі. Це дозволяє уникнути дорогого марного доступу до пам'яті.

Зробіть власні тести, і ви отримаєте значне поліпшення продуктивності в самих внутрішніх петлях.

c_register_side_effect_performance_boost


1

На підтримуваних компіляторах C він намагається оптимізувати код так, щоб значення змінної зберігалося у фактичному регістрі процесора.


1

Компілятор Visual C ++ від Microsoft ігнорує registerключове слово, коли включена глобальна оптимізація розподілу реєстру (прапор компілятора / Oe).

Дивіться реєструвати ключове слово на MSDN.


1

Ключове слово "Зареєструвати" повідомляє компілятору зберігати певну змінну в регістрах процесора, щоб вона могла бути доступною швидко. З точки зору програміста, ключове слово реєстру використовується для змінних, які широко використовуються в програмі, щоб компілятор міг пришвидшити код. Хоча від компілятора залежить, чи зберігати змінну в регістрах процесора чи основній пам'яті.


0

Регістр вказує компілятору для оптимізації цього коду, зберігаючи цю конкретну змінну в регістрах, а потім у пам'яті. це запит до компілятора, компілятор може або не може розглянути цей запит. Ви можете скористатися цим засобом у випадку, коли до деяких змінних ви звертаєтесь дуже часто. Наприклад: Цикл.

Ще одне, що якщо ви оголосите змінну як реєстр, ви не можете отримати її адресу, оскільки вона не зберігається в пам'яті. він отримує своє розподілення в регістрі процесора.


0

Вихід 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оптимізації все ще відбуваються.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.