Чи правильність const надає компілятору більше простору для оптимізації?


78

Я знаю, що це покращує читабельність і робить програму менш схильною до помилок, але наскільки це покращує продуктивність?

І на додаток, яка основна різниця між посиланням та constпокажчиком? Я б припустив, що вони зберігаються в пам'яті по-різному, але як так?


8
Ні, але це може дати ваш власний кодовий простір для більшої оптимізації. Прикладом є незмінний клас рядків, що забезпечує постійні операції підрядків у часі та створення з літералу без динамічного розподілу. У цьому багато сили const, але в основному для людей, і коду, який ми пишемо; компілятор не може нічому довіряти, а тому він не може розумно виграти таким же чином.
Вітаю і hth. - Альф

Відповіді:


70

[Редагувати: Добре, тому це питання є більш тонким, ніж я думав спочатку.]

Оголошення покажчика на const або посилання на const ніколи не допомагає жодному компілятору щось оптимізувати. (Хоча див. Оновлення внизу цієї відповіді.)

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

Приклад:

int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}

Компілятор не може припустити, що *pце не змінено викликом bar(), оскільки pможе бути (наприклад, вказівником на глобальний int і bar()може змінити його.

Якщо компілятор знає достатньо про того, хто викликає, foo()і вміст, bar()який він може довести bar(), не змінює *p, то він може також виконати цей доказ без оголошення const .

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

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

А як щодо функцій, які викликають foo()? Подібно до:

int x = 37;
foo(&x);
printf("%d\n", x);

Чи може компілятор довести, що це друкує 37, оскільки foo()приймає a const int *?

Ні. Незважаючи на те, що foo()приймає покажчик на const, він може відкинути const-ness і змінити int. (Це не є невизначеною поведінкою.) І тут компілятор взагалі не може робити жодних припущень; і якщо він знає достатньо про те, foo()щоб зробити таку оптимізацію, він буде знати, що навіть без const.

Єдиний час, який constможе дозволити оптимізацію, - це такі випадки:

const int x = 37;
foo(&x);
printf("%d\n", x);

Тут модифікувати за xдопомогою будь-якого механізму (наприклад, взявши вказівник на нього та відкинувши його const) означає викликати невизначену поведінку. Тож компілятор може вільно припустити, що ви цього не робите, і він може розповсюдити константу 37 у printf (). Така оптимізація є законною для будь-якого об’єкта, який ви заявляєте const. (На практиці локальна змінна, на яку ви ніколи не посилаєтесь, не виграє, оскільки компілятор вже бачить, чи ви її змінюєте в межах своєї сфери дії.)

Щоб відповісти на ваше запитання "побічної ноти", (a) покажчик const - це покажчик; і (b) покажчик const може дорівнювати NULL. Ви праві, що внутрішнє подання (тобто адреса), швидше за все, однакове.

[оновлення]

Як зазначає Крістоф у коментарях, моя відповідь є неповною, оскільки в ній не згадується restrict.

Розділ 6.7.3.1 (4) стандарту C99 говорить:

Під час кожного виконання B, нехай L - будь-яке значення l, яке має & L на основі P. Якщо L використовується для доступу до значення об'єкта X, який він позначає, і X також модифікується (будь-якими способами), тоді застосовуються наступні вимоги : T не повинен бути кваліфікованим. ...

(Тут B - основний блок, над яким знаходиться P, обмежувач-покажчик на T).)

Отже, якщо функція C foo()оголошена так:

foo(const int * restrict p)

... тоді компілятор може припустити, що жодні зміни не *pвідбуватимуться протягом життя p- тобто під час виконання foo()- тому, що в іншому випадку поведінка була б невизначеною.

Отже, в принципі поєднання restrictз покажчиком на const може забезпечити обидві оптимізації, які відхилено вище. Цікаво, хтось із компіляторів реалізує таку оптимізацію? (GCC 4.5.2, принаймні, ні.)

Зверніть увагу, що restrictіснує лише в C, а не в C ++ (навіть не C ++ 0x), за винятком розширення, специфічного для компілятора.


10
"Отже, у випадку глобальних змінних - які ви не повинні використовувати" Вибачте, але це погана порада - є багато випадків, коли глобальні змінні можна і потрібно використовувати.
Адам Розенфілд,

4
Модифікація будь-якої const змінної за допомогою const_castUB, а не лише глобальних consts ...
ildjarn

1
@Billy: відповідь може бути правильною, що стосується простих покажчиків const, але вона, принаймні, неповна - Немо забув про це restrict: оголосивши, foo()як int foo(const int *restrict p)справді, сказав компілятору, що значення, на яке вказує, pне буде змінено за допомогою виклику відfoo()
Крістоф

6
@Billy: перечитати розділ 6.7.3.1 C99: restrictнасправді це означає, що (1) якщо об'єкт, на який вказує restrict-кваліфікований вказівник, модифікується, весь доступ (включаючи той, що ініціював зміну) повинен базуватися на цьому покажчик та (2) - частина, яка тут актуальна - якщо відбудеться якась зміна, покажчик не повинен бути const-кваліфікованим; restrictпідсумок : забороняється змінювати об’єкти, на які вказують -кваліфіковані вказівники-до- constпротягом життя цього вказівника
Крістоф,

1
@AnishRamaswamy: Я не сумніваюся, що "обмежити" в деяких випадках змінює ситуацію, оскільки це допомагає при аналізі псевдонімів ... Те, що він не робить, це активізація цієї конкретної оптимізації розповсюдження константи по виклику. Я знаю, тому що спробував і подивився код збірки. (І я щойно спробував із GCC 4.8.1; той самий результат.)
Немо

6

У constC ++ є дві проблеми (що стосується оптимізації):

  • const_cast
  • mutable

const_cast означає, що навіть якщо ви передаєте об'єкт за посиланням const або покажчиком const, функція може відкинути const-ness та змінити об'єкт (дозволено, якщо для початку об'єкт не const).

mutableозначають, що навіть якщо об’єкт є const, деякі його частини можуть бути модифіковані (поведінка кешування). Крім того, об'єкти, на які вказують (замість того, щоб їм належати), можна модифікувати в constметодах, навіть якщо вони логічно є частиною стану об'єкта. І нарешті, глобальні змінні теж можна змінити ...

const тут, щоб допомогти розробнику рано ловити логічні помилки.


Якщо функція відкидає const за допомогою, const_castа потім модифікує об'єкт, це невизначена поведінка; компілятори можуть сміливо припускати, що справа ніколи не трапляється.
Джеймс Піконе

1
@JamesPicone: Так і ні. Це невизначене поведінку , щоб змінити constоб'єкт, але це причина-d'etre з const_castвідкинути на constатрибут посилання або покажчика для зміни не- constоб'єкта. Отже, компілятор може витягнути деякий приріст продуктивності const, лише частину того, без чого він міг би бути const_cast.
Matthieu M.

6

Зверху в голові я можу подумати про два випадки, коли правильна constкваліфікація дозволяє додаткові оптимізації (у випадках, коли аналіз цілої програми недоступний):

const int foo = 42;
bar(&foo);
printf("%i", foo);

Тут компілятор знає друк 42без необхідності досліджувати тіло bar()(яке може бути не видно в модулі перекладу потоку), оскільки всі модифікації до fooє незаконними ( це те саме , що приклад Немо ).

Однак це також можливо без позначення fooяк const, оголосивши bar()як

extern void bar(const int *restrict p);

У багатьох випадках програміст насправді хоче як restrictкваліфіковані параметри вказувати вказівники на, constа не звичайні вказівники на const, оскільки лише перші дають будь-які гарантії щодо мінливості об'єктів, на які вказують.

Що стосується другої частини Вашого запитання: Для всіх практичних цілей посилання на C ++ можна розглядати як постійний вказівник (а не вказівник на постійне значення!) З автоматичним опосередкуванням - це не будь-який „безпечніший” чи „швидший” ніж вказівник, просто зручніше.


Компілятор може поширювати значення nonconst об'єкта, якщо воно передається за допомогою посилання const: завдяки §5.2.2 / 5 він може мовчки створити тимчасову копію переданого об'єкта.
Руслан

4

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

Посилання та вказівники зберігаються точно так само в пам'яті.


2
Чи можете ви навести приклад, коли це допомагає (чи потрібно)? Висловлювання "не допомагає", "може допомогти" та "залежно від ситуації" - все в тому ж абзаці насправді не прояснює ситуації.
Андре Карон,

const допомагає з такими конструкціями, як наведені нижче (та більш складні варіанти): int x = 10; int f () {return x; } проти const int x = 10; int f () {return x; }
сервен

2

Це насправді залежить від компілятора / платформи (це може допомогти оптимізації на якомусь компіляторі, який ще не написаний, або на якійсь платформі, яку ви ніколи не використовуєте). Стандарти С і С ++ нічого не говорять про продуктивність, крім як вимоги до складності для деяких функцій.

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

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


1

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

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

Посилання - це лише вказівки знизу, з точки зору реалізації.


Така ж угода, як і інші відповіді, я б зробив -1 - якщо це справді ніколи не було записано у всій програмі, компілятор зможе виконати такі види оптимізації, незалежно від того, позначено це constчи ні.
Billy ONeal,

1
@Billy: не для externзмінних.
Matthieu M.

@Matthieu: Він конкретно має на увазі оптимізацію цілої програми, що виконується під час зв’язку, коли externперестає багато значити .
Dennis Zickefoose

@Dennis: ах, вибачте, я звик мати справу з DLL, де WPO втрачає багато сил.
Matthieu M.

@Billy Можливо, теоретично, але на практиці в Linux принаймні, ви будете використовувати спільні бібліотеки (або писати спільну бібліотеку), а компілятор цього не міг зробити. Яка програма в Linux не використовує спільні бібліотеки? В основному жоден з них. Я не впевнений, що gcc робить це в будь-якому випадку, спільні бібліотеки чи ні - якщо так, це досить недавня функція.
Havoc P

0

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

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

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

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


Якщо елемент не модифікований, сьогодні пристойні компілятори знатимуть, що це constнормально, без того, щоб ви про це сказали. (Якщо говорити про останні MSVC ++ та GCC, обидва мають генерацію коду часу зв’язку)
Billy ONeal

1
@Billy: Оптимізація часу посилання не є магічним засобом, оскільки всі бібліотеки також повинні бути скомпільовані з включеним lto, не кажучи вже про спільні бібліотеки - компілятор не може міркувати про код, який він ніколи не бачить ...
Крістоф

@Christoph: Компілятор ніколи не може довіряти constкваліфікації щодо змінних, які він також не бачить. Цю пам'ять можуть зіпсувати зовсім інші мови, де constвона не існує, або вона може бути навіть записана на синьому ассемблері. У таких випадках, коли constзмінна стає доступною для цієї бібліотеки, компілятор повинен ігнорувати кваліфікацію const. Тому я не бачу, як це стосується цього питання.
Billy ONeal
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.