aqu*_*nas 8 .net c# 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
}
}
Run Code Online (Sandbox Code Playgroud)
更新
调用theRow.BeginEdit和theRow.EndEdit慢速版本使慢速版本或多或少不变(我的机器上约4秒).如果我在桌子上确实有一些限制,我想这可能对我有意义.
当附加到表格时,正在做更多的工作来记录和跟踪每个变化的状态.
例如,如果你这样做,
theRow.BeginEdit();
for (int j = 0; j < NUM_COLS_TO_WRITE_TO; j++)
{
theRow[j] = "whatever";
}
theRow.CancelEdit();
Run Code Online (Sandbox Code Playgroud)
然后在BeginEdit(),在内部它采取的行的内容的副本,以便在任何时候,你可以回滚-与上述最终的结果又是一个空行不whatever.即使在BeginLoadData模式下,这仍然是可能的.遵循BeginEdit if附加到DataTable的路径,最终进入DataTable.NewRecord(),这表明它只是复制每个列的每个值来存储原始状态,因为需要取消 - 这里没什么大不了的.另一方面,如果没有连接到数据表,则根本不会发生太多事情BeginEdit并且很快就会退出.
EndEdit()同样非常重(当附加时),因为这里检查了所有约束等(最大长度,列是否允许空值等).此外,它还会触发一系列事件,显然可以释放存储空间,因为编辑被取消了,并且可以进行调用DataTable.GetChanges(),这仍然是可能的BeginLoadData.事实上看来源BeginLoadData似乎都是关闭约束检查和索引.
因此,它描述了什么BeginEdit和EditEdit做什么,并且它们在附加或不附加存储的内容时完全不同.现在考虑一下theRow[j] = "whatever"你可以在索引器设置器上看到它调用的DataRowBeginEditInternal,然后EditEdit是每次调用(如果它已经在编辑中,因为你明确调用了BeginEdit更早).这意味着每次执行此调用时,它都会复制并存储行中每列的每个值.所以你要做10次,这意味着你的1000列DataTable超过50,000行,这意味着它正在分配500,000,000个对象.除此之外,还有所有其他版本控制,检查和事件在每次更改后被触发,因此,总体而言,当行附加到DataTable时,它比非时更慢.