Чому можна взагалі використовувати модифікатор параметра “in” у C #?


84

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

Зазвичай я вважаю, що єдиною причиною використання a refє модифікація викличної змінної, що явно заборонено in. Тож передача за inпосиланням представляється логічно рівнозначною передачі за значенням.

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

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


1
Так, є перевага у виконанні. refвикористовується для передачі структур за допомогою посилання замість їх копіювання. inозначає, що структуру не слід модифікувати.
Panagiotis Kanavos

@dbc ні, це не має нічого спільного з
interop

4
Ефективність для типів значень. Нове ключове
слово

1
Детальне обговорення тут . Зверніть увагу на останнє попередження:It means that you should never pass a non-readonly struct as in parameter.
Panagiotis Kanavos

Я міг поклястись, що це дублікат, але я вже не можу його знайти
JAD,

Відповіді:


80

in нещодавно був представлений мовою C #.

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

Припускаючи, що у вас є:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}

і

void Process(in VeryLarge value) { }

У цьому випадку VeryLargeструктура буде передана за посиланням без створення захисних копій при використанні цієї структури в Processметоді (наприклад, при виклику value.Compute()), а незмінність структури забезпечується компілятором.

Зверніть увагу, що передача непрочитаного лише structз inмодифікатором призведе до того, що компілятор створить захисну копію під час виклику методів struct та доступу до властивостей у Processнаведеному вище методі, що негативно вплине на продуктивність!

Є справді хороший запис у блозі MSDN, який я рекомендую уважно прочитати.

Якщо ви хочете отримати більше історичної inдовідки про -представлення, ви можете прочитати це обговорення у сховищі GitHub мови C #.

Загалом, більшість розробників сходяться на думці, що введення програми inможна розглядати як помилку. Це досить екзотична мовна особливість і може бути корисною лише у випадках з високим рівнем ефективності.


Чи він придушує захисні копії, створені компілятором, або просто усуває необхідність програмістів створювати захисні копії вручну в контексті, який би використовувався в іншому випадку ref, наприклад, дозволяючи someProc(in thing.someProperty);проти propType myProp = thing.someProperty; someProc(ref myProp);? це inконцепція out, яка стосується лише C # , або вона була додана до .NET Framework?
supercat

@supercat, ти не можеш передати властивість за допомогою in, оскільки inце ефективно refза допомогою спеціального атрибута. Отже, ваш перший фрагмент не компілюється. @VisualMelon, це правильно, захисне копіювання відбувається під час виклику методів або доступу до властивостей структури зсередини методу, який отримує структуру як аргумент.
dymanoid

@dymanoid вибачте за спам, мені знадобилося близько 10 спроб зрозуміти, що ви написали (і я не думаю, що це ваша вина!)
VisualMelon

4
@dymanoid: І те, inі інше - outце поняття, які повинні були бути в фреймворку з самого початку (разом із засобом, за допомогою якого методи та властивості можуть вказувати, чи вони змінюються this). Компілятор може дозволити inпередачу властивості аргументу, передавши посилання на тимчасове його утримання; якщо функція, написана іншою мовою, модифікує цю тимчасову, семантика буде трохи хиткою, але це буде виною поведінки функції, яка не відповідає її підпису.
supercat

1
@supercat, будь ласка, не відкривай тут обговорення (я все одно не в команді концепцій .NET Framework). У відповіді є довга дискусія, і ця дискусія GitHub посилається на деякі інші - цікаве читання. До речі, ви можете передавати властивості by-ref у VB.NET - компілятор створює ці тимчасові файли для вас (що може призвести до незрозумілих проблем).
dymanoid

41

передача за inпосиланням здається логічно рівнозначною передачі за значенням.

Правильно.

Чи є якась перевага у виконанні?

Так.

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

Немає вимоги, щоб посилання на об'єкт і посилання на змінну мали однаковий розмір, і немає вимоги, щоб будь-який з них був розміром машинного слова, але так, на практиці обидва це 32 біти на 32 розрядні машини та 64 біти на 64-розрядних машинах.

Мені незрозуміло, яке відношення до вас має "фізична адреса". У Windows ми використовуємо віртуальні адреси , а не фізичні адреси в коді користувацького режиму. За яких можливих обставин ви могли б уявити, що фізична адреса має значення у програмі на C #, мені цікаво знати.

Також не вимагається, щоб будь-яке посилання було реалізоване як віртуальна адреса сховища. Посилання можуть бути непрозорими дескрипторами таблиць GC у відповідній реалізації специфікації CLI.

чи перевага лише у більших конструкціях?

Зниження вартості передачі більших конструкцій є мотиваційним сценарієм для функції.

Зауважте, що немає жодної гарантії, що inробить будь-яку програму насправді швидшою, і вона може робити програми повільнішими. На всі питання щодо результативності слід відповісти емпіричним дослідженням . Дуже мало оптимізацій, які завжди є перемогами ; це не оптимізація "завжди перемагай".

чи є якась закулісна оптимізація компілятора, яка робить її привабливою в іншому місці?

Компілятору та середовищу виконання дозволено робити будь-яку оптимізацію, яку вони обрали, якщо це не порушує правила специфікації C #. Наскільки мені відомо, ще не така оптимізація inпараметрів, але це не виключає такої оптимізації в майбутньому.

чому б мені не зробити кожен параметр вхідним?

Ну, припустимо, ви зробили intпараметр замість in intпараметра. Які витрати покладаються?

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

Припустимо, це a, doubleі ви змінили його на an in double. Знову ж таки, тепер змінну неможливо зареєструвати у високоефективному регістрі з плаваючою комою. Це не тільки впливає на продуктивність , але і може змінити поведінку програми! C # дозволено робити арифметику з плаваючою системою з точністю вище 64-бітової, і зазвичай це робиться лише за умови, що плаваючі можна зареєструвати.

Це не безкоштовна оптимізація. Ви повинні виміряти його ефективність у порівнянні з альтернативними. Ваша найкраща ставка - це просто не робити великі конструкції насамперед, як пропонують керівні принципи проектування.


"За яких [...] можливих обставин ви могли б уявити, що фізична адреса має значення у програмі на C #, [...]." . C # зазвичай використовується в системах, що мають введені / виведені на карту пам'яті, а також такі речі, як вектори скидання тощо. Тут я думаю про звичайні старі коробки Wintel. З процесорами x86 та кадровими буферами VGA. Гаразд, зазвичай ми не маніпулюємо ними безпосередньо, але ми могли б, так?
OmarL

5
Чи проходження повз inнасправді еквівалентно проходженню за значенням у всіх випадках? Якщо ми маємо f(ref T x, in T y)та fмодифікуємо x, він повинен спостерігати ту ж зміну, що і yпри виклику, як f(ref a,a). Те ж саме стосується, якщо fприймає a in T yі делегат, який буде змінюватися yпри виклику. Без inцього семантика була б іншою, оскільки yніколи не змінювало б її значення, я думаю, оскільки це була б копія.
chi

2
Я підозрюю, що OP означав "фізичну адресу" у неформальній формі, як "бітовий шаблон, що описує розташування пам'яті", протиставляючи це "будь-якому бекенду, який вирішить використовувати для опису, де знайти об'єкт".
Снефтель,

@Wilson: Я б хотів побачити приклад того, про що ви говорите; Я не знаю нікого, хто намагався б робити подібні речі в C #. Протягом багатьох років було кілька розмов про "системний C #", згідно з яким керована мова вищого рівня може бути використана для написання системних компонентів низького рівня, але я сам ніколи не бачив такого коду.
Ерік Ліпперт,

2
@chi: Це правильно. Якщо ви граєте в небезпечні ігри зі змінними псевдонімами, то можете потрапити в біду. Якщо боляче, коли ти робиш це, не роби цього. Намір з inполягає в поданні поняття «прохід по посиланню, тільки для читання», яка логічно еквівалентна передачі за значенням , якщо ви будете вести себе розсудливо .
Ерік Ліпперт,

6

Є. При пропущенні struct, то inключове слово дозволяє оптимізувати , де компілятор потрібно тільки передати покажчик, без ризику методу зміни змісту. Останнє є критичним - воно дозволяє уникнути операції копіювання. Для великих конструкцій це може змінити світ.


2
Це просто повторює те, що вже сказано у запитанні, і не відповідає на фактичне запитання, яке воно задає.
Серві

Тільки в структурах для читання. В іншому випадку компілятор все одно створить захисну копію
Панайотис Канавос,

1
Насправді ні. Це підтверджує наявність ефективної вигоди. Враховуючи те, що IN було створено під час виконання, що проходить через мову, так, це Є причина. Це ДОЗВОЛЯЄ оптимізацію (для конструкцій лише для читання).
TomTom

3
@TomTom Знову, що питання вже охоплювало . Він запитує: "Отже, тоді перевага просто у великих структурах, або є якась закулісна оптимізація компілятора, яка робить її привабливою в іншому місці? Якщо остання, чому б мені не вводити кожен параметр як"? " Зверніть увагу, як це не запитання, чи насправді це корисно для більших конструкцій, чи просто тоді, коли це корисно взагалі. Це просто запитання, чи корисно це для менших структур (і потім подальші дії, якщо так). Ви не відповіли ні на одне запитання.
Серві

2

Це робиться завдяки підходу функціонального програмування. Одним з головних принципів є те, що функція не повинна мати побічних ефектів, а це означає, що вона не повинна змінювати значення параметрів і повинна повертати деяке значення. У C # не було можливості передати структури (і тип значення), не скопіювавши лише посилання, що дозволяє змінювати значення. У swift існує хакерський алгоритм, який копіює struct (їх колекції є структурою BTW) до тих пір, поки метод починає змінювати свої значення. Люди, які швидко використовують, не всі знають про копіювання. Це приємна функція c #, оскільки вона ефективна і явна. Якщо ви подивитесь, що новогоВи побачите, що все більше і більше матеріалів робиться навколо структур і масивів у стеку. І в заяві просто необхідні ці особливості. Існують обмеження, згадані в інших відповідях, але це не так важливо для розуміння того, куди прямує .net.


2

в ній читається лише посилання на c # 7.2

це означає, що ви не передаєте цілий об'єкт у функціональний стек, подібно до випадку ref, ви передаєте лише посилання на структуру

але спроба змінити значення об'єкта видає помилку компілятора.

Так, це дозволить вам оптимізувати роботу коду, якщо ви використовуєте великі структури.

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