我需要循环数据集的每一行100k次。
此数据集包含1个主键和另一个字符串列。数据集有600k行。
所以现在我像这个一样循环
for (int i = 0; i < dsProductNameInfo.Tables[0].Rows.Count; i++)
{
for (int k = 0; k < dsFull.Tables[0].Rows.Count; k++)
{
}
}
现在dsProductNameInfo有100k行,dsFull则有600k行。我应该将dsFull转换为KeyValuePaired字符串列表并循环,否则不会有任何速度差异。
什么解决方案最快?
谢谢。
C#4.0 WPF应用程序
在您提到的确切场景中,性能将是相同的,只是转换到列表需要一些时间,并导致列表方法较慢。你可以通过编写一个单元测试并计时来很容易地找到答案
我认为最好这样做:
// create a class for each type of object you're going to be dealing with
public class ProductNameInformation { ... }
public class Product { ... }
// load a list from a SqlDataReader (much faster than loading a DataSet)
List<Product> products = GetProductsUsingSqlDataReader(); // don't actually call it that :)
// The only thing I can think of where DataSets are better is indexing certain columns.
// So if you have indices, just emulate them with a hashtable:
Dictionary<string, Product> index1 = products.ToDictionary( ... );
以下是对SqlDataReader和ToDictionary概念的引用,您可能熟悉也可能不熟悉。
真正的问题是,为什么不在数据库层进行这种繁重的处理?SQL服务器针对这类工作进行了更多的优化。此外,你可能不必实际这样做,为什么不发布原始问题,也许我们可以帮助你更深入地优化?
HTH
可能有很多事情可以优化,而与循环无关。例如,减少迭代次数会在压力下产生很大的效果——内部循环的主体被执行100k*600k次,因此消除外部循环的一次迭代将消除内部循环的600k次迭代(或者,如果更容易从内部循环中消除迭代,则可以切换内部和外部循环)
在任何情况下都可以做的一件事是为每个表只索引一次:
var productNameInfoRows = dsProductNameInfo.Tables[0].Rows
var productInfoCount = productNameInfoRows.Count;
var fullRows = dsFull.Tables[0].Rows;
var fullCount = fullRows.Count;
for (int i = 0; i < productInfoCount; i++)
{
for (int k = 0; k < fullCount; k++)
{
}
}
在循环中,您可以使用productNameInfoRows[i]
和FullRows[k]
到达行,这比使用长手更快。我猜优化主体可能会比循环集合的方式获得更多好处。当然,除非你已经分析了代码,并发现实际的循环是瓶颈
编辑阅读您对Marc关于您正在努力实现的目标的评论后。下面我们来看看你是如何做到这一点的。值得注意的是,下面的算法是概率性的。也就是说,当两个单词被视为相等而不是实际相等时,有一个1:2的^32。然而,这比比较字符串快得多。代码假定第一列是您正在比较的列。
//store all the values that will not change through the execution for faster access
var productNameInfoRows = dsProductNameInfo.Tables[0].Rows;
var fullRows = dsFull.Tables[0].Rows;
var productInfoCount = productNameInfoRows.Count;
var fullCount = fullRows.Count;
var full = new List<int[]>(fullCount);
for (int i = 0; i < productInfoCount; i++){
//we're going to compare has codes and not strings
var prd = productNameInfoRows[i][0].ToString().Split(';')
.Select(s => s.GetHashCode()).OrderBy(t=>t).ToArray();
for (int k = 0; k < fullCount; k++){
//caches the calculation for all subsequent oterations of the outer loop
if (i == 0) {
full.Add(fullRows[k][0].ToString().Split(';')
.Select(s => s.GetHashCode()).OrderBy(t=>t).ToArray());
}
var fl = full[k];
var count = 0;
for(var j = 0;j<fl.Length;j++){
var f = fl[j];
//the values are sorted so we can exit early
for(var m = 0;m<prd.Length && prd[m] <= f;m++){
count += prd[m] == f ? 1 : 0;
}
}
if((double)(fl.Length + prd.Length)/count >= 0.6){
//there's a match
}
}
}
编辑您的评论促使我再次尝试。下面的代码可能会有更少的迭代。可能是因为它取决于匹配的数量和唯一单词的数量。许多独特的单词和每个单词的大量匹配(每列需要大量单词)可能会产生更多的迭代。然而,在假设每一行的单词很少的情况下,这应该会产生更少的迭代。您的代码具有NM的复杂性,这具有N+M+(匹配productInfoMatches*fullMatches)。换句话说,后者必须接近99999*600k才能比您的有更多的迭代
//store all the values that will not change through the execution for faster access
var productNameInfoRows = dsProductNameInfo.Tables[0].Rows;
var fullRows = dsFull.Tables[0].Rows;
var productInfoCount = productNameInfoRows.Count;
var fullCount = fullRows.Count;
//Create a list of the words from the product info
var lists = new Dictionary<int, Tuple<List<int>, List<int>>>(productInfoCount*3);
for(var i = 0;i<productInfoCount;i++){
foreach (var token in productNameInfoRows[i][0].ToString().Split(';')
.Select(p => p.GetHashCode())){
if (!lists.ContainsKey(token)){
lists.Add(token, Tuple.Create(new List<int>(), new List<int>()));
}
lists[token].Item1.Add(i);
}
}
//Pair words from full with those from productinfo
for(var i = 0;i<fullCount;i++){
foreach (var token in fullRows[i][0].ToString().Split(';')
.Select(p => p.GetHashCode())){
if (lists.ContainsKey(token)){
lists[token].Item2.Add(i);
}
}
}
//Count all matches for each pair of rows
var counts = new Dictionary<int, Dictionary<int, int>>();
foreach(var key in lists.Keys){
foreach(var p in lists[key].Item1){
if(!counts.ContainsKey(p)){
counts.Add(p,new Dictionary<int, int>());
}
foreach(var f in lists[key].Item2){
var dic = counts[p];
if(!dic.ContainsKey(f)){
dic.Add(f,0);
}
dic[f]++;
}
}
}
如果性能是关键因素,那么我建议尝试一个结构数组;这具有最小的间接性(DataSet/DataTable具有相当多的间接性)。你提到KeyValuePair,这是可行的,尽管它可能不一定是我的第一选择。Milismetric说得对,如果您先创建一个DataSet,然后从tht构建一个数组/列表,则会产生开销-然而,即使这样,循环时节省的时间也可能超过构建时间。如果您可以重组负载以完全删除数据集,那就太好了。
我也会仔细观察循环,看看是否有什么可以减少实际需要的工作量;例如,构建字典/分组是否允许更快的查找?排序允许二进制搜索吗?是否可以对任何操作进行单独聚合并在更高级别(更少的行)应用?
如何处理嵌套循环中的数据?
数据集的来源是SQL数据库吗?如果是这样的话,您可能获得的最佳性能将是使用内部联接在SQL中执行计算,并将结果返回到.net.
另一种选择是使用数据集的内置查询方法,这些方法的作用类似于SQL,但在内存中。
如果这两个选项都不合适,那么通过将"完整"数据集检索为DataReader并将其循环作为外循环,可以提高性能。数据集一次将SQL中的所有数据加载到内存中。对于600k行,这将占用大量内存!而DataReader将保持与DB的连接打开,并在读取行时对行进行流式传输。一旦您读取了一行,内存将被垃圾收集器重新使用/回收。
在您对我之前的回答的评论回复中,您说这两个数据集本质上都是字符串列表,每个字符串实际上都是标记的分隔列表。我将首先查看数据库中的csv字符串的规范化。即,拆分CSV,将其添加到标签表中,并通过链接表从产品链接到标签。
然后,您可以很容易地创建一个SQL语句,该语句将根据链接记录而不是字符串进行匹配(这本身就更具性能)。
然后您会遇到的问题是,如果您的子集产品列表需要从.net传递到SQL中,则需要调用SP 10万次。值得庆幸的是,SQL 2008 R2引入了TableTypes。您可以在数据库中定义一个表类型,其中一列用于保存您的产品ID,让SP将其作为输入参数,然后在实际表和表参数之间执行内部联接。。我已经在我自己的项目中使用了这个非常大的数据集,并且性能得到了巨大的提高。
在.net端,您可以创建一个与SQL表类型的结构匹配的DataTable,然后在调用SP时将其作为命令参数传递(一次!)。
本文向您展示了如何同时使用SQL和.net。http://www.mssqltips.com/sqlservertip/2112/table-value-parameters-in-sql-server-2008-and-net-c/