【问题标题】:Why is .NET OData Serializer so slow为什么.NET OData 序列化器这么慢
【发布时间】:2023-03-13 02:00:02
【问题描述】:

我有一个 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);
}

【问题讨论】:

  • 输出的json结果是不是一模一样?
  • 您是否尝试过运行分析器来查看实际花费的时间?
  • 完全猜测,但它可能是 OData 想要使用的 IEnumerable,因此更改为 List 会有所帮助。
  • 如果你想找出它慢的原因,试着分析它来看看时间花在了哪里。显然它不是这里提供的代码 sn-ps 中的东西,因为它们几乎相同。

标签: .net json asp.net-core asp.net-web-api odata


【解决方案1】:

嗯,答案不是序列化程序。 添加 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 以不损害性能,但这取决于您用于托管它的基础设施。 希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-24
    • 2010-11-01
    • 2022-07-21
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    • 1970-01-01
    • 2011-01-18
    相关资源
    最近更新 更多