Performance in. NET Core

Performance in. NET Core

Performance in. NET Core

Ўсім прывітанне! Дадзены артыкул з'яўляецца складанкай Best Practices, якія я і мае калегі ўжываем на працягу доўгага часу пры працы на розных праектах.

Інфармацыя аб машыне, на якой выконваліся вылічэнні:BenchmarkDotNet=v0.11.5, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100
[Host]: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
Core: .NET Core 2.2.7 (CoreCLR 4.6.28008.02, CoreFX 4.6.28008.03), 64bit RyuJIT
[Host]: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT
Core: .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), 64bit RyuJIT

Job=Core Runtime=Core

ToList vs ToArray and Cycles


Дадзеную інфармацыю я планаваў рыхтаваць з выхадам .NET Core 3.0, але мяне апярэдзілі, мне не жадаецца красці чужую славу і капіяваць чужую інфармацыю, таму проста пакажу спасылку на добры артыкул, дзе падрабязна распісана параўнанне.

Ад сябе толькі жадаю прадставіць вам свае замеры і вынікі, я дадаў у іх зваротныя цыклы для аматараў "C++ стылю" напісанні цыклаў.

код:

public class Bench
    {
        private List<int> _list;
        private int[] _array;

        [Params(100000, 10000000)] public int N;

        [GlobalSetup]
        public void Setup()
        {
            const int MIN = 1;
            const int MAX = 10;
            Random random = new Random();
            _list = Enumerable.Repeat(0, N).Select(i => random.Next(MIN, MAX)).ToList();
            _array = _list.ToArray();
        }

        [Benchmark]
        public int ForList()
        {
            int total = 0;
            for (int i = 0; i < _list.Count; i++)
            {
                total += _list[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForListFromEnd()
        {
            int total = 0;t
            for (int i = _list.Count-1; i > 0; i--)
            {
                total += _list[i];
            }

            return total;
        }

        [Benchmark]
        public int ForeachList()
        {
            int total = 0;
            foreach (int i in _list)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForeachArray()
        {
            int total = 0;
            foreach (int i in _array)
            {
                total += i;
            }

            return total;
        }

        [Benchmark]
        public int ForArray()
        {
            int total = 0;
            for (int i = 0; i < _array.Length; i++)
            {
                total += _array[i];
            }

            return total;
        }
        
        [Benchmark]
        public int ForArrayFromEnd()
        {
            int total = 0;
            for (int i = _array.Length-1; i > 0; i--)
            {
                total += _array[i];
            }

            return total;
        }
    }

Хуткасць працы ў. NET Core 2.2 і 3.0 з'яўляюцца амаль ідэнтычнымі. Вось што мне атрымалася атрымаць у .NET Core 3.0:

Performance in. NET Core

Performance in. NET Core

Мы можам прыйсці да высновы аб тым, што цыклічная апрацоўка калекцыі тыпу Array з'яўляецца хутчэйшай, за рахунак сваіх унутраных аптымізацый і відавочнага вылучэння памеру калекцыі. Таксама варта памятаць, што ў калекцыі тыпу List ёсць свае перавагі і вам варта выкарыстоўваць патрэбную калекцыю ў залежнасці ад неабходных вылічэнняў. Нават калі вы пішаце логіку працы з цыкламі не варта забываць, што гэта звычайны loop і ён таксама схільны да магчымай аптымізацыі цыклаў. На habr даўнавата выйшаў артыкул: https://habr.com/ru/post/124910/. Яна ўсё яшчэ актуальная і рэкамендуецца да чытання.

Кідаць

Год таму я працаваў у кампаніі над legacy праектам, у тым праекце было ў рамках нармальнага апрацоўваць валідацыю палёў праз try-catch-throw канструкцыю. Я ўжо тады разумеў, што гэта нездаровая бізнэс-логіка працы праекту, таму па магчымасці імкнуўся не выкарыстоўваць такую ​​канструкцыю. Але давайце разбяромся, чым жа дрэнны падыход апрацоўваць памылкі такой канструкцыяй. Я напісаў невялікі код для таго, каб параўнаць два падыходы і зняў “Бенч” на кожны варыянт.

код:

        public bool ContainsHash()
        {
            bool result = false;
            foreach (var file in _files)
            {
                var extension = Path.GetExtension(file);
                if (_hash.Contains(extension))
                    result = true;
            }

            return result;
        }

        public bool ContainsHashTryCatch()
        {
            bool result = false;
            try
            {
                foreach (var file in _files)
                {
                    var extension = Path.GetExtension(file);
                    if (_hash.Contains(extension))
                        result = true;
                }
                
                if(!result) 
                    throw new Exception("false");
            }
            catch (Exception e)
            {
                result = false;
            }

            return result;
        }

Вынікі ў .NET Core 3.0 і Core 2.2 маюць аналагічны вынік (.NET Core 3.0):

Performance in. NET Core

Performance in. NET Core

Try catch ускладняе разуменне кода і павялічвае час выканання вашай праграмы. Але калі вам неабходна дадзеная канструкцыя, не варта ўстаўляць тыя радкі кода, ад якіх не чакаецца апрацоўка памылак - гэта аблегчыць разуменне кода. Насамрэч, нагружае сістэму не гэтулькі апрацоўка выключэнняў, колькі выкідванне саміх памылак праз канструкцыю throw new Exception.

Выкідванне выключэнняў працуе павольней якога-небудзь класа, які збярэ памылку ў патрэбным фармаце. Калі вы апрацоўваеце форму або якія-небудзь дадзеныя і відавочна ведаеце якая памылка павінна быць, дык чаму б не апрацаваць іх?

Не варта пісаць канструкцыю throw new Exception() калі гэтая сітуацыя не з'яўляецца выключнай. Апрацоўка і выкідванне выключэння каштуе вельмі дорага!!!

ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant

За свой 5 гадовы досвед працы на платформе. NET сустракаў нямала праектаў, якія выкарыстоўвалі супастаўленне радкоў. Таксама бачыў наступную карціну: было адно Enterprise рашэнне з мноствам праектаў, кожны з якіх выконваў параўнанне радкоў па-рознаму. Але што варта выкарыстоўваць і як гэта уніфікаваць? У кнізе CLR via C# Рыхтара я адымаў інфармацыю аб тым, што метад ToUpperInvariant() працуе хутчэй ToLowerInvariant().

Выразка з кнігі:

Performance in. NET Core

Вядома ж я не паверыў і вырашыў правесці некаторыя тэсты тады яшчэ на. NET Framework і вынік мяне шакаваў – больш за 15% прыросту прадукцыйнасці. Далей па прыходзе на працу наступнай раніцай я паказаў дадзеныя замеры свайму начальству і падаў ім доступ да зыходнікаў. Пасля гэтага 2 з 14 праектаў былі змененыя пад новыя замеры, а пры ўліку таго што гэтыя два праекты існавалі каб апрацоўваць велізарныя excel табліцы, вынік быў больш за значным для прадукта.

Таксама ўяўляю вам замеры для розных версій. NET Core, каб кожны з вас мог зрабіць выбар у бок найболей аптымальнага рашэння. А я толькі хачу дапоўніць, што ў кампаніі, дзе я працую, мы выкарыстоўваем ToUpper() для параўнання радкоў.

код:

public const string defaultString =  "VXTDuob5YhummuDq1PPXOHE4PbrRjYfBjcHdFs8UcKSAHOCGievbUItWhU3ovCmRALgdZUG1CB0sQ4iMj8Z1ZfkML2owvfkOKxBCoFUAN4VLd4I8ietmlsS5PtdQEn6zEgy1uCVZXiXuubd0xM5ONVZBqDu6nOVq1GQloEjeRN8jXrj0MVUexB9aIECs7caKGddpuut3";

        [Benchmark]
        public bool ToLower()
        {
            return defaultString.ToLower() == defaultString.ToLower();
        }

        [Benchmark]
        public bool ToLowerInvariant()
        {
            return defaultString.ToLowerInvariant() == defaultString.ToLowerInvariant();
        }

        [Benchmark]
        public bool ToUpper()
        {
            return defaultString.ToUpper() == defaultString.ToUpper();
        }

        [Benchmark]
        public bool ToUpperInvariant()
        {
            return defaultString.ToUpperInvariant() == defaultString.ToUpperInvariant();
        }

Performance in. NET Core

Performance in. NET Core

У .NET Core 3.0 прырост для кожнага з гэтых метадаў ~x2 і балансуе рэалізацыі паміж сабой.

Performance in. NET Core

Performance in. NET Core

Tier Compilation

У сваім мінулым артыкуле я апісаў гэты функцыянал коратка, хацелася б выправіць і дапоўніць свае словы. Шматузроўневая кампіляцыя паскарае час запуску вашага рашэння, але вы ахвяруеце тым, што часткі вашага кода будуць кампілявацца ў больш аптымізаваную версію ў фоне, што можа прывесці да невялікіх накладных выдаткаў. З прыходам NET Core 3.0 зменшыўся час зборкі праектаў з уключаным tier compilation і пафіксілі багі злучаныя з гэтай тэхналогій. Раней гэтая тэхналогія прыводзіла да памылак першых запытаў у ASP.NET Core і да падвісання пры першай зборцы ў рэжыме шматузроўневай кампіляцыі. На дадзены момант у .NET Core 3.0 яна ўключана па змаўчанні, але вы можаце яе адключыць па жаданні. Калі вы знаходзіцеся на пасадзе team-lead, senior, middle ці вы кіраўнік аддзела то, павінны разумець што хуткая распрацоўка праекта павялічвае каштоўнасць каманды і дадзеная тэхналогія дазволіць вам эканоміць час як распрацоўшчыкаў, так і сам час працы праекта.

.NET level up

Падвышайце версію вашага .NET Framework / .NET Core. Часцяком, кожная новая версія дае дадатковы прырост да прадукцыйнасці і дадае новыя фічы.

Але якія менавіта перавагі? Давайце разгледзім некаторыя з іх:

  • У. NET Core 3.0 з'явілася R2R выявы, якія дазволяць зменшыць час запуску. NET Core прыкладанняў.
  • З версіі 2.2 з'явілася Tier Compilation, дзякуючы якой праграмісты будуць марнаваць менш чакай на запуск праекту.
  • Падтрымка новых стандартаў. NET Standard.
  • Падтрымка новай версіі мовы праграмавання.
  • Аптымізацыя, з кожнай новай версіяй паляпшаецца аптымізацыя базавымі бібліятэкамі Collection/Struct/Stream/String/Regex і шмат чаго яшчэ. Калі вы пераходзіце з .NET Framework на .NET Core, вы атрымаеце вялікі прырост да прадукцыйнасці са скрынкі. Для прыкладу прымацоўваю спасылку на частку аптымізацый якія былі дададзены ў .NET Core 3.0: https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-core-3-0/

Performance in. NET Core

Заключэнне

Пры напісанне кода варта надаваць увагу розным аспектам вашага праекта і выкарыстоўваць функцыі вашай мовы праграмавання і платформы для дасягнення найлепшага выніку. Буду рады калі вы падзяліцеся сваімі ведамі звязанымі з аптымізацыяй у .NET.

Спасылка на github

Крыніца: habr.com

Дадаць каментар