为什么人们说SqlDataReader.GetXXX(i)比SqlDataReader [i]快?

时间:2012-02-10 02:31:13

标签: c# sql performance ado.net


更新:结果反射也不一定很慢。使用Fasterflect(http://www.codeproject.com/Articles/38840/Fasterflect-a-fast-and-simple-API-for-Reflection-i)。它从字面上做出反思(我的意思是“字面意义”这个词在字面意义上,而不是比喻,因为它经常被误用),速度提高100倍。

我现在已经获得了我的代码,它加载数据并将数据放入我的业务对象,就像sql server management studio可以在表上执行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方法表现更好。我将此作为一个整体计时,并且还计时个别检索操作。我真的只是揭穿神话吗?

编辑:经过测试后,我发现了一些事情。

1st - 使用一个get方法将一个值返回到一个强类型变量并且我没有上面所有多余的代码测试这个'是''是'稍微快一点(我运行的测试大约8%)所以没有调度或类似的东西......只是苹果到苹果。

但请注意,我使用的是GetSqlXXX函数而不是GetXXX函数。这是因为后者不能用于空值。但是,前者返回的类型如SqlInt32而不是int?。我的字段不是SqlXXX,它们是简单的可空类型,如int?。我认为对于大多数人来说通常会出现这种情况,这意味着除非你想在整个代码中开始使用SqlTypes,否则你不会真正提高类型化方法的速度。

其次,我注意到检索空值似乎比你预期的要慢......但这只是我的观点。

编辑2:只是为了Doug McClean和TheEvilPenguin,我把时间“恰好”分支如下:

        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());

我不得不在那里留下一些不特定于分支的行,但是你可以看到我只是在分支上增加了时间。起初我用毫秒做了它,结果(大约6万条记录)是1.显然每个循环都不到一毫秒所以我切换到滴答,结果是466472,不到1毫秒的一半(除非我得到我的小数点混淆了......如果我在那里,有人请纠正我。分支是多么昂贵?不

实际上这些结果看起来确实很小,所以如果我在测试中犯了错误,请有人纠正我,但无论哪种方式分支都是你能做的最便宜的事情之一。

2 个答案:

答案 0 :(得分:4)

这完全取决于ADO提供程序,此答案涉及SQL Server ADO.NET提供程序,即SqlClient

查看您的基准测试,它看起来不合适。特别是你在混合中添加了一堆字符串比较。

对于有效的微观基准GetXYZ位更快,因为GetValue的开销略高,特别是:

  1. GetValue将内容汇集到内部SqlBuffer.Value中,这是一个简单的案例语句,可以调度到相同的属性GetXYZ调度。

  2. GetValue调用SqlStatistics.StartTimerGetXYZ调用不调用GetValue()

  3. 有可能IL可以加快一点// 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(); } } } 实施,我怀疑它是否值得。

    以下微基准测试显示了性能差异:

    GetValue

    结果:

    GetXYZ 25094
    GetValue 27877
    GetXYZ 24226
    GetValue 25450
    ...
    GetXYZ 24029
    GetValue 26571
    

    {{1}}始终如此轻微。在绝对最坏的情况下,情况会恶化5%。

答案 1 :(得分:3)

我怀疑,虽然我没有进行基准测试,但如果您静态地知道与每个i相关联的类型,那么GetXXX(i)方法可能会有一个性能优于完全动态索引器语法。

另一方面,通过调度我不希望超出内置动态版本的类型来滚动自己的动态版本,所以我对给出的示例并不感到惊讶。