dt
:从具有超过170万行的CSV文件填充。大约也有一百万行该代码需要48个小时才能完成。我已将应用程序的属性更改为x64,以允许它使用更多的进程内存。现在它大约使用2.5GB。
我的问题是,有没有一种更有效的方法可以减少运行时间?
>如何填充
dataStructure.Tables["AccountData"]
://set is_legal column value for each row
foreach (DataRow row in dt.Rows)
{
var acctNum = row[0].ToString().Replace("\"", "");
foreach (DataRow queryRow in dataStructure.Tables["AccountData"].Rows)
{
var queryAcctNum = queryRow[0].ToString();
if (acctNum.Equals(queryAcctNum))
{
row[12] = "Y";
Console.WriteLine("Yes count: " + cnt);
}
else
{
row[12] = "N";
}
}
cnt++;
};
#1 楼
您的内部循环似乎是不必要的。为什么不创建查找:var knownAccountNumbers = new HashSet<string>(
dataStructure.Tables["AccountData"].Rows
.Cast<DataRow>()
.Select(row => row[0].ToString()));
现在您的循环很简单:
foreach (DataRow row in dt.Rows)
{
var accountNumber = row[0].ToString().Replace("\"", "");
row[12] = knownAccountNumbers.Contains(accountNumber) ? "Y" : "N";
}
我想我记得读取一次,
HashSet
的内存使用量为每个条目12字节+条目大小。因此,您正在查看的是12MB + 1,000,000 *(2 * accountNumber.Length)。因此,在宏伟的计划中基本上什么也没有。但是,您将获得不断的时间查询,这对于此类工作将是一个巨大的好处。请勿缩写,例如acctNum
-> accountNumber
。评论
\ $ \ begingroup \ $
此解决方案花费了17秒。
\ $ \ endgroup \ $
– HappyCoding
16年2月16日在15:36
\ $ \ begingroup \ $
@HappyCoding-真高兴! :)
\ $ \ endgroup \ $
– RobH
16年2月16日在15:48
\ $ \ begingroup \ $
每次调用Contains时,都会懒惰地计算knownAccountNumbers。最后应该确实有一个.ToList()或.ToDictionary()。
\ $ \ endgroup \ $
– Esben Skov Pedersen
16-2-17在14:27
\ $ \ begingroup \ $
@EsbenSkovPedersen-它不是这样工作的。可枚举在HashSet的构造函数中评估。您可以使用参考源进行确认。最终调用了枚举可枚举的UnionWith。
\ $ \ endgroup \ $
– RobH
16年2月17日在15:04
\ $ \ begingroup \ $
@EsbenSkovPedersen-我将不得不再次不同意。我不需要字典(键->值),我只需要基于键的简单快速查找-即哈希集。
\ $ \ endgroup \ $
– RobH
16年2月17日在20:01
#2 楼
该代码似乎已损坏,找到匹配项后您没有break;
,因此所有记录都可能具有row[12] == "N"
。您应该对
accountNumber
进行联接: > var matchingRows =
from DataRow row in dt.Rows
let rowKey = row[0].ToString().Replace("\"", "")
join DataRow queryRow in dataStructure.Tables["AccountData"].Rows
on rowKey equals queryRow[0]
select row;
foreach(var row in matchingRows)
{
row[12] = "Y";
}
这样,您将在第一次匹配时停止搜索,仅更新匹配的行。
评论
\ $ \ begingroup \ $
此解决方案花费了16秒。
\ $ \ endgroup \ $
– HappyCoding
16年2月16日在15:37
\ $ \ begingroup \ $
执行后,matchingRows仅包含那些匹配的行,而不是初始数据集。
\ $ \ endgroup \ $
– HappyCoding
16年2月16日在15:45
#3 楼
一些评论:避免使用内存密集型数据类型
而不是使用DataTable进行dt,而是直接从csv读取,一次只读取一行(readLine()和然后split(',')。这将大大减少您的内存使用量,而不是一次只使用一个时一次加载全部170万行。
排序数据更快br />
按帐号对数据结构进行排序。之后,您可以进行二进制搜索来查找帐号,并在对其进行迭代后将其中断,这将大大减少循环时间。在将数据读入dataStructure之前,您甚至可以使数据库进行排序,甚至更好。
替代方案
您还可以尝试将所有dt加载到临时表中运行dataStructure的同一数据库,然后使用存储过程进行更新。该数据库比使用C#循环可以更有效地执行此更新。
评论
\ $ \ begingroup \ $
-1用于推荐朴素的csv解析
\ $ \ endgroup \ $
–温斯顿·埃韦特(Winston Ewert)
16-2-17的1:23
\ $ \ begingroup \ $
@WinstonEwert您能解释为什么这是一件坏事吗?
\ $ \ endgroup \ $
–约西亚
16-2-17在13:47
\ $ \ begingroup \ $
如果CSV有很多转义符(逗号是文本的一部分,然后将其引号,然后引号需要转义),则拆分可能会出现问题。如果csv数据是“安全的”,那么我认为该建议没有任何不利之处
\ $ \ endgroup \ $
–扎克
16年2月17日在15:04
\ $ \ begingroup \ $
根据数据库的不同,您甚至可以将CSV文件声明为外部表并运行一个UPDATE语句,这将使用优化的查询批量更新所有行,而无需担心IO。
\ $ \ endgroup \ $
– Falco
16年2月17日在17:12
\ $ \ begingroup \ $
如果CSV具有单个转义的单元格,则拆分会给您不正确的结果。当您可以确保所有单元格都不会逃脱时,您就可以摆脱它。但是,对于您一无所知的170万行CSV文件,建议采用幼稚的解析策略,我认为这是个坏建议。
\ $ \ endgroup \ $
–温斯顿·埃韦特(Winston Ewert)
16-2-18的3:42
#4 楼
基于@Johnbot注释正确地知道您正在遍历
dataStructure.Tables["AccountData"]
的所有记录,无论是否找到匹配项。如果break
,您确实应该从此循环中退出acctNum.Equals(queryAcctNum)
。这应该可以大大加快您的任务速度(至少可以找到数据)。 另一个可能的增强功能是对表的行进行排序,并存储内部循环的最后找到的“索引”,以将其用作下一次迭代的开始。这需要将循环从
foreach
更改为正常的for
循环,这反而可能会更快。 假设
dt
的第一列也命名为“ AccountNumber”,这将加快处理过程。 dt.DefaultView.Sort = "AccountNumber";
var accountDataTable = dataStructure.Tables["AccountData"];
accountDataTable.DefaultView.Sort = "AccountNumber";
int numberOfAccountDataRows = accountDataTable.Rows.Count;
int currentIndex = 0;
foreach (DataRow row in dt.Rows)
{
var acctNum = row[0].ToString().Replace("\"", "");
for(int i = currentIndex; i < numberOfAccountDataRows; i++)
{
var queryAcctNum = accountDataTable.Rows[i][0].ToString();
if (acctNum.Equals(queryAcctNum))
{
row[12] = "Y";
currentIndex = i;
break;
}
}
}
#5 楼
免责声明:我没有用C#编写代码,因此您可能会感到失望。如果有人愿意将此移植到C#,请这样做,这可能会对OP有所帮助。比较两个包含或排除的数据集,只要它们经过排序就可以完成。
该算法接近“合并排序”的合并过程;以伪代码
left = /*sorted stream 1*/
right = /*sorted stream 2*/
while not left.empty() and not right.empty():
if left.current() == right.current():
print "Common item", left.current()
left.moveToNext()
right.moveToNext()
continue
if left.current() < right.current():
print "Left specific item", left.current()
left.moveToNext()
continue
if left.current() > right.current():
print "Right specific item", right.current()
right.moveToNext()
continue
while not left.empty()
print "Left specific item", left.current()
left.moveToNext()
while not right.empty()
print "Right specific item", right.current()
right.moveToNext()
优点:
恒定内存
快速
缺点:
要求两个流都需要预先排序
如果您可以轻松地获得两个流的排序顺序,那么很可能会失败。 >
如果必须准备一个数据集(对它进行排序),那么对于非常大的数据集(几乎不能容纳在内存中)还是有好处的。一个不进行排序,然后使用另一种解决方案(将一个较小的解决方案拖入一个哈希图中,并在迭代较大的解决方案时进行查找)。
#6 楼
感谢@RobH的回答,并在其上进行了微代码优化,例如:在String.Replace
中使用字符串常量使用char数据类型而不是字符串数据类型
在检索自身时已添加
knownAccountNumbers
值var knownAccountNumbers = new HashSet<string>();
//// If AccountData DataTable used only for condition then
//// Delete Code Related to AccountData DataTable and Use knownAccountNumbers HashSet alone.
DataTable accountDt = dataStructure.Tables["AccountData"];
while (readFile.Read())
{
string accountNumber = readFile.GetString(0).Trim();
knownAccountNumbers.Add(accountNumber);
DataRow dr = accountDt.NewRow();
dr[AppConst.AccountNumber] = accountNumber;
dr[AppConst.LegalStatus] = readFile.GetString(1);
accountDt.Rows.Add(dr);
}
/* ----------------------------------------------------------------------------------------------- */
foreach (DataRow row in dt.Rows)
{
//// For single char string replace use data type as char instead of string.
//// Gives more perfromance
//// var accountNumber = row[0].ToString().Replace(AppConst.BackSlashChar, AppConst.EmptyChar);
var accountNumber = row[0].ToString().Replace(AppConst.BackSlash, string.Empty);
//// Use Constant String will not create unnecessary memory allocation.
row[12] = knownAccountNumbers.Contains(accountNumber) ? AppConst.Yes : AppConst.No;
}
/*------------------------------------------------------------------------------------------------ */
public static class AppConst
{
public const string Yes = "Y";
public const string No = "N";
//// public const char BackSlashChar = '\"';
//// public const char EmptyChar = 'q4312078q';
public const string BackSlash = "\"";
public const string AccountNumber = "AccountNumber";
public const string LegalStatus = "LegalStatus";
}
评论
\ $ \ begingroup \ $
几乎可以肯定,代码的性能优势来自在构建DataTable的同时初始化Hashset。我的答案为所有数据添加了一个额外的循环,以构建您的代码无法执行的数据。
\ $ \ endgroup \ $
– RobH
17年7月5日在11:17
\ $ \ begingroup \ $
@RobH:这只是在答案之上完成的微型代码优化。避免强制转换类型转换,拆箱,选择循环,并且使用字符串常量来减少内存消耗和节省几秒钟的时间....:-0)
\ $ \ endgroup \ $
– Thulasiram
17年7月5日在13:05
\ $ \ begingroup \ $
字符串常量不会对性能造成任何影响-尤其是内存消耗,因为C#会保留字符串文字。我不知道您认为您没有像以前那样拳击。我可以看到的唯一区别是我之前说过的:通过DataTable保存整个迭代。
\ $ \ endgroup \ $
– RobH
17年7月5日在13:23
评论
Console.WriteLine是否在您的真实代码中?您必须运行剖析器才能找到所需的时间,但这是我投入金钱的地方。看起来好像坏了。您不会中断;找到匹配项之后,所有记录都将具有row [12] ==“ N”。
@Johnbot多数民众赞成在一个很好的收获,你应该让这个答案!
为什么首先要在内存中执行此操作?将所有数据倒入数据库,添加适当的索引,用SQL编写要编写的查询,并使数据库优化该查询;这就是它的优点。
我感到惊讶的是,您有耐心等待48小时才能看到需要多长时间。我的意思是,这段代码效率极低,但是这种耐心必须有所作为。