为什么 .NET OData Serializer 这么慢
Why is .NET OData Serializer so slow
我有一个 OData 端点(使用 .NET Core 和 .NET 4.7.1 进行了测试),它公开了内存中内置的 2,500 个对象。 Get OData 调用需要 30-40 秒。等效的 ASP.NET WEB API 调用 return raw JSON 需要 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,这意味着:
Filters in ASP.NET Core allow code to be run before or after specific stages in the request processing pipeline.
在此处查看有关 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 个不同的线程对这些 enpoints 中的每一个执行相同的请求:get1, get2, get3[=43=] 结果如下:
每一个的平均毫秒数是:433,355,337
我认为这还不错,第一个是 Odata,与最后一个相比,此负载测试仅相差 96 毫秒。
不确定为什么您的示例在这里花费 30-40 秒,因为我使用了相同的代码和 Jmeter 进行负载测试,并且我得到的最长时间是一个请求的 900 毫秒,只有第一个请求,这很有意义,因为应用程序池是从第一个请求开始,以防它处于睡眠状态
恕我直言,如果你想实现你可以用 odata 做的所有操作(shaping、排序、分页、过滤器)你也需要做很多反射工作,至少对于 shaping 和过滤器,没有说明所有可用的二元运算符。
创建你自己的语法,对我来说不是一个选项,你需要创建一个词法分析器和一个解析器来适应你自己的语法。所以我认为你从中获得的好处是巨大的,除非你的 API 不需要这些复杂的运算符。要考虑的一件事是如何扩展 api 以不损害性能,但这取决于您用来托管它的基础设施。
希望这有帮助。
我有一个 OData 端点(使用 .NET Core 和 .NET 4.7.1 进行了测试),它公开了内存中内置的 2,500 个对象。 Get OData 调用需要 30-40 秒。等效的 ASP.NET WEB API 调用 return raw JSON 需要 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,这意味着:
Filters in ASP.NET Core allow code to be run before or after specific stages in the request processing pipeline.
在此处查看有关 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 个不同的线程对这些 enpoints 中的每一个执行相同的请求:get1, get2, get3[=43=] 结果如下:
每一个的平均毫秒数是:433,355,337 我认为这还不错,第一个是 Odata,与最后一个相比,此负载测试仅相差 96 毫秒。 不确定为什么您的示例在这里花费 30-40 秒,因为我使用了相同的代码和 Jmeter 进行负载测试,并且我得到的最长时间是一个请求的 900 毫秒,只有第一个请求,这很有意义,因为应用程序池是从第一个请求开始,以防它处于睡眠状态
恕我直言,如果你想实现你可以用 odata 做的所有操作(shaping、排序、分页、过滤器)你也需要做很多反射工作,至少对于 shaping 和过滤器,没有说明所有可用的二元运算符。 创建你自己的语法,对我来说不是一个选项,你需要创建一个词法分析器和一个解析器来适应你自己的语法。所以我认为你从中获得的好处是巨大的,除非你的 API 不需要这些复杂的运算符。要考虑的一件事是如何扩展 api 以不损害性能,但这取决于您用来托管它的基础设施。 希望这有帮助。