使用DataTable.Add时性能差异极大

时间:2015-01-30 21:02:04

标签: c# .net performance

看看下面的程序。这很容易理解,但无论如何我都会解释:)

我有两种方法,一种是快速的,一种是慢速的。这些方法完全相同:它们创建一个包含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秒)。如果我实际上对表有一些约束,我想这可能对我有意义。

1 个答案:

答案 0 :(得分:8)

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

例如,如果你这样做,

theRow.BeginEdit();

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

theRow.CancelEdit();

然后在BeginEdit()中,internally它会获取该行内容的副本,以便在任何时候都可以回滚 - 并且上面的结果是没有whatever的情况下再次空行。即使处于BeginLoadData模式,这仍然是可能的。遵循BeginEdit 的路径(如果附加到DataTable ),最终进入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时,它比非连接时慢得多。