为什么.NET OData序列化器如此缓慢

10

我有一个OData端点(在.NET Core和.NET 4.7.1上都进行了测试),它公开了2500个在内存中构建的对象。获取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 结果完全相同吗? - Simon Mourier
2
你尝试过运行分析器来查看实际花费时间的位置吗? - John M
完全猜测,但可能是IEnumerable是OData想要处理的内容,因此更改为List会有所帮助。 - Thomas Koelle
如果你想找出为什么它很慢,尝试对其进行分析以查看时间花费在哪里。 显然这不是提供的代码片段中的内容,因为它们几乎相同。 - Tim Johnson
1个回答

3
好的,答案不是序列化程序。 当您添加EnableQuery时,默认情况下允许OData使用Select、Count、Skip、Top、Expand等不同的方法。但更重要的是,EnableQuery是一个ActionFilterAttribute,这意味着: ASP.NET Core中的过滤器允许在请求处理管道的特定阶段之前或之后运行代码。 请查看此处以获取有关ActionFilters的更多信息。 也就是说,EnableQuery覆盖了两种方法(Before/After)。
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,以避免影响性能,但这取决于你用来托管它的基础架构。 希望这能帮到你。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接