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

本文关键字:SqlDataReader GetXXX 为什么 人们说 | 更新日期: 2023-09-27 18:24:11


更新:事实证明,反射也不必很慢。 使用Fasterflect (http://www.codeproject.com/Articles/38840/Fasterflect-a-fast-and-simple-API-for-Reflection-i(。 它使字面上的反射(我的意思是最字面意义上的"字面意思"这个词,而不是比喻,因为它经常被滥用(快 100 倍。

我现在已经掌握了我的代码,它可以加载数据并将数据放入我的业务对象中,就像 sql Server Management Studio 可以在表上执行选择 * 一样快。


我刚刚运行了这段代码,用于检查表中的任何数据类型并使用适当的 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?。 我认为对于大多数人来说,这通常是这种情况,这意味着除非你想在整个代码中开始使用 SqlType,否则你不会真正获得类型化方法的速度提高。

其次,我注意到检索空值似乎比您通常预期的要慢......但这当然只是我的意见。

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

不得不在那里留下几行不特定于分支的行,但您可以看到我只是在分支周围增加时间。 起初我在几毫秒内完成,结果(大约 60k 条记录(是 1。 显然每个周期都小于一毫秒,所以我切换到即时报价,结果是466472不到 1 毫秒的一半(除非我把小数位弄混了......如果我在那里,有人请纠正我(。 那么分支有多贵呢?不。

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

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

这一切都取决于 ADO 提供程序,此答案涉及 SQL Server ADO.NET 提供程序,也称为 SqlClient

查看您的基准测试,它看起来无效。特别是,您将一堆字符串比较添加到组合中。

对于有效的微观基准测试,GetXYZ速度稍快一些,因为GetValue开销略高,特别是:

  1. GetValue将内容漏斗到内部SqlBuffer.Value,这是一个简单的 case 语句,调度到相同的属性GetXYZ调度。

  2. GetValue调用SqlStatistics.StartTimer,而GetXYZ调用则不调用。

您可能会 IL 烘焙一个稍微快一点的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();
        }
    }
}

结果:

获取XYZ 25094获取值 27877获取XYZ 24226获取值 25450...获取XYZ 24029获取值 26571

GetValue总是稍微慢一点。 在绝对最坏的情况下差 5%。

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

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