ServiceStack 基准测试继续:为什么坚持简单(复杂)到 JSON 会减慢 SELECT 的速度?
ServiceStack benchmark continued: why does persisting a simple (complex) to JSON slow down SELECTs?
我想切换到 OrmLite,我需要弄清楚它是否很慢,如果是,为什么。
在我的研究中,我得出的结论是,在 OrmLite 中被斑点化为 JSON 的复杂对象是 SELECTs 非常慢的罪魁祸首。
因此,我创建了一个只关注 OrmLite 的新项目,不与除自身以外的任何其他项目进行比较,这里的目的是查看有斑点 JSON 对象和没有拥有它们。
可以在GitHub上找到:
https://github.com/tedekeroth/ormlitebenchmarking
解决方案如下所示:
我是 运行 OrmLite 5.1.1 Windows 7、2.6Ghz、24 GB RAM,目前没有 CPU 负载,使用 MySql 5.6。应用程序连接到 127.0.0.1 (root/root) 并需要数据库 "ormlite".
我启用了 ThrowOnError:
OrmLiteConfig.ThrowOnError = JsConfig.ThrowOnError = true;
应用程序如下所示:
无数据:只有创建的对象,没有属性有数据:
基元:只填充了一些简单的基元属性:
Prim + 一个复数:所有原语如上+一个斑点复数对象:
完整数据:以上所有 + 另外 2 个复杂的斑点对象:
Create 按钮首先在列表中创建 10000 个对象,然后使用 OrmLite Insert 方法将它们持久化。时间测量仅针对 INSERT,而不是创建对象。
public void AddRow<T>(T coreObject) where T : CoreObject
{
long id = 0;
using (var _db = _dbFactory.Open())
{
id = _db.Insert<T>(coreObject, selectIdentity: true);
}
}
Read 按钮读取 table 中的所有行,并重新创建 Customer 对象:
public List<T> FetchAll<T>()
{
using (var _db = _dbFactory.Open())
{
List<T> list = _db.Select<T>();
return list;
}
}
因此,测试应该像这样进行:
- Select模式,按创建,会显示耗时
- 按读取读回当前在table
中的所有行
要测试另一种模式,请清空数据库 table (customer
) 以获得干净的模式。
基准测试
插入
创建 10 000 个对象(未测量)并将它们插入数据库。
- 无数据:~26-27 秒
- 原语:~27.1-27.4 秒
- Prim + 一个复合体:~27.5-29 秒
- 完整数据:~28 秒
所以,总而言之,大致相同,26-29 秒。
SELECT
从数据库中读取 10 000 个对象,如上所示。
- 无数据:~460 毫秒
- 原语:~700-720 毫秒
- Prim + 一个复合体:~970-1030 毫秒
- 完整数据:30000-32000 毫秒(30-32 秒)
结论
"Full data" 显然是大打脸的地方。
添加的复杂斑点对象 (ContactDetails
) 似乎把事情搞砸了。我在之前的测试中注意到了这一点,但对象本身并不是很复杂,请参见下文。所以,我不确定为什么它会这样跳,或者这些数字是否合理。
我之前问过一个关于这个的问题,但这个基准测试应该更准确。
问题是:为什么持久化对象(按照 OrmLite 到 JSON)会以这种方式减慢 SELECTs?
[Serializable]
public class ContactDetails
{
public List<ContactItem> ContactItemList
{
get; set;
}
public ContactItem CurrentContactItem
{
get; set;
}
public ContactItem DefaultContactItem
{
get; set;
}
public bool IgnorePrimaryWaitBuffer
{
get; set;
}
public ContactDetails(List<ContactItem> contactItemList, ContactItem currentContactItem, ContactItem defaultContactItem)
{
ContactItemList = contactItemList;
CurrentContactItem = currentContactItem;
DefaultContactItem = defaultContactItem;
}
public ContactDetails()
{
}
}
我已经成功下载并分析了此解决方案,它突出显示了未缓存后期绑定类型的类型访问器问题的原因,该问题已通过 this commit 解决。
通过此更改,加载 10000 行复杂类型的性能从 11,765 毫秒减少到 669 毫秒(在我的 iMac 5k 上),如下所示:
此更改适用于现在 available on MyGet 的 v5.1.1。
注意:我删除了下面的 JsConfig.IncludeTypeInfo
行:
JsConfig.IncludeTypeInfo = true;
这会强制序列化程序为 increases the payload size and decreases performance 的每个对象发出类型信息。 ServiceStack.Text 会在需要时发出类型信息,即 object
、interfaces
和 abstract
类,所以你应该很少自己强制它,除非它是绝对的需要,因为它会对性能产生重大的不利影响。
理想情况下,您的 DTO 不应使用接口、后期绑定对象或继承,但如果您考虑使基本类型 abstract
强制类型信息仅在需要的地方使用,而不是总是发出它们。
我想切换到 OrmLite,我需要弄清楚它是否很慢,如果是,为什么。
在我的研究中,我得出的结论是,在 OrmLite 中被斑点化为 JSON 的复杂对象是 SELECTs 非常慢的罪魁祸首。
因此,我创建了一个只关注 OrmLite 的新项目,不与除自身以外的任何其他项目进行比较,这里的目的是查看有斑点 JSON 对象和没有拥有它们。
可以在GitHub上找到: https://github.com/tedekeroth/ormlitebenchmarking
解决方案如下所示:
我是 运行 OrmLite 5.1.1 Windows 7、2.6Ghz、24 GB RAM,目前没有 CPU 负载,使用 MySql 5.6。应用程序连接到 127.0.0.1 (root/root) 并需要数据库 "ormlite".
我启用了 ThrowOnError:
OrmLiteConfig.ThrowOnError = JsConfig.ThrowOnError = true;
应用程序如下所示:
无数据:只有创建的对象,没有属性有数据:
基元:只填充了一些简单的基元属性:
Prim + 一个复数:所有原语如上+一个斑点复数对象:
完整数据:以上所有 + 另外 2 个复杂的斑点对象:
Create 按钮首先在列表中创建 10000 个对象,然后使用 OrmLite Insert 方法将它们持久化。时间测量仅针对 INSERT,而不是创建对象。
public void AddRow<T>(T coreObject) where T : CoreObject
{
long id = 0;
using (var _db = _dbFactory.Open())
{
id = _db.Insert<T>(coreObject, selectIdentity: true);
}
}
Read 按钮读取 table 中的所有行,并重新创建 Customer 对象:
public List<T> FetchAll<T>()
{
using (var _db = _dbFactory.Open())
{
List<T> list = _db.Select<T>();
return list;
}
}
因此,测试应该像这样进行:
- Select模式,按创建,会显示耗时
- 按读取读回当前在table 中的所有行
要测试另一种模式,请清空数据库 table (customer
) 以获得干净的模式。
基准测试
插入
创建 10 000 个对象(未测量)并将它们插入数据库。
- 无数据:~26-27 秒
- 原语:~27.1-27.4 秒
- Prim + 一个复合体:~27.5-29 秒
- 完整数据:~28 秒
所以,总而言之,大致相同,26-29 秒。
SELECT
从数据库中读取 10 000 个对象,如上所示。
- 无数据:~460 毫秒
- 原语:~700-720 毫秒
- Prim + 一个复合体:~970-1030 毫秒
- 完整数据:30000-32000 毫秒(30-32 秒)
结论
"Full data" 显然是大打脸的地方。
添加的复杂斑点对象 (ContactDetails
) 似乎把事情搞砸了。我在之前的测试中注意到了这一点,但对象本身并不是很复杂,请参见下文。所以,我不确定为什么它会这样跳,或者这些数字是否合理。
我之前问过一个关于这个的问题,但这个基准测试应该更准确。
问题是:为什么持久化对象(按照 OrmLite 到 JSON)会以这种方式减慢 SELECTs?
[Serializable]
public class ContactDetails
{
public List<ContactItem> ContactItemList
{
get; set;
}
public ContactItem CurrentContactItem
{
get; set;
}
public ContactItem DefaultContactItem
{
get; set;
}
public bool IgnorePrimaryWaitBuffer
{
get; set;
}
public ContactDetails(List<ContactItem> contactItemList, ContactItem currentContactItem, ContactItem defaultContactItem)
{
ContactItemList = contactItemList;
CurrentContactItem = currentContactItem;
DefaultContactItem = defaultContactItem;
}
public ContactDetails()
{
}
}
我已经成功下载并分析了此解决方案,它突出显示了未缓存后期绑定类型的类型访问器问题的原因,该问题已通过 this commit 解决。
通过此更改,加载 10000 行复杂类型的性能从 11,765 毫秒减少到 669 毫秒(在我的 iMac 5k 上),如下所示:
此更改适用于现在 available on MyGet 的 v5.1.1。
注意:我删除了下面的 JsConfig.IncludeTypeInfo
行:
JsConfig.IncludeTypeInfo = true;
这会强制序列化程序为 increases the payload size and decreases performance 的每个对象发出类型信息。 ServiceStack.Text 会在需要时发出类型信息,即 object
、interfaces
和 abstract
类,所以你应该很少自己强制它,除非它是绝对的需要,因为它会对性能产生重大的不利影响。
理想情况下,您的 DTO 不应使用接口、后期绑定对象或继承,但如果您考虑使基本类型 abstract
强制类型信息仅在需要的地方使用,而不是总是发出它们。