使用.net 的tpl并行计算,主要是从Excel表中读取21万条数据,对满足要求的赋值到Hashtable中,采用并行计算,为什么每次读到4万多条时,提示“没有足够的内存执行该程序”?而在串行计算时,就不会存在这个问题,串行计算大概用1个小时完成。本人笔记本电脑T400双核CPU,内存3G。代码如下,请教高手指教是哪里出了问题:
//ws为Excel表单
int sumlength = 210000;
Hashtable table1 = new Hashtable();
Hashtable table2 = new Hashtable();
Parallel.For(1,sumlength, i=>                
{
    //对每一行,读取Excel表单ws第2列、第3列的数据
    string str1 = ((Excel.Range)ws.Cells[i, 2]).Text.ToString().Trim();
    string str2 = ((Excel.Range)ws.Cells[i, 3]).Text.ToString().Trim();
    //如果满足规则,哈希表中增加数据
  if ((!String.IsNullOrEmpty(str1)) && str1.Contains("在"))                        
   {
     table1.Add(i.ToString(), "1");
   }
  if ((!String.IsNullOrEmpty(str2)) && (!String.Equals(str2, "无")))            
   {
     table2.Add(i.ToString(), "1");
   }
}
);

解决方案 »

  1.   

    其实就算是你把任务分为10个线程,你这个程序不一定会比单线程快。而你分为21万个线程?哪有这样分的?也不能将Parallel滥用到这种“颗粒度过细令人发指”的程度吧!你可以分为11个Parallel任务,每一个负责处理2万条记录,这也许还行。
      

  2.   

    Parallel.For(1,10, x=>                
    {
        for (int j = 0; j < sumlength / 10; j++)
        {
        int i = j * 10 + x;
        //对每一行,读取Excel表单ws第2列、第3列的数据
        string str1 = ((Excel.Range)ws.Cells[i, 2]).Text.ToString().Trim();
        string str2 = ((Excel.Range)ws.Cells[i, 3]).Text.ToString().Trim();
        //如果满足规则,哈希表中增加数据
      if ((!String.IsNullOrEmpty(str1)) && str1.Contains("在"))                        
       {
         table1.Add(i.ToString(), "1");
       }
      if ((!String.IsNullOrEmpty(str2)) && (!String.Equals(str2, "无")))            
       {
         table2.Add(i.ToString(), "1");
       }
        }
    }
      

  3.   

    int i = sumlength * x / 10 + j;
    这样可能快一些。但是就你的代码来说,不好说。Excel的调用可能会被上锁。
      

  4.   

    .net tpl 不是自己在后台自动将数据按照tpl自带的算法分块,并行计算么?还需要人工分好了让.net去执行么?按照我这个例子,并行计算21万条数据,tpl难道不会按照自己的算法在后台自动分成n(2个或10个等等)个线程去执行?请大虾指教!
      

  5.   

    它是自己控制规模的,但是那种控制应该是非常是有限的。比如说你盖10个养猪场每个养猪场最多养200头猪,显然比你盖2000个养猪场的初始化资源要少。当然具体的要你自己测试。这里很难确定。但是毫无疑问地,并发任务划分“过细”以至于一下子就创建21万个任务这恐怕连微软的那个项目组设计这个TP框架时也没有想到要支持你这种需求,他们一定没有把这个作为正规的需求、测试用例来对待过。另外要注意,在并发任务中使用table1、table2,必要时要考虑加锁。甚至你要考虑使用HashTable是否正确。
      

  6.   

    是不是 执行Parallel.ForEach时,TPL会自动将整个集合分成多个不相交的子集,
    而在执行Parallel.For时,TPL不会这样做,需要人工提前划分好?
      

  7.   

    谢谢大虾!
    当时也考虑到线程之间Hashtable存储时冲突的问题,但是加锁的话,计算速度上不去了,除了哈希表,还有其他好的数据存储方式么?或者为每个任务建1个独立的哈希表,最后再合并到一起这种方式?
      

  8.   

    基础知识不扎实自定义数据分区
    parallel.foreach (Partitioner.Create(1, 任务数量n , 任务数n/处理器数+1),...)
    最大并行数, 也可以指定, parallelOption.MaxDegreeOfPara***
    任务调度器,你这个就不需要了
    你的多线程的算法和数据结构, 也不行.
    对处理器缓存Cache也极不友好.慢5倍,都是可能的. 读Excel几行,又去判断字符串,hash, 大量的缓存/指令分支预测失效.[你的代码, 从CPU的缓存,到硬盘的缓存, 所有的一切Cache, 都搞得一塌糊涂.]应该 AAA读取数据.  BBB处理数据.  并行的生产者,消费者
    还有,这四处充满了 代价很高的同步,也是错误的.另外,复杂计算,参照MapReduce 的思路
    map = xxx.asparallel().ToLookUp
    reduce = map.asParallel.xxx.xxx....
    ..还有, toString, 又 trim, 大量的string对象生灭,性能很差的. 
    ------------------------------就你的例子
    很明显,   A ==> data  ==>B  , 有很多IProducerConsumerCollection的可选. 当然,针对这个例子,有更高性能的,自己写的版本.A: 多线程,预先分块, 每个线程块内线性顺序读取 excel 文件. 
    Enqueue ==> 并行无锁的 ConcurrentQueue或者其它的
    (为什么,读取硬盘IO要多线程? 现代机械盘支持NCQ以及SSD,在多线程队列深度更大的情况下,性能更高.有大量数据测试. 关键词: 队列深度 SSD 评测)B:TryDequeue,  各线程,维护自己的 数据,C: 归并B产生的数据
      

  9.   

    并行第一课: Amdahl 定律. 串行代码的比例. (盲目堆硬件, 就是给你一万个处理器,垃圾代码的性能也和单处理器性能一样)充满了高代价的同步, 串行代码. 还有 伪共享,AB乒乓的问题.intel IA32 优化书, 有很多例子. 因为垃圾的数据结构, 造成10倍以上的性能差距------------
    你的 内存不足, 原因是因为 String . 21万数据, 可能临时的string有三四十万.或者更多
      

  10.   


    这个, 是泛泛而谈, 指的是常见的 并行编程的错误. 可能你的代码,没有这个错误,没仔细看你可以参照 MSDN ,  ConcurrentQueue ,或者其它并行容器里面的, 例子.你的题目,可以典型的 生产者,消费者. 并且,通过类似mapReduce的思路, 把同步降到很低最好呢, 还是系统性地学习, 按照科班的培养计划, 本科,硕士,博士等这些的培养套路.
      

  11.   

    我在 9# 后面的 A..B..C, 已经说了,基本的流程.S01: 可以单线程,或者多线程, 简单读取excel ,以行或者单元格为单位, 存储到一个并行无锁队列queue中 == >作为生产者S02: 可以单线程,或者多线程, 从 队列queue中取出一个需要处理的数据.---------这是最基本的. S01, 和 S02 分开了. 
    以后,可能不要excel,换成csv格式的文件, 都可以,只需换写S01高级一点, S02 可以使用类似mapReduce的思路.可以用一个"调度器",根据实际的数据的特性把不同数据分布在不同的线程, 各个线程,独立处理数据,无需同步. 全部完成后,再归并
    还可以进一步重构, 玩出很多花样.