【问题标题】:Extreme performance difference when using DataTable.Add使用 DataTable.Add 时的极端性能差异
【发布时间】:2015-01-31 08:15:23
【问题描述】:

看看下面的程序。这很不言自明,但无论如何我都会解释:)

我有两种方法,一种快,一种慢。这些方法做的事情完全相同:它们创建一个包含 50,000 行和 1000 列的表。我写入表中可变数量的列。在下面的代码中,我选择了 10 个 (NUM_COLS_TO_WRITE_TO)。

换句话说,1000 列中只有 10 列实际包含数据。好的。这两种方法的唯一区别在于,快速填充列并然后调用DataTable.AddRow,而慢速填充之后。就是这样。

然而,性能差异是令人震惊的(无论如何对我来说)。快速版本几乎完全不受更改我们写入的列数的影响,而慢速版本线性上升。例如,当我写入的列数为 20 时,快速版本需要 2.8 秒,但慢速版本需要 分钟

这里到底发生了什么?

我认为添加dt.BeginLoadData 可能会有所不同,它确实在某种程度上将时间从 61 秒缩短到了约 50 秒,但这仍然是一个巨大的差异。

当然,显而易见的答案是,“好吧,不要那样做。”好的。当然。但究竟是什么导致了这种情况?这是预期的行为吗?我当然没想到。 :)

public class Program
{
    private const int NUM_ROWS = 50000;
    private const int NUM_COLS_TO_WRITE_TO = 10;
    private const int NUM_COLS_TO_CREATE = 1000;

    private static void AddRowFast() {
        DataTable dt = new DataTable();            
        //add a table with 1000 columns
        for (int i = 0; i < NUM_COLS_TO_CREATE; i++) {
            dt.Columns.Add("x" + i, typeof(string));
        }
        for (int i = 0; i < NUM_ROWS; i++) {                
            var theRow = dt.NewRow();
            for (int j = 0; j < NUM_COLS_TO_WRITE_TO; j++) {
                theRow[j] = "whatever";
            }

            //add the row *after* populating it
            dt.Rows.Add(theRow);                
        }
    }

    private static void AddRowSlow() {
        DataTable dt = new DataTable();
        //add a table with 1000 columns
        for (int i = 0; i < NUM_COLS_TO_CREATE; i++) {
            dt.Columns.Add("x" + i, typeof(string));
        }
        for (int i = 0; i < NUM_ROWS; i++) {
            var theRow = dt.NewRow();
            //add the row *before* populating it
            dt.Rows.Add(theRow);

            for (int j=0; j< NUM_COLS_TO_WRITE_TO; j++){
                theRow[j] = "whatever";
            }                
        }
    }

    static void Main(string[] args)
    {
        var sw = Stopwatch.StartNew();
        AddRowFast();
        sw.Stop();
        Console.WriteLine(sw.Elapsed.TotalMilliseconds);

        sw.Restart();
        AddRowSlow();
        sw.Stop();
        Console.WriteLine(sw.Elapsed.TotalMilliseconds);

        //When NUM_COLS is 5
        //FAST: 2754.6782
        //SLOW: 15794.1378

        //When NUM_COLS is 10
        //FAST: 2777.431  ms
        //SLOW 32004.7203 ms

        //When NUM_COLS is 20
        //FAST:  2831.1733 ms
        //SLOW: 61246.2243 ms
    }
}

更新

在慢速版本中调用 theRow.BeginEdittheRow.EndEdit 会使慢速版本或多或少保持不变(在我的机器上大约 4 秒)。如果我真的桌子上有一些限制,我想这对我来说可能有意义。

【问题讨论】:

  • 我认为解释很简单。当DataRow 在表格之外时,更改数据不会触发任何检查/验证/通知等。为了更准确的解释,我会查看DataRows 列值设置器代码...
  • @Dennis,但我认为 BeginLoadData 的目的是“在加载数据时关闭通知、索引维护和约束”。此外,似乎慢 30 是一个相当大的差异。
  • 另外,我觉得奇怪的是表中的列数会影响运行时。也就是说,如果我更新 10 列并且只有 are 10 列,那么它运行得非常好。那么,.NET 对我从未接触过的所有这些列做了什么?事件是否也在这些事件上触发?看起来很奇怪。
  • @aquinas:您的示例中没有调用BeginLoadData。此外,文档说“将 BeginLoadData 与 LoadDataRow 和 EndLoadData 结合使用”。看起来它适用于非常特殊的场景。
  • 对,就像我上面说的,当我添加 BeginLoadData 时,它会在 60 秒中缩短大约 10 秒。

标签: c# .net performance


【解决方案1】:

当附加到表格时,正在做更多的工作来记录和跟踪每次更改的状态。

例如,如果你这样做,

theRow.BeginEdit();

for (int j = 0; j < NUM_COLS_TO_WRITE_TO; j++)
{
   theRow[j] = "whatever";
}

theRow.CancelEdit();

然后在BeginEdit()internally 中,它会复制行的内容,以便在任何时候,您都可以回滚 - 上面的最终结果是没有whatever 的空行。即使在BeginLoadData 模式下,这仍然是可能的。遵循BeginEdit 的路径如果附加到数据表,最终您会进入DataTable.NewRecord(),这表明它只是复制每一列的每个值以存储原始状态,以防需要取消 -这里没有太多魔法。另一方面,如果没有附加到数据表,BeginEdit 中根本不会发生太多事情,它会很快退出。

EndEdit() 同样很重(附加时),因为这里检查了所有约束等(最大长度,列是否允许空值等)。它还会触发一系列事件,在编辑被取消的情况下显式释放使用的存储空间,并可以通过DataTable.GetChanges() 调用,这在BeginLoadData 中仍然是可能的。事实上,查看源代码 BeginLoadData 似乎所做的就是关闭约束检查和索引。

所以这描述了BeginEditEditEdit 的作用,就存储的内容而言,它们在附加或不附加时完全不同。现在考虑单个theRow[j] = "whatever",您可以在DataRow 的索引器设置器上看到它调用BeginEditInternal,然后在每次调用时调用EditEdit(如果它尚未在编辑中,因为您之前明确调用了BeginEdit )。因此,这意味着每次您执行此调用时,它都会复制和存储行中每一列的每个值。所以你做了 10 次,这意味着你的 1,000 列 DataTable,超过 50,000 行,这意味着它正在分配 500,000,000 个对象。除此之外,每次更改后都会触发所有其他版本控制、检查和事件,因此,总体而言,将行附加到 DataTable 时要比未附加时慢得多。

【讨论】:

  • 好答案。是的,对 NewRecord 的调用绝对是一个杀手锏。
猜你喜欢
  • 2017-04-29
  • 2012-12-29
  • 2016-05-21
  • 2011-08-13
  • 1970-01-01
  • 2021-10-16
  • 2013-11-26
  • 2015-05-22
  • 2019-07-31
相关资源
最近更新 更多