Dlaczego ktoś miałby kiedykolwiek używać modyfikatora parametru „in” w C #?


86

Tak więc (chyba rozumiem) rozumiem, co inrobi modyfikator parametrów. Ale to, co robi, wydaje się dość zbędne.

Zwykle wydaje mi się, że jedynym powodem użycia a refbyłaby modyfikacja zmiennej wywołującej, co jest wyraźnie zabronione przez in. Zatem przekazywanie przez inodniesienie wydaje się logicznie równoważne z przekazywaniem przez wartość.

Czy jest jakaś przewaga wydajności? Wierzyłem, że po stronie zaplecza refparametr musi przynajmniej kopiować fizyczny adres zmiennej, który powinien mieć taki sam rozmiar jak każde typowe odniesienie do obiektu.

Czy zatem korzyść dotyczy tylko większych struktur, czy też jest jakaś zakulisowa optymalizacja kompilatora, która czyni go atrakcyjnym w innym miejscu? Jeśli to drugie, dlaczego nie miałbym zmienić każdego parametru na in?


1
Tak, istnieje przewaga wydajności. refsłuży do przekazywania struktur przez odwołanie zamiast ich kopiowania. inoznacza, że ​​struktura nie powinna być modyfikowana.
Panagiotis Kanavos

@dbc nie, to nie ma nic wspólnego z interopem
Panagiotis Kanavos

5
Wydajność dla typów wartości. Nowe słowo kluczowe „w” dla języka C # 7.2
Silvermind

1
Szczegółowa dyskusja tutaj . Zwróć uwagę na ostatnie ostrzeżenie:It means that you should never pass a non-readonly struct as in parameter.
Panagiotis Kanavos

Mógłbym przysiąc, że to duplikat, ale nie mogę go już znaleźć
JAD

Odpowiedzi:


81

in został niedawno wprowadzony do języka C #.

injest w rzeczywistości ref readonly. Ogólnie rzecz biorąc, jest tylko jeden przypadek użycia, w którym inmoże być pomocny: wysokowydajne aplikacje obsługujące wiele dużych readonly structplików s.

Zakładając, że masz:

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

    public long Compute() { }
    // etc
}

i

void Process(in VeryLarge value) { }

W takim przypadku VeryLargestruktura zostanie przekazana przez referencję bez tworzenia kopii obronnych podczas używania tej struktury w Processmetodzie (np. Podczas wywoływania value.Compute()), a niezmienność struktury jest zapewniana przez kompilator.

Zauważ, że przekazanie not-readonly structz inmodyfikatorem spowoduje, że kompilator utworzy kopię obronną podczas wywoływania metod struktury i uzyskiwania dostępu do właściwości w Processmetodzie powyżej, co negatywnie wpłynie na wydajność!

Na blogu MSDN jest naprawdę dobry wpis, który polecam uważnie przeczytać.

Jeśli chcesz uzyskać więcej informacji na temat historii in-wprowadzania, możesz przeczytać tę dyskusję w repozytorium GitHub języka C #.

Ogólnie większość programistów zgadza się, że wprowadzenie programu inmoże być postrzegane jako błąd. Jest to dość egzotyczna funkcja językowa i może być przydatna tylko w przypadkach o wysokiej wydajności.


Czy tłumi kopie obronne generowane przez kompilator, czy po prostu eliminuje potrzebę ręcznego tworzenia kopii obronnych przez programistów w kontekstach, które w innym przypadku byłyby używane ref, np. Zezwalając someProc(in thing.someProperty);kontra propType myProp = thing.someProperty; someProc(ref myProp);? jest inpojęciem C #-only jak out, czy też zostało dodane do .NET Framework?
supercat

@supercat, nie możesz przekazać właściwości przy użyciu in, ponieważ w inrzeczywistości refma specjalny atrybut. Więc Twój pierwszy fragment kodu nie zostanie skompilowany. @VisualMelon, to prawda, kopiowanie obronne zachodzi podczas wywoływania metod lub uzyskiwania dostępu do właściwości struktury z poziomu metody, która pobiera strukturę jako argument.
dymanoid

@dymanoid przepraszam, że cię spamowałem, zajęło mi około 10 prób zrozumienia tego, co napisałeś (i nie sądzę, że to twoja wina!)
VisualMelon

4
@dymanoid: Obie koncepcje ini outsą koncepcjami, które powinny znajdować się w Framework od samego początku (wraz ze środkami, za pomocą których metody i właściwości mogą wskazywać, czy modyfikują this). Kompilator mógłby pozwolić na przekazanie właściwości do inargumentu przez przekazanie odniesienia do tymczasowego jej przechowywania; jeśli funkcja napisana w innym języku modyfikuje to tymczasowe, semantyka byłaby nieco nieprzyjemna, ale byłaby to wina funkcji niezgodnej z jej sygnaturą.
supercat

1
@supercat, proszę nie otwierać tutaj dyskusji (i tak nie jestem w zespole koncepcyjnym .NET Framework). W odpowiedzi pojawia się długa dyskusja, a dyskusja na GitHubie nawiązuje do kilku innych - interesująca lektura. BTW, możesz przekazać właściwości by-ref w VB.NET - kompilator tworzy te tymczasowe dla Ciebie (co oczywiście może prowadzić do niejasnych problemów).
dymanoid

42

przekazywanie przez inodniesienie wydaje się logicznie równoważne przekazywaniu przez wartość.

Poprawny.

Czy jest jakaś przewaga wydajności?

Tak.

Wierzyłem, że po stronie zaplecza refparametr musi przynajmniej kopiować fizyczny adres zmiennej, który powinien mieć taki sam rozmiar jak każde typowe odniesienie do obiektu.

Nie ma wymogu, aby odwołanie do obiektu i odniesienie do zmiennej miały ten sam rozmiar, i nie ma wymogu, aby każdy z nich był wielkością słowa maszynowego, ale tak, w praktyce oba mają 32 bity na 32 maszyny bitowe i 64-bitowe na maszynach 64-bitowych.

Jak myślisz, co ma z tym wspólnego „adres fizyczny”, nie jest dla mnie jasne. W systemie Windows używamy adresów wirtualnych , a nie adresów fizycznych w kodzie trybu użytkownika. Ciekaw jestem, w jakich możliwych okolicznościach możesz sobie wyobrazić, że adres fizyczny ma znaczenie w programie C #.

Nie ma również wymogu implementacji jakiegokolwiek odniesienia jako adresu wirtualnego magazynu. Odwołania mogą być nieprzezroczystymi uchwytami do tabel GC w zgodnej implementacji specyfikacji CLI.

czy zaletą jest tylko w przypadku większych konstrukcji?

Motywującym scenariuszem dla tej funkcji jest obniżenie kosztu przekazywania większych konstrukcji.

Zwróć uwagę, że nie ma gwarancji, inże program będzie faktycznie szybszy i może spowalniać programy. Na wszystkie pytania dotyczące wydajności muszą odpowiedzieć badania empiryczne . Istnieje bardzo niewiele optymalizacji, które zawsze przynoszą korzyści ; nie jest to optymalizacja typu „zawsze wygrywająca”.

czy jest jakaś zakulisowa optymalizacja kompilatora, która czyni go atrakcyjnym gdzie indziej?

Kompilator i środowisko uruchomieniowe mogą dokonywać dowolnej optymalizacji, którą wybiorą, jeśli nie narusza to reguł specyfikacji języka C #. O inile mi wiadomo, nie ma jeszcze takiej optymalizacji parametrów, ale to nie wyklucza takich optymalizacji w przyszłości.

dlaczego nie powinienem uczynić każdego parametru in?

Załóżmy, że intzamiast parametru utworzyłeś in intparametr. Jakie koszty są nakładane?

  • witryna wywołania wymaga teraz zmiennej, a nie wartości
  • zmienna nie może zostać zarejestrowana. Starannie dostrojony schemat alokacji rejestrów jittera właśnie został wrzucony do niego kluczem.
  • kod w miejscu wywołania jest większy, ponieważ musi pobierać odwołanie do zmiennej i umieścić go na stosie, podczas gdy wcześniej mógł po prostu umieścić wartość na stosie wywołań
  • większy kod oznacza, że ​​niektóre instrukcje krótkiego skoku mogły teraz stać się instrukcjami skoku w dal, więc znowu kod jest teraz większy. Ma to efekt domina na różne rzeczy. Pamięci podręczne zapełniają się wcześniej, jitter ma więcej do zrobienia, jitter może zdecydować się nie wykonywać pewnych optymalizacji dla większych rozmiarów kodu i tak dalej.
  • w witrynie wywoływanej zmieniliśmy dostęp do wartości na stosie (lub rejestrze) w pośredni wskaźnik. Prawdopodobnie ten wskaźnik znajduje się w pamięci podręcznej, ale nadal zmieniliśmy dostęp do wartości z jedną instrukcją w dostęp z dwiema instrukcjami.
  • I tak dalej.

Załóżmy, że to a doublei zmienisz go na in double. Ponownie, teraz zmienna nie może zostać zarejestrowana w wysokowydajnym rejestrze zmiennoprzecinkowym. Ma to wpływ nie tylko na wydajność , ale może również zmienić zachowanie programu! C # może wykonywać arytmetykę zmiennoprzecinkową z dokładnością wyższą niż 64-bitowa i zazwyczaj robi to tylko wtedy, gdy można zarejestrować zmiennoprzecinkowe.

To nie jest darmowa optymalizacja. Musisz porównać jego wydajność z alternatywami. Najlepiej jest po prostu nie tworzyć dużych konstrukcji, jak sugerują wytyczne projektowe.


„W jakich [...] możliwych okolicznościach można sobie wyobrazić, że adres fizyczny ma znaczenie w programie C #, [...]”. . C # jest zwykle używany w systemach, które mają operacje we / wy mapowane w pamięci i takie rzeczy, jak wektory resetowania itp. Myślę tutaj o zwykłych starych pudełkach wintel. Z procesorami x86 i buforami ramki VGA. Okej, zwykle nie manipulujemy nimi bezpośrednio, ale moglibyśmy, prawda?
OmarL

5
Czy mijanie innaprawdę jest równoznaczne z przekazywaniem wartości we wszystkich przypadkach? Jeśli mamy f(ref T x, in T y)i fmodyfikuje x, powinien obserwować tę samą zmianę, ygdy jest wywoływany jako f(ref a,a). To samo dotyczy sytuacji, gdy fprzyjmuje a in T yi delegata, który będzie modyfikowany ypo wywołaniu. Bez insemantyki byłaby inna, ponieważ ymyślę, że nigdy nie zmieniłaby się jej wartość, ponieważ byłaby to kopia.
chi

2
Podejrzewam, że OP oznaczał „adres fizyczny” w nieformalny sposób, jako „wzorzec bitowy opisujący lokalizację pamięci”, kontrastując to z „czymkolwiek backend zdecyduje się użyć do opisania, gdzie znaleźć obiekt”.
Sneftel

@Wilson: Chciałbym zobaczyć przykład tego, o czym mówisz; Nie znam nikogo, kto próbowałby robić tego typu rzeczy w C #. Przez lata mówiło się o „systemowym C #”, w którym język zarządzany wyższego poziomu mógłby być używany do pisania niskopoziomowych komponentów systemu, ale nigdy nie widziałem takiego kodu.
Eric Lippert

2
@chi: Zgadza się. Jeśli grasz w niebezpieczne gry ze zmiennym aliasowaniem, możesz wpaść w kłopoty. Jeśli to boli, nie rób tego. Zamiar od injest do reprezentowania pojęcia „przejściu przez odniesienie tylko do odczytu”, co jest logicznie równoważne przechodzącej przez wartość , gdy zachowują się rozsądnie .
Eric Lippert

6

Jest. Podczas przekazywania a struct, insłowo kluczowe umożliwia optymalizację, w której kompilator musi tylko przekazać wskaźnik, bez ryzyka zmiany zawartości przez metodę. To ostatnie jest krytyczne - pozwala uniknąć operacji kopiowania. W przypadku dużych struktur może to mieć ogromne znaczenie.


2
To tylko powtórzenie tego, co już mówi pytanie, i nie odpowiada na zadane pytanie.
Servy

Tylko w strukturach tylko do odczytu. W przeciwnym razie kompilator nadal będzie tworzył kopię obronną
Panagiotis Kanavos

1
Właściwie nie. Jest to potwierdzenie, że istnieje korzyść w zakresie wydajności. Biorąc pod uwagę, że IN zostało stworzone podczas wykonywania przez język, tak, to JEST powód. POZWALA na optymalizację (na strukturach tylko do odczytu).
TomTom

3
@TomTom Again, które już dotyczyło pytania . Pytanie brzmi: „Czy zatem korzyść jest tylko w przypadku większych struktur, czy też jest jakaś ukryta optymalizacja kompilatora, która czyni go atrakcyjnym w innym miejscu? Jeśli to drugie, dlaczego nie miałbym uczynić każdego parametru in?”. Zwróć uwagę, że nie pyta, czy jest to rzeczywiście korzystne dla większych struktur, czy tylko wtedy, gdy w ogóle jest korzystne. Po prostu pyta, czy jest to korzystne dla mniejszych struktur (a następnie kontynuacja, jeśli tak). Nie odpowiedziałeś na żadne pytanie.
Servy

2

Dzieje się tak ze względu na podejście do programowania funkcjonalnego. Jedną z głównych zasad jest to, że funkcja nie powinna mieć skutków ubocznych, co oznacza, że ​​nie powinna zmieniać wartości parametrów i powinna zwracać jakąś wartość. W C # nie było możliwości przekazania struktur (i typu wartości) bez kopiowania ich tylko przez odwołanie, które pozwala na zmianę wartości. W swift jest zepsuty algorytm, który kopiuje struct (przy okazji, ich kolekcje to structs) tak długo, jak metoda zacznie zmieniać swoje wartości. Ludzie, którzy używają szybkiego, nie wszyscy są świadomi kopiowania. Jest to przyjemna funkcja języka C #, ponieważ jest wydajna pod względem pamięci i jawna. Jeśli spojrzysz na to, co nowegozobaczysz, że wokół struktur i tablic w stosie robi się coraz więcej rzeczy. A w zestawieniu jest to konieczne dla tych funkcji. Istnieją ograniczenia wymienione w innych odpowiedziach, ale nie są one niezbędne do zrozumienia, dokąd zmierza .net.


2

in to odwołanie tylko do odczytu w c # 7.2

oznacza to, że nie przekazujesz całego obiektu do stosu funkcji, podobnie jak w przypadku ref , przekazujesz tylko referencję do struktury

ale próba zmiany wartości obiektu powoduje błąd kompilatora.

I tak, pozwoli to zoptymalizować wydajność kodu, jeśli używasz dużych struktur.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.