循环数据集的每一行和相同的数据集列表形式之间会有什么性能差异吗



我需要循环数据集的每一行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/

最新更新