C# 为什么人们说SqlDataReader.GetXXX(i)比SqlDataReader[i]快?
更新:事实证明反射也不一定要慢。使用Fasterflect(http://www.codeproject.com/Articles/38840/Fasterflect-a-fast-and-simple-API-for-Reflection-i). 它使反射速度提高100倍(我指的是最字面的“字面”一词,而不是经常被误用的比喻) 我的代码现在可以加载数据并将数据放入我的业务对象,就像SQLServerManagementStudio在表上执行select*一样快C# 为什么人们说SqlDataReader.GetXXX(i)比SqlDataReader[i]快?,c#,sql,performance,ado.net,C#,Sql,Performance,Ado.net,更新:事实证明反射也不一定要慢。使用Fasterflect(http://www.codeproject.com/Articles/38840/Fasterflect-a-fast-and-simple-API-for-Reflection-i). 它使反射速度提高100倍(我指的是最字面的“字面”一词,而不是经常被误用的比喻) 我的代码现在可以加载数据并将数据放入我的业务对象,就像SQLServerManagementStudio在表上执行select*一样快 我刚刚运行了这段代码,检查表
我刚刚运行了这段代码,检查表中的任何数据类型,并使用适当的Get方法:
foreach (var p in obj.Properties)
{
object value;
var i = fieldNumbers[p.Alias];
if (p.Type == "System.Nullable`1[System.Int16]") value = dr.GetSqlInt16(i);
else if (p.Type == "System.Nullable`1[System.Int32]") value = dr.GetSqlInt32(i);
else if (p.Type == "System.Nullable`1[System.Decimal]") value = dr.GetSqlDecimal(i);
else if (p.Type == "System.Nullable`1[System.Boolean]") value = dr.GetSqlBoolean(i);
else if (p.Type == "System.String") value = dr.GetSqlString(i);
else if (p.Type == "System.Nullable`1[System.DateTime]") value = dr.GetSqlDateTime(i);
}
这是:
foreach (var p in obj.Properties)
{
object value;
var i = fieldNumbers[p.Alias];
value = dr[i];
}
而第二个始终表现得更快。我对此感到惊讶,但这似乎是真的。有没有人能告诉我,我是否忽略了一些东西,因为我看到一些人声称使用GetXXX方法的性能更好。我将此作为一个整体进行计时,并对单个检索操作进行计时。我真的刚刚揭穿了一个神话吗
编辑:在测试了一些之后,我发现了一些问题
第一-使用get方法将值返回到强类型变量中稍微快一点(对于我运行的测试来说大约是8%),我在没有上面所有多余代码的情况下对其进行了测试,因此没有调度或类似的操作。。。只是苹果对苹果
但是,请注意,我使用的是GetSqlXXX函数,而不是GetXXX函数。这是因为后者不能用于空值。但是,前者返回的类型是SqlInt32而不是int?。我的字段不是SqlXXX,它们是简单的可为空类型,比如int?。我认为大多数人都会遇到这种情况,这意味着除非您想在整个代码中开始使用SqlTypes,否则无法真正提高类型化方法的速度
其次,我注意到检索空值的速度似乎比您通常预期的要慢。。。当然,这只是我的看法
编辑2:仅针对Doug McClean和Theevillpenguin,我按如下方式对分支进行“仅”计时:
Stopwatch sw = new Stopwatch();
long time = 0;
while (dr.Read())
{
var obj = new O();
obj.Initializing = true;
sw.Restart();
foreach (var p in obj.Properties)
{
if (p.Type == "System.Nullable`1[System.Int16]") continue;
else if (p.Type == "System.Nullable`1[System.Int32]") continue;
else if (p.Type == "System.Nullable`1[System.Decimal]") continue;
else if (p.Type == "System.Nullable`1[System.Boolean]") continue;
else if (p.Type == "System.String") continue;
}
time += sw.ElapsedTicks;
}
sw.Stop();
MessageBox.Show(time.ToString());
我不得不在这里留下一些不特定于分支的行,但是你可以看到,我只是把分支周围的时间加起来。起初,我是在毫秒内完成的,结果(大约60k条记录)是1。很明显,每个周期都不到一毫秒,所以我切换到滴答声,结果是466472,不到1毫秒的一半(除非我把小数点弄错了……如果我不在那里,请有人纠正我)。那么分支有多贵呢?不是
实际上,这些结果看起来非常小,所以如果我在测试中犯了错误,请有人纠正我,但无论哪种方式分支都是最便宜的事情之一。我怀疑,尽管我还没有进行基准测试,如果你静态地知道与每个
I
相关联的内容的类型,与完全动态索引器语法相比,GetXXX(i)
方法可能具有性能优势
另一方面,通过调度我不希望优于内置动态版本的类型来滚动您自己的动态版本,因此我对给出的示例并不感到惊讶。这一切都取决于ADO提供程序,这个答案涉及SQL Server ADO.NET提供程序,也称为
SqlClient
看看你的基准测试,它看起来是无效的。特别是在混合中添加了一组字符串比较
对于有效的微基准测试,GetXYZ
稍微快一点,因为GetValue
的开销稍大,特别是:
GetValue
将内容导入内部SqlBuffer.Value
中,这是一个简单的case语句,可以分派到相同的属性GetXYZ
分派GetValue
调用SqlStatistics.StartTimer
,而GetXYZ
调用不调用GetValue()
实现,我怀疑这是否值得
以下微观基准测试演示了性能差异:
// include Dapper from nuget
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlClient;
using Dapper;
using System.Diagnostics;
namespace ConsoleApplication16
{
class Program
{
static void Main(string[] args)
{
var cnn = new SqlConnection("Data Source=.;Initial Catalog=tempdb;Integrated Security=True");
cnn.Open();
cnn.Execute("create table #t(num int, str nvarchar(50))");
// 10 k records
cnn.Execute("insert #t values (@num, @str)",
Enumerable.Range(1, 10000).Select(i => new { num = i, str = Guid.NewGuid().ToString() }));
Stopwatch sw;
SqlCommand cmd = new SqlCommand("select * from #t");
cmd.Connection = cnn;
for (int i = 0; i < 10; i++)
{
sw = Stopwatch.StartNew();
using (var reader = cmd.ExecuteReader())
{
int num;
string str;
while (reader.Read())
{
num = reader.GetInt32(0);
str = reader.GetString(1);
}
}
Console.WriteLine("GetXYZ {0}", sw.ElapsedTicks);
sw = Stopwatch.StartNew();
using (var reader = cmd.ExecuteReader())
{
int num;
string str;
while (reader.Read())
{
num = (int)reader.GetValue(0);
str = (string)reader.GetValue(1);
}
}
Console.WriteLine("GetValue {0}", sw.ElapsedTicks);
}
Console.ReadKey();
}
}
}
//包括nuget的Dapper
使用制度;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用System.Data.SqlClient;
使用整洁;
使用系统诊断;
命名空间控制台应用程序16
{
班级计划
{
静态void Main(字符串[]参数)
{
var cnn=new-SqlConnection(“数据源=;初始目录=tempdb;集成安全性=True”);
cnn.Open();
执行(“创建表#t(num int,str nvarchar(50))”;
//10K记录
Execute(“插入#t值(@num,@str)”,
范围(11000)。选择(i=>new{num=i,str=Guid.NewGuid().ToString()});
秒表开关;
SqlCommand cmd=newsqlcommand(“选择*from#t”);
cmd.Connection=cnn;
对于(int i=0;i<10;i++)
{
sw=秒表。开始新();
使用(var reader=cmd.ExecuteReader())
{
int-num;
字符串str;
while(reader.Read())
{
num=reader.GetInt32(0);
str=reader.GetString(1);
}
}
Console.WriteLine(“GetXYZ{0}”,sw.ElapsedTicks);
sw=秒表。开始新();
使用(var reader=cmd.ExecuteReader())
{
int-num;
字符串str;
while(reader.Read())
{
num=(int)reader.GetValue(0);
str=(string)reader.GetValue(1);
}
}
GetXYZ 25094
GetValue 27877
GetXYZ 24226
GetValue 25450
...
GetXYZ 24029
GetValue 26571