.net core 在網路高併發下提高JSON的處理效率

smark發表於2019-04-11

現有的webapi一般都基於JSON的格式來處理資料,由於JSON是一個文字類的序列化協議所以在效能上自然就相對低效一些。在.net中常用Newtonsoft.Json是最常用的元件,由於提供簡便基於完整的jsonString方法使用起來非常方便;但也正是這原因導致Newtonsoft.Json在效能上一直被說慢,雖然Newtonsoft.Json提供Stream的方式來處理JSON不過想複用writerreader還是需要一些應用技巧。如果需要在網路通訊中應用JSON,那在這裡介紹一下SpanJson這個元件,並通過一些測試來講述如何使用它。

SpanJson介紹

SpanJson是一個效能相對不錯的JSON元件,元件直接提供了byte[]和stream兩種操作方式,而這兩種方式非常適合在構建自有網路通訊上使用。通過這些基礎的位元組和流結構來處理可以相對降低一個大string的開銷。不過這個元件的熱度並不高,完善成度暫還不如Newtonsoft.Json,不過asp.net core 在FrameworkBenchmarks測試上已經引入。可以嘗試一下使用,元件開源地址: https://github.com/Tornhoof/SpanJson

效能測試

元件提供的方法相對比較少,從設計上來說更多是針對通訊方面的支援。基於Stream的序列化可以直接掛載在NetStream上,這樣可以節省資料複製帶來的開銷。不過反序列化不能直接在有混合資料的Stream上進行,這或多或少有些可惜。從issues的解答來看作者也不太願意在混合資料流上進行調整。接下來針對bytesStream使用進行一個效能測試,而Stream則採用一個可複用池的設計

MemoryStream 池的設計

    public class MemoryStreamPool
    {

        private static System.Collections.Concurrent.ConcurrentStack<JsonMemoryStream> mPool = new System.Collections.Concurrent.ConcurrentStack<JsonMemoryStream>();

        public static Stream Pop()
        {
            if (!mPool.TryPop(out JsonMemoryStream result))
            {
                result = new JsonMemoryStream(1024 * 32);
            }
            return result;
        }


        public class JsonMemoryStream : MemoryStream
        {
            public JsonMemoryStream(int size) : base(size) { }

            protected override void Dispose(bool disposing)
            {
                MemoryStreamPool.Push(this);
            }

        }

        private static void Push(JsonMemoryStream stream)
        {
            stream.Position = 0;
            stream.SetLength(0);
            mPool.Push(stream);
        }
    }

測試內容

測試的方式主要針對一個簡單的物件和一個物件列表,然後在不同執行緒下bytesStream pool這兩種方式的效能差別;壓測的執行緒資料分別是1,2,4,8,16,24,32,每次測試執行的總數是100萬次,然後統計出執行需要的時間和併發量。 測試程式碼:

    public class Bytes_JSON : BeetleX.Benchmark.BenchmarkBase
    {
        protected override void OnTest()
        {
            while (Increment())
            {
                var data = SpanJson.JsonSerializer.NonGeneric.Utf8.Serialize(DataHelper.Defalut.Employees[0]);
                var employees = SpanJson.JsonSerializer.Generic.Utf8.Deserialize<Employee>(data);
            }
        }
    }

    public class StreamPool_JSON : BeetleX.Benchmark.BenchmarkBase
    {
        protected override void OnTest()
        {
            RunTest();
        }

        private async void RunTest()
        {
            while (Increment())
            {
                using (Stream stream = MemoryStreamPool.Pop())
                {
                    await SpanJson.JsonSerializer.NonGeneric.Utf8.SerializeAsync(DataHelper.Defalut.Employees[0], stream);
                    stream.Position = 0;
                    var employees = await SpanJson.JsonSerializer.Generic.Utf8.DeserializeAsync<Employee>(stream);
                }
            }
        }
    }


    public class Bytes_JSON_List : BeetleX.Benchmark.BenchmarkBase
    {
        protected override void OnTest()
        {
            while (Increment())
            {
                var data = SpanJson.JsonSerializer.NonGeneric.Utf8.Serialize(DataHelper.Defalut.Employees);
                var employees = SpanJson.JsonSerializer.Generic.Utf8.Deserialize<List<Employee>>(data);
            }
        }
    }

    public class StreamPool_JSON_List : BeetleX.Benchmark.BenchmarkBase
    {
        protected override void OnTest()
        {
            RunTest();
        }

        private async void RunTest()
        {
            while (Increment())
            {
                using (Stream stream = MemoryStreamPool.Pop())
                {
                    await SpanJson.JsonSerializer.NonGeneric.Utf8.SerializeAsync(DataHelper.Defalut.Employees, stream);
                    stream.Position = 0;
                    var employees = await SpanJson.JsonSerializer.Generic.Utf8.DeserializeAsync<List<Employee>>(stream);
                }
            }
        }
    }

測試結果


C:\Users\Administrator\Desktop\json_test>dotnet JsonSample.dll
BeetleX.Benchmark [0.5.4.0] Copyright ? ikende.com 2019
EMail:henryfan@msn.com
Github:https://github.com/ikende
-------------------------------------------------------------------------------
|Name                          | Round| Threads|     Count| Use time(s)|   Sec|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|       1|   1000000|        5.57|179580|
-------------------------------------------------------------------------------
|StreamPool_JSON               |     1|       1|   1000000|        5.44|183898|
-------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|       1|   1000000|       43.01| 23248|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|       1|   1000000|       42.75| 23391|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|       2|   1000000|        2.81|355990|
-------------------------------------------------------------------------------
|StreamPool_JSON               |     1|       2|   1000000|        2.95|338969|
-------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|       2|   1000000|       23.16| 43180|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|       2|   1000000|        22.4| 44650|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|       4|   1000000|        1.51|661246|
-------------------------------------------------------------------------------
|StreamPool_JSON               |     1|       4|   1000000|        1.57|636130|
-------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|       4|   1000000|       13.35| 74915|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|       4|   1000000|       11.97| 83508|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|       8|   1000000|         .83|1199453|
--------------------------------------------------------------------------------
|StreamPool_JSON               |     1|       8|   1000000|         .88|1142495|
--------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|       8|   1000000|        9.24|108228|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|       8|   1000000|        6.75|148132|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|      16|   1000000|         .56|1795910|
--------------------------------------------------------------------------------
|StreamPool_JSON               |     1|      16|   1000000|         .74|1344851|
--------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|      16|   1000000|        7.67|130424|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|      16|   1000000|        4.61|216860|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|      24|   1000000|         .54|1849769|
--------------------------------------------------------------------------------
|StreamPool_JSON               |     1|      24|   1000000|         .73|1361382|
--------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|      24|   1000000|        7.61|131373|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|      24|   1000000|         4.7|212779|
-------------------------------------------------------------------------------
|Bytes_JSON                    |     1|      32|   1000000|         .55|1825484|
--------------------------------------------------------------------------------
|StreamPool_JSON               |     1|      32|   1000000|         .75|1339050|
--------------------------------------------------------------------------------
|Bytes_JSON_List               |     1|      32|   1000000|        8.01|124885|
-------------------------------------------------------------------------------
|StreamPool_JSON_List          |     1|      32|   1000000|        5.21|192038|
-------------------------------------------------------------------------------
Test completed!

總結

從測試結果來看,如果序列化的物件比小,那可以直接基於bytes的方式。雖然會產生新的bytes物件,不過由於物件比較小,引起的分配和回收並沒有物件池操作上的損耗高。不過如果物件相對複雜些的情況下,那物件池的作用就能發揮出來,併發越大其作用越明顯!,當併發執行緒數達到8的時候,效率已經明顯拋開!由於業務上的資料資訊都相對比較複雜些,所以在處理上還是建議通過物件池的方式來完成json序列化處理。

下載測試程式碼

http://ikende.com/Files/JsonSample.zip

相關文章