说明:本文的第一版由于反对人数较多(推荐/反对数量是:23 / 17), 我在8月20日删除了博文内容,只留下一段简单的内容。
既然分享技术也引来这么多的反对,那我就不分享了。
如果希望知道我的优化方法,请回复留下email地址。
但是让我万万没有想到的是:到10月17日为止,内容没有了,推荐数量还翻了一倍。
为了表示对所有点过【推荐】的朋友表示感谢,我决定重写本文。
在此,尤其要感谢那些 在没有博文的情况下仍然愿意点击推荐 的朋友,真心谢谢你们的支持。
我的每篇博文后面都会说:我的写作热情也离不开您的肯定支持。,所以,是你们重新给了我重写本文的热情。
本文的第一版,说了过多的理论内容。 在我阅读一些人的评论后(来自其它博客),我发现解释理论是个失败的写作方法, 这次重写,我决定不提任何理论内容,直接用代码解释【ClownFish 比手写代码还快的原因】。
一般说来,用纯ADO.NET的方式从数据库加载一个实体列表,【应该】会写成这样:
private static List<Product> LoadProduct_1(DbDataReader reader) { List<Product> list = new List<Product>(); while( reader.Read() ) { Product p = new Product(); p.ProductID = (int)reader["ProductID"]; p.ProductName = reader["ProductName"].ToString(); p.CategoryID = (int)reader["CategoryID"]; p.Unit = reader["Unit"].ToString(); p.UnitPrice = (decimal)reader["UnitPrice"]; p.Remark = reader["Remark"].ToString(); p.Quantity = (int)reader["Quantity"]; list.Add(p); } return list; }
然而,这段代码在性能上,并不是最优秀的,最优秀的代码应该是这样的:
private static List<Product> LoadProduct_2(DbDataReader reader) { List<Product> list = new List<Product>(); while( reader.Read() ) { Product p = new Product(); p.ProductID = reader.GetInt32(0); p.ProductName = reader.GetString(1); p.CategoryID = reader.GetInt32(2); p.Unit = reader.GetString(3); p.UnitPrice = reader.GetDecimal(4); p.Remark = reader.GetString(5); p.Quantity = reader.GetInt32(6); list.Add(p); } return list; }
在 LoadProduct_2 方法中,直接通过序号去读取数据,而且使用专用类型的方法,因此,速度是无敌的。
二段代码贴完了,我想有必要申明一下了:
我这里所说的【手工代码】,是指在能在项目中使用的手工代码,因此,LoadProduct_1应该是这类代码的代表。
LoadProduct_2方法中所使用的小技巧,我想有些人是知道的。
但是,我想【正常人】是不会在项目中写这样的代码的,
因为这样的代码很难维护,你可以自己想像一下:
1. 它要求ProductID,ProductName,CategoryID,.....等等字段的输出顺序【一定】是固定的。
2. 所有代码中出现的字段必须同时输出。
现实情况是:
1. 实体对象可以从不同的查询结果中加载,不同的查询语句,字段的输出顺序不会一样。
2. 不同的查询输出的字段的数量不会一样,Product对象有时可能加载3个成员,有时可能加载5个成员。
所以,LoadProduct_2的那种数据加载方法,【正常人】是不会在项目中那样写的。
也因为这个缘故,我认为手工版本的ADO.NET代码应该是LoadProduct_1那样子才对。
自从这篇博客出现后,我发现一直有人认为我的说法【ClownFish比手工代码要快】不正确,
他们于是就拿出LoadProduct_2那种代码与ClownFish做比较。
对于这种人,我真是无语了。
我还是那个问题:你在项目中是那样写代码的吗?
曾经听到有人说:他的项目中有这样的代码,是用代码生成器生成的。
这个回答实让我更无语了:代码生成器生成的代码是你写手的吗??
其实,我何尝不知道LoadProduct_2的那种方法,
LoadProduct_2中的代码就是ClownFish的优化目标,
ClownFish正是采用这种方式来加载数据的。
或许你会认为很奇怪:ClownFish是一个类库,怎么能以这种方式加载数据?
答案:没什么奇怪的,ClownFish内部自带一个代码生成器,可以根据实体类型的定义,生成这样的代码,
然后在运行时,动态编译这些代码,最后直接调用它们。
因此,ClownFish在加载数据时,根本不使用反射,而且使用优化的数据加载方式,所以,性能非常好。
关于ClownFish的编译模式,需要注意的是,ClownFish默认情况下不开启用编译模式。
点击此处阅读:让ClownFish以编译模式工作
我承认,ClownFish的速度确实要比LoadProduct_2稍微慢一点,
由于,ClownFish是一个类库,它不知道将要加载什么数据集,它不可能做出任何关于字段顺序的假设,
因此,ClownFish在运行时动态生成的代码要添加一些判断,处理顺序、字段是否存在以及数据库空值等等问题,
所以,ClownFish不可能达到LoadProduct_2的速度。
另外,请你想一下:LoadProduct_2的代码有什么意义? 就是证明手工代码比ClownFish快吗?
虽然理论上用纯ADO.NET的方式,是可以比ClownFish快,然而那种调用方式不适合用在实际项目中。
如果你仍然固执地拿LoadProduct_2来证明手工代码比ClownFish快,
我还建议你不要活在理论的梦想中,除非你真的想写出不让人维护的代码。