歡迎大家持續(xù)關注葡萄城控件技術團隊博客,更多更好的原創(chuàng)文章盡在這里~~

.NET Core(開放源代碼,跨平臺,x-copy可部署等)有許多令人興奮的方面,其中最值得稱贊的就是其性能了。

感謝所有社區(qū)開發(fā)人員對.NET Core做出的貢獻,其中的許多改進也將在接下來的幾個版本中引入.NET Framework。

本文主要介紹.NET Core中的一些性能改進,特別是.NET Core 2.0中的,重點介紹各個核心庫的一些示例。

 

集合

集合是任何應用程序的基石,同時.NET庫中也有大量集合。.NET庫中的一些改進是為了消除開銷,例如簡化操作以便更好的實現(xiàn)內(nèi)聯(lián),減少指令數(shù)量等。例如,下面的這個使用Q<T>的例子:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Generic;public class Test
{    public static void Main()
    {        while (true)
        {            var q = new Queue<int>();            var sw = Stopwatch.StartNew();            for (int i = 0; i < 100_000_000; i++)
            {
                q.Enqueue(i);
                q.Dequeue();
            }
            Console.WriteLine(sw.Elapsed);
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

PR dotnet/corefx #2515移除了這些操作中相對復雜的模數(shù)運算,在個人計算機,以上代碼在.NET 4.7上產(chǎn)生如下輸出:

00:00:00.9392595 00:00:00.9390453 00:00:00.9455784 00:00:00.9508294 00:00:01.0107745

而使用.NET Core 2.0則會產(chǎn)生如下輸出:

00:00:00.5514887 00:00:00.5662477 00:00:00.5627481 00:00:00.5685286 00:00:00.5262378

由于這是掛鐘時間所節(jié)省的,較小的值計算的更快,這也表明吞吐量增加了約2倍!

在其他情況下,通過更改操作算法的復雜性,可以更快地進行操作。編寫軟件時,最初編寫的一個簡單實現(xiàn),雖然是正確的,但是這樣實現(xiàn)往往不能表現(xiàn)出最佳的性能,直到特定的場景出現(xiàn)時,才考慮如何提高性能。例如,SortedSet <T>的ctor最初以相對簡單的方式編寫,由于使用O(N ^ 2)算法來處理重復項,因此不能很好地處理復雜性。該算法在PRnetnet / corefx#1955中的.NET Core中得到修復。以下簡短的程序說明了修復的區(qū)別:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Generic;using System.Linq;public class Test
{    public static void Main()
    {        var sw = Stopwatch.StartNew();        var ss = new SortedSet<int>(Enumerable.Repeat(42, 400_000));
        Console.WriteLine(sw.Elapsed);
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在個人電腦的.NET Framework上,這段代碼需要大約7.7秒執(zhí)行完成。在.NET Core 2.0上,減少到大約0.013s(改進改變了算法的復雜性,集合越大,節(jié)省的時間越多)。

或者在SortedSet <T>上考慮這個例子:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

public class Test
{    static int s_result;    public static void Main()
    {        while (true)
        {            var s = new SortedSet<int>();            for (int n = 0; n < 100_000; n++)
            {
                s.Add(n);
            }            var sw = Stopwatch.StartNew();            for (int i = 0; i < 10_000_000; i++)
            {
                s_result = s.Min;
            }
            Console.WriteLine(sw.Elapsed);
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

.NET 4.7中MinMax的實現(xiàn)遍布SortedSet <T>的整個樹,但是只需要找到最小或最大值即可,因為實現(xiàn)可以只遍歷相關的節(jié)點。PR dotnet / corefx#11968修復了.NET Core實現(xiàn)。在.NET 4.7中,此示例生成如下結(jié)果:

00:00:01.142724600:00:01.1295220 00:00:01.1350696 00:00:01.1502784 00:00:01.1677880

而在.NET Core 2.0中,我們得到如下結(jié)果:

00:00:00.0861391 00:00:00.0861183 00:00:00.0866616 00:00:00.0848434 00:00:00.0860198

顯示出相當大的時間下降和吞吐量的增加。

即使像List <T>這樣的主工作核心也有改進的空間??紤]下面的例子:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Generic;public class Test
{    public static void Main()
    {        while (true)
        {            var l = new List<int>();            var sw = Stopwatch.StartNew();            for (int i = 0; i < 100_000_000; i++)
            {
                l.Add(i);
                l.RemoveAt(0);
            }
            Console.WriteLine(sw.Elapsed);
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在.NET 4.7中,會得到的結(jié)果如下:

00:00:00.4434135 00:00:00.4394329 00:00:00.4496867 00:00:00.4496383 00:00:00.4515505

和.NET Core 2.0,得到:

00:00:00.3213094 00:00:00.3211772 00:00:00.3179631 00:00:00.3198449 00:00:00.3164009

可以肯定的是,在0.3秒內(nèi)可以實現(xiàn)1億次這樣的添加并從列表中刪除的操作,這表明操作開始并不慢。但是,通過執(zhí)行一個應用程序,列表通常會添加到很多,同時也節(jié)省了總時間消耗。

這些類型的集合改進擴展不僅僅是System.Collections.Generic命名空間; System.Collections.Concurrent也有很多改進。事實上,.NET Core 2.0上的ConcurrentQueue <T>ConcurrentBag <T>完全重寫了。下面看看一個基本的例子,使用ConcurrentQueue <T>但沒有任何并發(fā),例子中使用ConcurrentQueue <T>代替了Queue<T>

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Concurrent;public class Test
{    public static void Main()
    {        while (true)
        {            var q = new ConcurrentQueue<int>();            var sw = Stopwatch.StartNew();            for (int i = 0; i < 100_000_000; i++)
            {
                q.Enqueue(i);
                q.TryDequeue(out int _);
            }
            Console.WriteLine(sw.Elapsed);
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在個人電腦上,.NET 4.7產(chǎn)生的輸出如下:

00:00:02.648517400:00:02.6144919 00:00:02.6699958 00:00:02.6441047 00:00:02.6255135

顯然,.NET 4.7上的ConcurrentQueue <T>示例比.NET 4.7中的Queue <T>版本慢,因為ConcurrentQueue <T>需要采用同步來確保是否安全使用。但是,更有趣的比較是當在.NET Core 2.0上運行相同的代碼時會發(fā)生什么:

00:00:01.7700190 00:00:01.8324078 00:00:01.7552966 00:00:01.7518632 00:00:01.7560811

這表明當將.NET Core 2.0切換到30%時,ConcurrentQueue <T>的吞吐量沒有任何并發(fā)性提高。但是實施中的變化提高了序列化的吞吐量,甚至更多地減少了使用隊列的生產(chǎn)和消耗之間的同步,這可能對吞吐量有更明顯的影響。請考慮以下代碼:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Concurrent;using System.Threading.Tasks;public class Test
{    public static void Main()
    {        while (true)
        {            const int Items = 100_000_000;            var q = new ConcurrentQueue<int>();            var sw = Stopwatch.StartNew();

            Task consumer = Task.Run(() =>
            {                int total = 0;                while (total < Items) if (q.TryDequeue(out int _)) total++;
            });            for (int i = 0; i < Items; i++) q.Enqueue(i);
            consumer.Wait();

            Console.WriteLine(sw.Elapsed);
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在.NET 4.7中,個人計算機輸出如下結(jié)果:

00:00:06.136604400:00:05.7169339 00:00:06.3870274 00:00:05.5487718 00:00:06.6069291

而使用.NET Core 2.0,會得到以下結(jié)果:

00:00:01.2052460 00:00:01.5269184 00:00:01.4638793 00:00:01.4963922 00:00:01.4927520

這是一個3.5倍的吞吐量的增長。不但CPU效率提高了, 而且內(nèi)存分配也大大減少。下面的例子主要觀察GC集合的數(shù)量,而不是掛鐘時間:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Concurrent;public class Test
{    public static void Main()
    {        while (true)
        {            var q = new ConcurrentQueue<int>();            int gen0 = GC.CollectionCount(0), gen1 = GC.CollectionCount(1), gen2 = GC.CollectionCount(2);            for (int i = 0; i < 100_000_000; i++)
            {
                q.Enqueue(i);
                q.TryDequeue(out int _);
            }
            Console.WriteLine($"Gen0={GC.CollectionCount(0) - gen0} Gen1={GC.CollectionCount(1) - gen1} Gen2={GC.CollectionCount(2) - gen2}");
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在.NET 4.7中,得到以下輸出:

Gen0 = 162 Gen1 = 80 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0 Gen0 = 162 Gen1 = 81 Gen2 = 0

而使用.NET Core 2.0,會得到如下輸出:

Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0 Gen0 = 0 Gen1 = 0 Gen2 = 0

.NET 4.7中的實現(xiàn)使用了固定大小的數(shù)組鏈表,一旦固定數(shù)量的元素被添加到每個數(shù)組中,就會被丟棄, 這有助于簡化實現(xiàn),但也會導致生成大量垃圾。在.NET Core 2.0中,新的實現(xiàn)仍然使用鏈接在一起的鏈接列表,但是隨著新的片段的添加,這些片段的大小會增加,更重要的是使用循環(huán)緩沖區(qū),只有在前一個片段完全結(jié)束時,新片段才會增加。這種分配的減少可能對應用程序的整體性能產(chǎn)生相當大的影響。

ConcurrentBag <T>也有類似改進。ConcurrentBag <T>維護thread-local work-stealing隊列,使得添加到的每個線程都有自己的隊列。在.NET 4.7中,這些隊列被實現(xiàn)為每個元素占據(jù)一個節(jié)點的鏈接列表,這意味著對該包的任何添加都會導致分配。在.NET Core 2.0中,這些隊列是數(shù)組,這意味著除了增加陣列所涉及的均攤成本之外,增加的還是無需配置的。以下可以看出:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Diagnostics;using System.Collections.Concurrent;public class Test
{    public static void Main()
    {        while (true)
        {            var q = new ConcurrentBag<int>() { 1, 2 };            var sw = new Stopwatch();            int gen0 = GC.CollectionCount(0), gen1 = GC.CollectionCount(1), gen2 = GC.CollectionCount(2);
            sw.Start();            for (int i = 0; i < 100_000_000; i++)
            {
                q.Add(i);
                q.TryTake(out int _);
            }

            sw.Stop();
            Console.WriteLine($"Elapsed={sw.Elapsed} Gen0={GC.CollectionCount(0) - gen0} Gen1={GC.CollectionCount(1) - gen1} Gen2={GC.CollectionCount(2) - gen2}");
        }
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在.NET 4.7中,個人計算機上產(chǎn)生以下輸出:

Elapsed=00:00:06.5672723 Gen0=953 Gen1=0 Gen2=0Elapsed=00:00:06.4829793 Gen0=954 Gen1=1 Gen2=0Elapsed=00:00:06.9008532 Gen0=954 Gen1=0 Gen2=0Elapsed=00:00:06.6485667 Gen0=953 Gen1=1 Gen2=0Elapsed=00:00:06.4671746 Gen0=954 Gen1=1 Gen2=0

而使用.NET Core 2.0,會得到:

Elapsed=00:00:04.3377355 Gen0=0 Gen1=0 Gen2=0Elapsed=00:00:04.2892791 Gen0=0 Gen1=0 Gen2=0Elapsed=00:00:04.3101593 Gen0=0 Gen1=0 Gen2=0Elapsed=00:00:04.2652497 Gen0=0 Gen1=0 Gen2=0Elapsed=00:00:04.2808077 Gen0=0 Gen1=0 Gen2=0

吞吐量提高了約30%,并且分配和完成的垃圾收集量減少了。

 

LINQ

在應用程序代碼中,集合通常與語言集成查詢(LINQ)緊密相連,該查詢已經(jīng)有了更多的改進。LINQ中的許多運算符已經(jīng)完全重寫為.NET Core,以便減少分配的數(shù)量和大小,降低算法復雜度,并且消除不必要的工作。

例如,Enumerable.Concat方法用于創(chuàng)建一個單一的IEnumerable <T>,它首先產(chǎn)生first域可枚舉的所有元素,然后再生成second域所有的元素。它在.NET 4.7中的實現(xiàn)是簡單易懂的,下面的代碼正好反映了這種行為表述:

static IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second) {    foreach (TSource element in first) yield return element;    foreach (TSource element in second) yield return element;
}

當兩個序列是簡單的枚舉,如C#中的迭代器生成的,這種過程會執(zhí)行的很好。但是如果應用程序代碼具有如下代碼呢?

first.Concat(second.Concat(third.Concat(fourth)));

每次我們從迭代器中退出時,則會返回到枚舉器的MoveNext方法。這意味著如果你從另一個迭代器中枚舉產(chǎn)生一個元素,則會返回兩個MoveNext方法,并移動到下一個需要調(diào)用這兩個MoveNext方法的元素。你調(diào)用的枚舉器越多,操作所需的時間越長,特別是這些操作中的每一個都涉及多個接口調(diào)用(MoveNextCurrent)。這意味著連接多個枚舉會以指數(shù)方式增長,而不是呈線性增長。PR dotnet / corefx#6131修正了這個問題,在下面的例子中,區(qū)別是顯而易見的:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Test
{    public static void Main()
    {
        IEnumerable<int> zeroToTen = Enumerable.Range(0, 10);
        IEnumerable<int> result = zeroToTen;        for (int i = 0; i < 10_000; i++)
        {
            result = result.Concat(zeroToTen);
        }        var sw = Stopwatch.StartNew();        foreach (int i in result) { }
        Console.WriteLine(sw.Elapsed);
    }
}

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

在個人計算機上,.NET 4.7需要大約4.12秒。但在.NET Core 2.0中,這只需要約0.14秒,提高了30倍。

通過消除多個運算器同時使用時的消耗,運算器也得到了大大的提升。例如下面的例子:

iOS培訓,Swift培訓,蘋果開發(fā)培訓,移動開發(fā)培訓

using System;using System.Collections.Generic;using System.Diagnostics;using System.Linq;public class Te

轉(zhuǎn)載請注明出自:葡萄城控件

http://www.cnblogs.com/powertoolsteam/p/net_core_2.html