Wydajność metod statycznych a metody instancyjne

108

Moje pytanie dotyczy charakterystyki wydajności metod statycznych względem metod instancyjnych oraz ich skalowalności. Załóżmy, że w tym scenariuszu wszystkie definicje klas znajdują się w jednym zestawie i wymagane jest wiele dyskretnych typów wskaźników.

Rozważać:

public sealed class InstanceClass
{
      public int DoOperation1(string input)
      {
          // Some operation.
      }

      public int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more instance methods.
}

public static class StaticClass
{
      public static int DoOperation1(string input)
      {
          // Some operation.
      }

      public static int DoOperation2(string input)
      {
          // Some operation.
      }

      // … more static methods.
}

Powyższe klasy reprezentują wzorzec stylu pomocnika.

W klasie instancji rozwiązanie metody instancji zajmuje chwilę, w przeciwieństwie do metody StaticClass.

Moje pytania to:

  1. Kiedy zachowanie stanu nie jest problemem (nie są wymagane żadne pola ani właściwości), czy zawsze lepiej jest używać klasy statycznej?

  2. Jeśli istnieje znaczna liczba tych statycznych definicji klas (na przykład 100, z których każda zawiera wiele metod statycznych), czy wpłynie to negatywnie na wydajność wykonywania lub zużycie pamięci w porównaniu z taką samą liczbą definicji klas instancji?

  3. Gdy wywoływana jest inna metoda w tej samej klasie instancji, czy rozpoznawanie instancji nadal występuje? Na przykład użycie słowa kluczowego [this] jak this.DoOperation2("abc")z DoOperation1tego samego wystąpienia.

Bernie White
źródło
co rozumiesz przez „rozwiązywanie instancji”? Na poziomie IL wskaźnik „this” jest dostępny tak samo, jak każda inna zmienna lokalna. W rzeczywistości w niektórych starych wersjach CLR / JIT można wywołać metodę instancji na wartości NULL, pod warunkiem, że nie dotknęła ona „this” - kod po prostu przeleciał i nie zawiesił się na niczym. Teraz CLR / JIT zawiera jawne null- sprawdź, czy każdy członek przywołuje ..
quetzalcoatl
> vijaymukhi.com/documents/books/ilbook/chap8.htm i „zadzwoń do instancji” zamiast „zadzwoń”. Pierwsza oczekuje parametru „tego”, a druga - nie.
quetzalcoatl
@Quetzalcoatl przepraszam za zamieszanie, pytanie było bardziej metodą metody z tej samej instancji i czy to wymaga rozwiązania instancji dla siebie.
Bernie White,
1
@quetzalcoatl Przypuszczałem, że miał na myśli: „czy kompilator pozbywa się sprawdzania, czy thiscoś wskazuje na coś, gdy klasa wywołuje metodę instancji na sobie?”.
Jon Hanna,

Odpowiedzi:

152

Teoretycznie metoda statyczna powinna działać nieco lepiej niż metoda instancji, przy czym wszystkie inne thisparametry powinny być takie same, ze względu na dodatkowy ukryty parametr.

W praktyce robi to tak małą różnicę, że będzie ukryte w szumie różnych decyzji kompilatora. (W związku z tym dwie osoby mogą „udowodnić” jedną lepiej niż drugą z różnymi wynikami). Nie tylko dlatego, że thisjest zwykle przekazywany w rejestrze i często znajduje się w tym rejestrze na początku.

Ten ostatni punkt oznacza, że ​​teoretycznie powinniśmy spodziewać się statycznej metody, która przyjmuje obiekt jako parametr i robi coś z nim, będzie nieco gorsza niż odpowiednik jako instancja tego samego obiektu. Znowu jednak różnica jest tak niewielka, że ​​gdybyś próbował ją zmierzyć, prawdopodobnie skończyłbyś mierzeniem innej decyzji kompilatora. (Zwłaszcza, że ​​prawdopodobieństwo, że odniesienie to będzie w rejestrze przez cały czas, jest również dość wysokie).

Rzeczywiste różnice w wydajności sprowadzą się do tego, czy sztucznie masz obiekty w pamięci, aby zrobić coś, co naturalnie powinno być statyczne, czy też splątasz łańcuchy przekazywania obiektów w skomplikowany sposób, aby zrobić to, co powinno naturalnie być instancją.

Stąd dla numeru 1. Kiedy utrzymywanie stanu nie jest problemem, zawsze lepiej być statycznym, ponieważ statystyka służy temu . Nie jest to problem z wydajnością, chociaż istnieje ogólna zasada ładnego grania z optymalizacjami kompilatora - jest bardziej prawdopodobne, że ktoś zadał sobie trud optymalizacji przypadków, które pojawiają się przy normalnym użyciu, niż tych, które wymyślają dziwne użycie.

Numer 2. Nie ma różnicy. Dla każdego członka istnieje pewna kwota kosztu na klasę, która zależy zarówno od ilości metadanych, ile kodu znajduje się w rzeczywistym pliku DLL lub EXE, jak i od tego, ile będzie w nim utraconego kodu. To jest to samo, czy jest to instancja, czy statyczna.

W przypadku pozycji 3 thisjest tak thissamo. Jednak uwaga:

  1. thisParametr jest przekazywana w danym rejestrze. Podczas wywoływania metody instancji w tej samej klasie, prawdopodobnie będzie ona już w tym rejestrze (chyba że została ukryta i rejestr używany z jakiegoś powodu), a zatem nie jest wymagana żadna akcja, aby ustawić thisto, co należy ustawić . Dotyczy to w pewnym stopniu np. Pierwszych dwóch parametrów metody, która jest pierwszymi dwoma parametrami wywołania, które wykonuje.

  2. Ponieważ będzie jasne, że thisnie jest to null, w niektórych przypadkach może to służyć do optymalizacji połączeń.

  3. Ponieważ będzie jasne, że thisnie jest null, może to ponownie zwiększyć wydajność wywołań metod wbudowanych, ponieważ kod utworzony w celu sfałszowania wywołania metody może pominąć niektóre sprawdzenia wartości null, których i tak może potrzebować.

  4. To powiedziawszy, czeki zerowe są tanie!

Warto zauważyć, że ogólne metody statyczne działające na obiekcie, a nie metody instancji, mogą zmniejszyć niektóre koszty omówione na stronie http://joeduffyblog.com/2011/10/23/on-generics-and-some-of- związane z narzutami / w przypadku, gdy dana statyczna nie jest wywoływana dla danego typu. Jak to ujął: „Na marginesie okazuje się, że metody rozszerzające to świetny sposób, aby generyczne abstrakcje były bardziej opłacalne”.

Należy jednak pamiętać, że dotyczy to tylko tworzenia instancji innych typów używanych przez metodę, które w innym przypadku nie istnieją. W związku z tym tak naprawdę nie ma zastosowania w wielu przypadkach (inna metoda instancji używała tego typu, jakiś inny kod w innym miejscu używał tego typu).

Podsumowanie:

  1. Przeważnie koszty wydajności instancji w porównaniu ze statycznymi są poniżej nieistotnych.
  2. Jakie są koszty, generalnie pojawią się, gdy na przykład nadużyjesz statyczności lub odwrotnie. Jeśli nie podejmiesz decyzji o wyborze między statycznym a instancją, masz większe szanse na uzyskanie prawidłowego wyniku.
  3. Istnieją rzadkie przypadki, w których statyczne metody ogólne innego typu powodują utworzenie mniejszej liczby typów niż metody generyczne instancji, co może czasami mieć niewielką korzyść, gdy są rzadko używane (i „rzadko” odnosi się do typów, z którymi są używane w żywotność aplikacji, a nie jak często jest wywoływana). Kiedy już dowiesz się, o czym mówi w tym artykule, zobaczysz, że i tak jest to w 100% nieistotne dla większości decyzji statycznych i instancji. Edycja: I przeważnie ma ten koszt tylko z ngen, a nie z jittedem.

Edycja: uwaga na temat tego, jak tanie są czeki zerowe (które twierdziłem powyżej). Większość sprawdzeń null w .NET w ogóle nie sprawdza wartości null, raczej kontynuuje to, co zamierzali zrobić, zakładając, że zadziała, a jeśli zdarzy się wyjątek dostępu, zostanie zamieniony w plik NullReferenceException. W związku z tym, głównie wtedy, gdy koncepcyjnie kod C # obejmuje sprawdzenie wartości null, ponieważ uzyskuje dostęp do elementu członkowskiego wystąpienia, koszt, jeśli się powiedzie, wynosi w rzeczywistości zero. Wyjątkiem byłyby niektóre wbudowane wywołania (ponieważ chcą zachowywać się tak, jakby dzwonili do członka instancji) i po prostu uderzają w pole, aby wywołać to samo zachowanie, więc są również bardzo tanie i i tak często można je pominąć (np. jeśli pierwszy krok metody obejmował dostęp do pola w takiej postaci).

Jon Hanna
źródło
Czy możesz skomentować, czy pytanie statyczne a pytanie o instancję ma wpływ na spójność pamięci podręcznej? Czy poleganie na jednym lub drugim jest bardziej prawdopodobne, aby spowodować błędy pamięci podręcznej? Czy istnieje dobry zarys wyjaśniający dlaczego?
scriptocalypse
@scriptocalypse Niezupełnie. Pamięć podręczna instrukcji nie zauważy żadnej różnicy, a na tym poziomie nie ma dużej różnicy między dostępem do danych za pośrednictwem thislub przez jawny parametr. Większy wpływ miałby tutaj to, jak blisko danych są powiązane dane (pola typu wartości lub wartości tablic są bliżej niż dane w polach typu referencyjnego) oraz wzorce dostępu.
Jon Hanna
„Teoretycznie powinniśmy spodziewać się, że metoda statyczna, która przyjmuje obiekt jako parametr i robi coś z nim, będzie nieco mniej dobra niż odpowiednik jako instancja tego samego obiektu.” - Czy masz na myśli, że jeśli powyższa metoda przykładowa przyjmuje parametr jako obiekt zamiast ciągu, statyczność jest lepsza? na przykład: Mam moja metoda statyczna przyjmuje obiekt jako parametr i serializuje go na ciąg i zwraca ciąg. czy sugerujesz użycie w tym przypadku niestatycznego?
batmaci
1
@batmaci Mam na myśli, że jest duża szansa, obj.DoSomehting(2)że byłaby nieco tańsza niż, DoSomething(obj, 2)ale jak powiedziałem również, różnica jest tak niewielka i tak zależna od drobnych rzeczy, które mogą skończyć się inaczej w ostatecznej kompilacji, że nie warto się o to martwić. Jeśli robisz coś tak kosztownego (w stosunku do tego rodzaju różnic w grze) jak serializowanie czegoś do łańcucha, to jest to szczególnie bezcelowe.
Jon Hanna
W tej znakomitej odpowiedzi brakuje jednej, być może oczywistej, ale ważnej rzeczy: metoda instancji wymaga instancji, a jej tworzenie nie jest tanie. Nawet ustawienie domyślne ctornadal wymaga inicjalizacji wszystkich pól. Jeśli masz już instancję, obowiązuje ta odpowiedź („wszystkie inne rzeczy są równe”). Oczywiście kosztowne cctormoże również spowolnić metody statyczne, ale dzieje się tak tylko przy pierwszym wywołaniu i dotyczy to jednakowo metod instancji. Zobacz także docs.microsoft.com/en-us/previous-versions/dotnet/articles/…
Abel
8

Kiedy zachowanie stanu nie jest problemem (nie są wymagane żadne pola ani właściwości), czy zawsze lepiej jest używać klasy statycznej?

Powiedziałbym tak. Deklarując coś static, deklarujesz zamiar bezpaństwowej egzekucji (nie jest to obowiązkowe, ale zamiar czegoś, czego można by się spodziewać)

Jeśli istnieje znaczna liczba tych klas statycznych (na przykład 100, każda z wieloma metodami statycznymi), czy wpłynie to negatywnie na wydajność wykonywania lub zużycie pamięci w porównaniu z taką samą liczbą klas instancji?

Nie myśl tak, chyba że jesteś pewien, że klasy statyczne są naprawdę bezpretensjonalne, ponieważ jeśli nie, łatwo zepsuć alokacje pamięci i uzyskać wycieki pamięci.

Gdy słowo kluczowe [this] jest używane do wywołania innej metody w tej samej klasie instancji, czy rozpoznawanie instancji nadal występuje?

Nie jestem pewien, co do tego punktu (jest to czysto implementacyjny szczegół CLR), ale myślę, że tak.

Tigran
źródło
Metody statyczne nie mogą być wyśmiewane, jeśli wykonujesz TDD lub nawet testowanie jednostkowe, bardzo zaszkodzi to twoim testom.
trampster
@trampster Why? To tylko kawałek logiki. Możesz łatwo kpić z tego, co mu dajesz? Aby uzyskać prawidłowe zachowanie. I tak wiele metod statycznych będzie prywatnymi elementami logiki w funkcji.
M. Mimpen
@ M.Mimpen tak długo, jak zostawisz to na małe prywatne fragmenty, twoja grzywna, jeśli jest to metoda publiczna i używasz jej z innych zamknięć i musisz zmienić to, co robi w twoim teście, wtedy utkniesz, rzeczy takie jak we / wy pliku lub dostęp do bazy danych lub wywołania sieciowe itp., jeśli umieścisz metodę statyczną, staną się niemożliwe do wykonania, chyba że tak jak mówisz, wstrzykujesz mockowalną zależność jako parametr do metody statycznej
trampster
-2

metody statyczne są szybsze, ale mniej OOP, jeśli będziesz używać wzorców projektowych metoda statyczna prawdopodobnie zły kod, aby lepiej pisać logikę biznesową bez statycznej, typowe funkcje, takie jak czytanie plików, żądanie WebRequest itp. lepiej realizować się jako statyczne ... Twoje pytania nie mają uniwersalnego odpowiedź

palenie_LEGION
źródło
16
Nie podałeś żadnych argumentów do swoich roszczeń.
ymajoros
2
@ fjch1997 2 upvoters wydaje się myśleć inaczej (bez względu na to, co jest warte). Komentowanie głosów przeciw jest zalecaną praktyką na stackexchange
ymajoros