Dot-Net

為什麼.NET OData 序列化器這麼慢

  • August 18, 2021

我有一個 OData 端點(使用 .NET Core 和 .NET 4.7.1 進行了測試),它公開了 2,500 個內置在記憶體中的對象。Get OData 呼叫需要 30-40 秒。返回原始 JSON 的等效 ASP.NET WEB API 呼叫需要 1 秒。感覺好像 OData 框架不如 Json.NET 高效。關於如何提高性能的任何建議?

真的很慢。

   [EnableQuery(EnsureStableOrdering = false)]
   public ActionResult<IEnumerable<Person>> Get()
   {
       var list = new List<Person>();
       for (var i = 0; i < 2500; i++)
       {
           list.Add(new Person());
       }

       return list;
   }

真的很快。

public IHttpActionResult Get()
{
   var list = new List<Person>();
   for (var i = 0; i < 2500; i++)
   {
       list.Add(new Person());
   }

   var json = JsonConvert.SerializeObject(list);
   return Ok(json);
}

好吧,答案不是序列化程序。添加 EnableQuery 時,預設情況下允許 OData 使用 Select、Count、Skip、Top、Expand 的不同方法。但最重要的是 EnableQuery 是一個 ActionFilterAttribute 這意味著:

ASP.NET Core 中的過濾器允許在請求處理管道中的特定階段之前或之後執行程式碼。

在這裡查看有關ActionFilters的更多資訊

話雖如此,EnableQuery 覆蓋了兩種方法(之前/之後):

public override void OnActionExecuting(ActionExecutingContext actionExecutingContext)

public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)

第一個是創建和驗證查詢選項的地方。這涉及到大量的反思工作。第二個檢查結果是否已設置並成功,例如,如果您返回一個 IQueryable,這裡是它被執行的地方。查詢在此級別實現。它首先嘗試從返回的響應消息中檢索 IQueryable。然後,它根據“EnableQueryAttribute”上的驗證設置驗證來自 uri 的查詢。它最終適當地應用查詢,並將其重置迴響應消息。

如您所見,所有這些額外的邏輯都比僅將結果轉換為 JSON 輸出(這是最後一步)更複雜。

我舉了你的例子:

[ApiController]
   [Route("api/test")]
   public class WeatherForecastController : ControllerBase
   {
       [Route("/get1")]
       [HttpGet]
       [EnableQuery(EnsureStableOrdering = false)]
       public ActionResult<IEnumerable<Person>> Get1()
       {
           var list = new List<Person>();
           for (var i = 0; i < 2500; i++)
           {
               list.Add(new Person());
           }

           return list;
       }

       [Route("/get2")]
       [HttpGet]
       public IActionResult Get2()
       {
           var list = new List<Person>();
           for (var i = 0; i < 2500; i++)
           {
               list.Add(new Person());
           }

           var json = JsonConvert.SerializeObject(list);
           return Ok(json);
       }

       [Route("/get3")]
       [HttpGet]
       public IActionResult Get3()
       {
           var list = new List<Person>();
           for (var i = 0; i < 2500; i++)
           {
               list.Add(new Person());
           }

           return Ok(list);
       }

我用 20 個不同的執行緒對每個端點執行相同的請求進行性能測試:get1、get2、get3結果如下:

每個的平均毫秒數是:433,355,337 ,我認為這還不錯,第一個是 Odata,與最後一個相比,這個負載測試只有 96 毫秒的差異。不知道為什麼您的範例在這裡需要 30-40 秒,因為我使用相同的程式碼和 Jmeter 進行負載測試,我得到的最大時間是 900 毫秒的一個請求,只有第一個請求,因為 apppool 是從第一個請求開始,以防它處於睡眠狀態

恕我直言,如果您想實現所有可以使用 odata 執行的操作(整形、排序、分頁、過濾器),您還需要大量使用反射,至少對於整形和過濾器,不要說所有也可用的二元運算符。創建你自己的語法,對我來說不是一個選項,你需要創建一個詞法分析器和一個解析器來適應你自己的語法。所以我認為你得到的好處是巨大的,除非你的 API 不需要這些複雜的操作符。要考慮的一件事是如何擴展您的 api 以不損害性能,但這取決於您用於託管它的基礎設施。希望這可以幫助。

引用自:https://stackoverflow.com/questions/60004222