Umarł .NET Framework, niech żyje .NET Core (oraz .NET 5) i jego wydajność
09.05.2020 | aktual.: 18.05.2020 19:50
Czas się pożegnać
Pierwsze wydanie .NET Frameworka 1.0 to początek roku 2002. W tym momencie najnowsza wersja wersja 4.8 będzie ostatnim głównym wydaniem klasycznego frameworku .NET. Oczywiście nadal będzie on dostarczany z Windowsem, ale to nie ma co się oszukiwać jest to już koniec. Microsoft nie będzie już dodawał nowych ficzerów czy optymalizacji do .NET Frameworku. Pozostanie czysty support i nic więcej, zatem możliwe będą aktualizacje bezpieczeństwa i zapewne nic więcej..NET Framework to także biblioteki i składowe, które zostaną pożegnane razem z odchodzącą wersją. Zatem nie usłyszymy już, albo będziemy słyszeć już coraz mniej, o WCF (trochę szkoda, aczkolwiek warto nadmienić, że zostaje wersja kliencka, ale nie serwerowa) czy WebFormsach (nareszcie! składam wyrazy współczucia każdemu, kto jeszcze w tej technologii pisze).
.NET Core (3.x) i .NET 5 - olbrzymi skok wydajnościowy
Wydany we wrześniu .NET Core 3.0 to nie tylko przeniesienie kolejnych rzeczy z .NET Frameworka do Corea. To również, a może i przede wszystkim, bardzo duży skok wydajnościowy.
.NET Core 3.0 to nowe algorytmy do sortowania czy operacji na kolekcjach. To również nowe typy struktur: Span<T> oraz Memory<T> to dzięki nim operacje na stringach czy plikach XML lub JSON są 2x lub 3x szybsze w porównaniu do .NET Frameworka. Różnica jest bardzo duża.
Pozbyto się również starego kodu i zaktualizowano wiele bibliotek, ale także dodano kilka sztuczek, które podniosły wydajność w wielu newralgicznych miejscach. Autoryzacja online to olbrzymi ilość szyfrowania, haszowania itd. Z tego też powodu w .NET Core operacje kryptograficzne wykonywanie są w... natywnym kodzie. Tak nie jest uruchamiany kod napisany w .NET, ale brane są natywne biblioteki napisane w C++. Tak oto pod Windowsem ujrzymy CNG Windows, zaś na Linuxie .NET Core do kryptografii zaprzęgnie OpenSSL.
Co więcej, dopiero .NET Core wykorzystuje sprzętowe możliwości Ryzenów w kwestii obliczeń kryptograficznych. Więcej w zewnętrznym wpisie: Will AMD’s Ryzen finally bring SHA extensions to Intel’s CPUs? Intel zostaje daleeeeko w tyle.
Nie tak dawno Microsoft wydał już 3. wersję preview kolejnego środowiska runtime - .NET 5. Nie jest to rewolucja, jaką był .NET Core w porównaniu do .NET Frameworka, ale kolejny etap w ewolucji .NET. Stawia on na usprawnienia .NET Core i ujednolicenie środowisk uruchomieniowych.
Jeśli chodzi o wydajność .NET 5, to jest zbliżona do wydajności .NET Core 3.x (na ten moment). W wersji preview 3 wydajność została "jedynie" bardzo podkręcona w temacie wyrażeń regularnych. Tutaj obliczenia mogą być nawet 60824x szybsze!
Nie chcąc być gołosłownym przedstawię porównanie działania tego samego kodu na .NET Frameworku i .NET Core. Do tego celu użyte były dwa procesory: Intel Core i7‑4702MQ oraz AMD Ryzen R7 3700x (w porównaniu wydajności przy obliczeniu SHA256). Wyniki zmierzone przy użyciu BenchmarkDotNet (określają średni czas), kod bazuje na przykładach z blogu MS.
(Z racji tego, że BenchmarkDotNet opisuje .NET 5 jako .NET Core 5.0, tak też jest on oznaczony na wykresach i szczegółowych czasach)
Parsowanie Enuma:
public DayOfWeek EnumParse() => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), "Thursday");
EnumParse .NET 4.8 199.1 ns
EnumParse .NET Core 3.1 124.4 ns
EnumParse .NET Core 5.0 125.7 ns
Operacje Linq:
//IEnumerable<int> _tenMillionToZero = Enumerable.Range(0, 10_000_000).Reverse(); public int LinqOrderBySkipFirst() => _tenMillionToZero.OrderBy(i => i).Skip(4).First();
LinqOrderBySkipFirst .NET 4.8 1,818,312,740.0 ns
LinqOrderBySkipFirst .NET Core 3.1 228,086,635.6 ns
LinqOrderBySkipFirst .NET Core 5.0 222,083,673.8 ns
Obliczanie SHA256:
//SHA256 _sha256 = SHA256.Create(); public byte[] Sha256() => _sha256.ComputeHash(_raw);
Intel:
Sha256 .NET 4.8 1,028,103,786.7 ns
Sha256 .NET Core 3.1 500,472,964.3 ns
Sha256 .NET Core 5.0 504,334,426.7 ns
AMD:
Sha256 .NET 4.8 649,423,446.2 ns
Sha256 .NET Core 3.1 50,023,144.0 ns
Sha256 .NET Core 5.0 49,985,630.0 ns
Intel:
AMD:
Obliczenia na stringu:
//static string _s = "abcdefghijklmnopqrstuvwxyz"; public bool StringStartsWith() { var data = false; for (int i = 0; i < 100_000_000; i++) { data = _s.StartsWith("abcdefghijklmnopqrstuvwxy-", StringComparison.Ordinal); } return data; }
StringStartsWith .NET 4.8 1,918,666,640.0 ns
StringStartsWith .NET Core 3.1 880,179,750.0 ns
StringStartsWith .NET Core 5.0 858,676,357.1 ns
Deserializacja:
//byte[] _raw = new byte[100 * 1024 * 1024]; public object Deserialize() { var books = new List<Book>(); for (int i = 0; i < 1_00000; i++) { string id = i.ToString(); books.Add(new Book { Name = id, Id = id }); } var formatter = new BinaryFormatter(); var mem = new MemoryStream(); formatter.Serialize(mem, books); mem.Position = 0; return formatter.Deserialize(mem); }
Deserialize .NET 4.8 980,524,740.0 ns
Deserialize .NET Core 3.1 421,885,039.1 ns
Deserialize .NET Core 5.0 415,699,686.8 ns
Regex:
Email:
_regexEmail = new Regex(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", RegexOptions.Compiled); _regexEmail.IsMatch(_commonInput);
Regex_Email .NET 4.8 2,299,723.7 ns
Regex_Email .NET Core 3.1 1,845,451.4 ns
Regex_Email .NET Core 5.0 961,795.4 ns
StrongPassword:
_regexStrongPassword = new Regex(@"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$", RegexOptions.Compiled); _regexStrongPassword.IsMatch(_commonInput);
Regex_StrongPassword .NET 4.8 1,887.3 ns
Regex_StrongPassword .NET Core 3.1 1,726.2 ns
Regex_StrongPassword .NET Core 5.0 427.4 ns
SpanSearching:
_regexSpanSearching = new Regex("([ab]cd|ef[g-i])jklm", RegexOptions.Compiled); _regexSpanSearching.IsMatch(_commonInput);
Regex_SpanSearching .NET 4.8 339,303.0 ns
Regex_SpanSearching .NET Core 3.1 295,767.1 ns
Regex_SpanSearching .NET Core 5.0 22,660.0 ns
BackTracking:
_regexBackTracking = new Regex("a*a*a*a*a*a*a*b", RegexOptions.Compiled);; _regexBackTracking.IsMatch("aaaaaaaaaaaaaaaaaaaaa");
Regex_BackTracking .NET 4.8 43,722,439.1 ns
Regex_BackTracking .NET Core 3.1 34,763,742.9 ns
Regex_BackTracking .NET Core 5.0 578.1 ns
Przetestuj sam!
Nic nie stoi na przeszkodzie, aby samemu sprawdzić jak bardzo .NET Core i .NET 5 są szybkie na lokalnej maszynie. Z tego też powodu już jakiś czas temu stworzyłem projekt na GitHubie: https://github.com/djfoxer/DotNetFrameworkVsCore Są tu dostępne kody źródłowe i gotowy plik exe, który porówna wymienione trzy frameworki na Twoim komputerze. Dodatkowo zamieszczam kilka wyników z różnych procesorów od różnych producentów. Warto sprawdzić, na pewno będziesz mile zaskoczony jak faktycznie olbrzymi skok wydajnościowy został uczyniony w .NET Core 3x i .NET 5.
DotNetFrameworkVsCore - https://github.com/djfoxer/DotNetFrameworkVsCore