20

そのため、DataAccess ルートを DataTable と DataReader のどちらにするかについて、激しい議論が繰り広げられました。

免責事項私は DataReader 側におり、これらの結果は私の世界を揺るがしました。

速度の違いをテストするために、いくつかのベンチマークを作成することになりました。DataReader の方が高速であることが一般的に認められていましたが、私たちはどれだけ高速かを確認したかったのです。

その結果は私たちを驚かせました。DataTable は一貫して DataReader よりも高速でした。時々2倍の速さで近づく。

だから私はあなたに目を向けます、SOのメンバー。なぜ、ほとんどのドキュメントや Microsoft でさえ、DataReader の方が高速であると述べているのに、私たちのテストではそうではないことが示されているのです。

そして今、コードのために:

テスト ハーネス:

    private void button1_Click(object sender, EventArgs e)
    {
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();

        DateTime date = DateTime.Parse("01/01/1900");

        for (int i = 1; i < 1000; i++)
        {

            using (DataTable aDataTable = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveDTModified(date))
            {
            }
        }
        sw.Stop();
        long dataTableTotalSeconds = sw.ElapsedMilliseconds;

        sw.Restart();


        for (int i = 1; i < 1000; i++)
        {
            List<ArtifactBusinessModel.Entities.ArtifactString> aList = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveModified(date);

        }

        sw.Stop();

        long listTotalSeconds = sw.ElapsedMilliseconds;

        MessageBox.Show(String.Format("list:{0}, table:{1}", listTotalSeconds, dataTableTotalSeconds));
    }

これは DataReader の DAL です。

        internal static List<ArtifactString> RetrieveByModifiedDate(DateTime modifiedLast)
        {
            List<ArtifactString> artifactList = new List<ArtifactString>();

            try
            {
                using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts"))
                {
                    using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn))
                    {
                        command.CommandType = CommandType.StoredProcedure;
                        command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast));
                        using (SqlDataReader reader = command.ExecuteReader())
                        {
                            int formNumberOrdinal = reader.GetOrdinal("FormNumber");
                            int formOwnerOrdinal = reader.GetOrdinal("FormOwner");
                            int descriptionOrdinal = reader.GetOrdinal("Description");
                            int descriptionLongOrdinal = reader.GetOrdinal("DescriptionLong");
                            int thumbnailURLOrdinal = reader.GetOrdinal("ThumbnailURL");
                            int onlineSampleURLOrdinal = reader.GetOrdinal("OnlineSampleURL");
                            int lastModifiedMetaDataOrdinal = reader.GetOrdinal("LastModifiedMetaData");
                            int lastModifiedArtifactFileOrdinal = reader.GetOrdinal("LastModifiedArtifactFile");
                            int lastModifiedThumbnailOrdinal = reader.GetOrdinal("LastModifiedThumbnail");
                            int effectiveDateOrdinal = reader.GetOrdinal("EffectiveDate");
                            int viewabilityOrdinal = reader.GetOrdinal("Viewability");
                            int formTypeOrdinal = reader.GetOrdinal("FormType");
                            int inventoryTypeOrdinal = reader.GetOrdinal("InventoryType");
                            int createDateOrdinal = reader.GetOrdinal("CreateDate");

                            while (reader.Read())
                            {
                                ArtifactString artifact = new ArtifactString();
                                ArtifactDAL.Map(formNumberOrdinal, formOwnerOrdinal, descriptionOrdinal, descriptionLongOrdinal, formTypeOrdinal, inventoryTypeOrdinal, createDateOrdinal, thumbnailURLOrdinal, onlineSampleURLOrdinal, lastModifiedMetaDataOrdinal, lastModifiedArtifactFileOrdinal, lastModifiedThumbnailOrdinal, effectiveDateOrdinal, viewabilityOrdinal, reader, artifact);
                                artifactList.Add(artifact);
                            }
                        }
                    }
                }
            }
            catch (ApplicationException)
            {
                throw;
            }
            catch (Exception e)
            {
                string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast);
                Logging.Log(Severity.Error, errMsg, e);
                throw new ApplicationException(errMsg, e);
            }

            return artifactList;
        }
    internal static void Map(int? formNumberOrdinal, int? formOwnerOrdinal, int? descriptionOrdinal, int? descriptionLongOrdinal, int? formTypeOrdinal, int? inventoryTypeOrdinal, int? createDateOrdinal,
        int? thumbnailURLOrdinal, int? onlineSampleURLOrdinal, int? lastModifiedMetaDataOrdinal, int? lastModifiedArtifactFileOrdinal, int? lastModifiedThumbnailOrdinal,
        int? effectiveDateOrdinal, int? viewabilityOrdinal, IDataReader dr, ArtifactString entity)
    {

            entity.FormNumber = dr[formNumberOrdinal.Value].ToString();
            entity.FormOwner = dr[formOwnerOrdinal.Value].ToString();
            entity.Description = dr[descriptionOrdinal.Value].ToString();
            entity.DescriptionLong = dr[descriptionLongOrdinal.Value].ToString();
            entity.FormType = dr[formTypeOrdinal.Value].ToString();
            entity.InventoryType = dr[inventoryTypeOrdinal.Value].ToString();
            entity.CreateDate = DateTime.Parse(dr[createDateOrdinal.Value].ToString());
            entity.ThumbnailURL = dr[thumbnailURLOrdinal.Value].ToString();
            entity.OnlineSampleURL = dr[onlineSampleURLOrdinal.Value].ToString();
            entity.LastModifiedMetaData = dr[lastModifiedMetaDataOrdinal.Value].ToString();
            entity.LastModifiedArtifactFile = dr[lastModifiedArtifactFileOrdinal.Value].ToString();
            entity.LastModifiedThumbnail = dr[lastModifiedThumbnailOrdinal.Value].ToString();
            entity.EffectiveDate = dr[effectiveDateOrdinal.Value].ToString();
            entity.Viewability = dr[viewabilityOrdinal.Value].ToString();
    }

これは、DataTable の DAL です。

        internal static DataTable RetrieveDTByModifiedDate(DateTime modifiedLast)
        {
            DataTable dt= new DataTable("Artifacts");

            try
            {
                using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts"))
                {
                    using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn))
                    {
                        command.CommandType = CommandType.StoredProcedure;
                        command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast));

                        using (SqlDataAdapter da = new SqlDataAdapter(command))
                        {
                            da.Fill(dt);
                        }
                    }
                }
            }
            catch (ApplicationException)
            {
                throw;
            }
            catch (Exception e)
            {
                string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast);
                Logging.Log(Severity.Error, errMsg, e);
                throw new ApplicationException(errMsg, e);
            }

            return dt;
        }

結果:

テスト ハーネス内の 10 回の反復

テスト ハーネス内での 10 回の反復

テスト ハーネス内での 1000 回の反復の場合

ここに画像の説明を入力

これらの結果は、接続の作成による違いを軽減するための 2 回目の実行です。

4

4 に答える 4

26

次の 3 つの問題があります。

  1. DataReader を使用する方法は、それをリストに変換することにより、メモリ内の単一アイテムの大きな利点を無効にします。
  2. DataTable を優先する方法で本番環境とは大幅に異なる環境でベンチマークを実行している。
  3. DataReader レコードを、DataTable コードで複製されていない Artifact オブジェクトに変換するのに時間を費やしています。

DataReader の主な利点は、一度にすべてをメモリにロードする必要がないことです。これは、CPU ではなくメモリがボトルネックになることが多い Web アプリの DataReader にとって大きな利点となるはずですが、各行を一般的なリストに追加することで、これを無効にしました。これはまた、一度に 1 つのレコードのみを使用するようにコードを変更した後でも、多くの空きメモリを備えたシステムで実行しているため、違いがベンチマークに現れない可能性があることを意味します。これは、DataTable を優先します。また、DataReader バージョンは、DataTable がまだ行っていない Artifact オブジェクトへの結果の解析に時間を費やしています。

DataReader の使用に関する問題を修正するには、すべての場所に変更List<ArtifactString>IEnumerable<ArtifactString>、DataReader DAL で次の行を変更します。

artifactList.Add(artifact);

これに:

yield return artifact;

これは、結果を反復処理するコードを DataReader テスト ハーネスに追加して、公平性を保つ必要があることを意味します。

ベンチマークを調整して、DataTable と DataReader の両方に公平なより一般的なシナリオを作成する方法がわかりません。ただし、ページの 2 つのバージョンを作成し、各バージョンを同様の運用レベルの負荷で 1 時間提供する場合を除きます。実際のメモリ プレッシャがあることを確認してください... 実際の A/B テストを行います。また、DataTable の行を Artifacts に変換することをカバーしていることを確認してください...そして、DataReader に対してはこれを行う必要があるが、DataTable に対してはこれを行う必要がないという議論がある場合、それは単に間違っています。

于 2012-11-30T18:13:06.033 に答える
2

SqlDataAdapter.Fillsetを指定してSqlCommand.ExecuteReaderを呼び出しますCommandBehavior.SequentialAccess。多分それは違いを生むのに十分です。

余談ですが、IDbReaderパフォーマンス上の理由から、実装は各フィールドの序数をキャッシュしているようです。このアプローチの代わりに、DbEnumeratorクラスを使用することもできます。

DbEnumeratorフィールド名->序数ディクショナリを内部にキャッシュするため、フィールド名を簡単に使用できるため、序数を使用することによるパフォーマンス上の利点の多くが得られます。

foreach(IDataRecord record in new DbEnumerator(reader))
{
    artifactList.Add(new ArtifactString() {
        FormNumber = (int) record["FormNumber"],
        FormOwner = (int) record["FormOwner"],
        ...
    });
}

あるいは:

return new DbEnumerator(reader)
    .Select(record => new ArtifactString() {
        FormNumber = (int) record["FormNumber"],
        FormOwner = (int) record["FormOwner"],
        ...
      })
    .ToList();
于 2012-11-30T18:14:02.977 に答える
2

2 つのことがあなたを遅くしている可能性があります。

まず、パフォーマンスに関心がある場合は、各列の「名前による序数の検索」は行いません。以下の「レイアウト」クラスがこのルックアップを処理することに注意してください。そして、「0」、「1」、「2」などを使用する代わりに、後でレイアウトプロバイダーが読みやすくなります。また、Concrete の代わりにインターフェイス (IDataReader) にコーディングできます。

2番。「.Value」プロパティを使用しています。(そして、これは違いを生むと思います)

具体的なデータ型「ゲッター」を使用すると、より良い結果(IMHO)が得られます。

GetString、GetDateTime、GetInt32 など。

これが私の典型的な IDataReader to DTO/POCO コードです。

[Serializable]
public partial class Employee
{
    public int EmployeeKey { get; set; }                   
    public string LastName { get; set; }                   
    public string FirstName { get; set; }   
    public DateTime HireDate  { get; set; }  
}

[Serializable]
public class EmployeeCollection : List<Employee>
{
}   

internal static class EmployeeSearchResultsLayouts
{
    public static readonly int EMPLOYEE_KEY = 0;
    public static readonly int LAST_NAME = 1;
    public static readonly int FIRST_NAME = 2;
    public static readonly int HIRE_DATE = 3;
}


    public EmployeeCollection SerializeEmployeeSearchForCollection(IDataReader dataReader)
    {
        Employee item = new Employee();
        EmployeeCollection returnCollection = new EmployeeCollection();
        try
        {

            int fc = dataReader.FieldCount;//just an FYI value

            int counter = 0;//just an fyi of the number of rows

            while (dataReader.Read())
            {

                if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.EMPLOYEE_KEY)))
                {
                    item = new Employee() { EmployeeKey = dataReader.GetInt32(EmployeeSearchResultsLayouts.EMPLOYEE_KEY) };

                    if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.LAST_NAME)))
                    {
                        item.LastName = dataReader.GetString(EmployeeSearchResultsLayouts.LAST_NAME);
                    }

                    if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.FIRST_NAME)))
                    {
                        item.FirstName = dataReader.GetString(EmployeeSearchResultsLayouts.FIRST_NAME);
                    }

                    if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.HIRE_DATE)))
                    {
                        item.HireDate = dataReader.GetDateTime(EmployeeSearchResultsLayouts.HIRE_DATE);
                    }


                    returnCollection.Add(item);
                }

                counter++;
            }

            return returnCollection;

        }
        //no catch here... see  http://blogs.msdn.com/brada/archive/2004/12/03/274718.aspx
        finally
        {
            if (!((dataReader == null)))
            {
                try
                {
                    dataReader.Close();
                }
                catch
                {
                }
            }
        }
    }
于 2013-04-09T17:15:57.830 に答える
0

すべての違いを説明できるとは思いませんが、次のようなことを試して、余分な変数と関数呼び出しのいくつかを排除してください。

using (SqlDataReader reader = command.ExecuteReader())
{
    while (reader.Read())
    {
        artifactList.Add(new ArtifactString
        {
            FormNumber = reader["FormNumber"].ToString(),
            //etc
        });
     }
}
于 2012-11-30T17:59:45.633 に答える