Зачем вообще использовать модификатор параметра «in» в C #?


86

Итак, я (думаю, я) понимаю, что inделает модификатор параметра. Но то, что он делает, кажется совершенно избыточным.

Обычно я думаю, что единственной причиной использования a refбыло бы изменение вызывающей переменной, что явно запрещено in. Таким образом, передача по inссылке кажется логически эквивалентной передаче по значению.

Есть ли какое-то преимущество в производительности? Я считал, что на внутренней стороне вещей refпараметр должен как минимум копировать физический адрес переменной, который должен быть того же размера, что и любая типичная ссылка на объект.

Итак, тогда преимущество только в более крупных структурах, или есть некоторая закулисная оптимизация компилятора, которая делает его привлекательным в другом месте? В последнем случае почему бы мне не сделать каждый параметр значением in?


1
Да, есть преимущество в производительности. refиспользуется для передачи структур по ссылке вместо их копирования. inозначает, что структуру нельзя изменять.
Панайотис Канавос

@dbc нет, это не имеет ничего общего с взаимодействием
Панайотис Канавос

5
Производительность для типов значений. Новое ключевое слово «in» для C # 7.2
Silvermind

1
Подробное обсуждение здесь . Обратите внимание на последнее предупреждение:It means that you should never pass a non-readonly struct as in parameter.
Панайотис Канавос

Я мог бы поклясться, что это дубликат, но я больше не могу его найти
JAD

Ответы:


81

in недавно был представлен язык C #.

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

Если у вас есть:

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

    public long Compute() { }
    // etc
}

и

void Process(in VeryLarge value) { }

В этом случае VeryLargeструктура будет передана по ссылке без создания защитных копий при использовании этой структуры в Processметоде (например, при вызове value.Compute()), а неизменяемость структуры обеспечивается компилятором.

Обратите внимание, что передача not-readonly structс inмодификатором заставит компилятор создать защитную копию при вызове методов структуры и доступе к свойствам в Processметоде выше, что отрицательно скажется на производительности!

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

Если вы хотите получить больше исторической справки о inвведении, вы можете прочитать это обсуждение в репозитории GitHub языка C #.

В целом, большинство разработчиков согласны с тем, что внедрение inможет рассматриваться как ошибка. Это довольно экзотическая языковая функция, которая может быть полезна только в высокопроизводительных случаях.


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

@supercat, вы не можете передать свойство using in, потому что inэто эффективно refсо специальным атрибутом. Итак, ваш первый фрагмент не будет компилироваться. @VisualMelon, верно, защитное копирование происходит при вызове методов или доступе к свойствам структуры изнутри метода, который получает структуру как аргумент.
dymanoid

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

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

1
@supercat, пожалуйста, не открывайте здесь обсуждение (я все равно не в команде разработчиков .NET Framework). В ответе есть длинное обсуждение, и это обсуждение GitHub ссылается на некоторые другие - интересное чтение. Кстати, вы можете передавать свойства по ссылке в VB.NET - компилятор создает эти временные файлы для вас (что, конечно, может привести к неясным проблемам).
dymanoid

42

передача по inссылке кажется логически эквивалентной передаче по значению.

Верный.

Есть ли какое-то преимущество в производительности?

Да.

Я считал, что на внутренней стороне вещей refпараметр должен как минимум копировать физический адрес переменной, который должен быть того же размера, что и любая типичная ссылка на объект.

Не существует требования, чтобы ссылка на объект и ссылка на переменную были одинакового размера, и не требуется, чтобы любой из них был размером машинного слова, но да, на практике оба 32 бита на 32 битовые машины и 64-битные на 64-битных машинах.

Как вы думаете, какое отношение к этому имеет "физический адрес", мне неясно. В Windows мы используем виртуальные адреса , а не физические адреса в коде пользовательского режима. Мне любопытно знать, при каких возможных обстоятельствах вы можете представить, что физический адрес имеет значение в программе на C #.

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

преимущество только в более крупных структурах?

Снижение стоимости передачи больших структур - мотивирующий сценарий для этой функции.

Обратите внимание, что нет никакой гарантии, что inкакая-либо программа действительно будет быстрее или медленнее. На все вопросы о производительности необходимо ответить эмпирическим исследованием . Очень мало оптимизаций, которые всегда приносят пользу ; это не всегда выигрышная оптимизация.

есть ли какая-то закулисная оптимизация компилятора, которая делает его привлекательным в других местах?

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

почему бы мне не сделать каждый параметр входным?

Что ж, предположим, вы создали intпараметр вместо in intпараметра. Какие накладные расходы?

  • для сайта вызова теперь требуется переменная, а не значение
  • переменная не может быть зарегистрирована. Тщательно настроенная схема распределения регистров джиттера только что была добавлена ​​в нее.
  • код на сайте вызова больше, потому что он должен принимать ссылку на переменную и помещать ее в стек, тогда как раньше он мог просто поместить значение в стек вызовов
  • Более крупный код означает, что некоторые инструкции короткого перехода теперь могли стать инструкциями длинного перехода, поэтому, опять же, код теперь больше. Это накладывает отпечаток на самые разные вещи. Кеши заполняются быстрее, дрожание требует больше работы, дрожание может не выполнять определенные оптимизации для больших размеров кода и так далее.
  • на вызываемом сайте мы превратили доступ к значению в стеке (или регистре) в косвенное обращение к указателю. Теперь этот указатель, скорее всего, находится в кеше, но все же мы превратили доступ с одной инструкцией к значению в доступ с двумя инструкциями.
  • И так далее.

Предположим, это a, doubleи вы измените его на 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, я думаю, ее значение никогда не изменилось бы, поскольку это была бы копия.
Чи

2
Я подозреваю, что OP означал «физический адрес» неформальным образом, как «битовый шаблон, описывающий расположение в памяти», в отличие от «того, что серверная часть выбирает для использования для описания места нахождения объекта».
Sneftel

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

2
@chi: Это правильно. Если вы играете в опасные игры с переменным псевдонимом, у вас могут возникнуть проблемы. Если это причиняет боль, не делайте этого. Намерение из inзаключается в представлении понятие «проход по ссылке, только для чтения», которая логически эквивалентна передаче по значению , если вы будете вести себя благоразумно .
Эрик Липперт

6

Есть. При пропускании struct, то inключевое слово позволяет оптимизировать , где компилятор нужно только передать указатель, без риска метода изменения содержания. Последнее критично - это позволяет избежать операции копирования. Для больших структур это может иметь огромное значение.


2
Это просто повторение того, что уже сказано в вопросе, и не отвечает на вопрос, который задается.
Servy

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

1
Вообще-то, нет. Это подтверждение того, что есть преимущества в производительности. Учитывая, что IN был создан в процессе выполнения, проходящего через langauge, да, это причина. Это ДОПУСКАЕТ оптимизацию (для структур только для чтения).
TomTom

3
@TomTom Опять же, вопрос уже обсуждался . Он спрашивает: «Так, тогда преимущество только в более крупных структурах, или есть некоторая закулисная оптимизация компилятора, которая делает его привлекательным в другом месте? Если последнее, почему бы мне не сделать каждый параметр входным?» Обратите внимание на то, что он не спрашивает, действительно ли это полезно для больших структур или просто когда это вообще полезно. Он просто спрашивает, полезно ли это для небольших структур (и затем, если да). Вы не ответили ни на один вопрос.
Servy

2

Это сделано из-за подхода функционального программирования. Один из основных принципов заключается в том, что функция не должна иметь побочных эффектов, что означает, что она не должна изменять значения параметров и должна возвращать какое-то значение. В C # не было возможности передавать структуры (и тип значения) без копирования только по ссылке, что позволяет изменять значение. В swift есть хакерский алгоритм, который копирует структуры (их коллекции - это структуры BTW), пока метод начинает изменять свои значения. Люди, которые используют swift, не все знают о копиях. Это хорошая функция C #, так как она эффективно использует память и явно. Если вы посмотрите на что новоговы увидите, что все больше и больше делается вокруг структур и массивов в стеке. И для этих функций in statement просто необходимо. В других ответах упоминаются ограничения, но это не так важно для понимания того, куда движется .net.


2

в нем ссылка только для чтения в С # 7.2

это означает, что вы не передаете весь объект в стек функций, как и в случае ref, вы передаете только ссылку на структуру

но попытка изменить значение объекта дает ошибку компилятора.

И да, это позволит вам оптимизировать производительность кода, если вы используете большие структуры.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.