我做了一个通过线程池执行运算的函数
if (dtSampleSource.Rows.Count > 0) {//有等待鉴定的数据 waitSampleCount = dtSampleSource.Rows.Count; foreach (DataRow item in dtSampleSource.Rows) { ThreadPool.QueueUserWorkItem(new WaitCallback(IDWithCompareFunction), item); } }
在笔记本可能因为配置低问题不明显,当我将他部署到机架式服务器后,发现执行效率非常低
服务器配置是2U,8核服务器,等于一共是16核,32个逻辑处理器
我把程序部署后,最多只能利用不到1/10的cpu感觉,主频也很低,即使包括一些其他的程序,cpu使用率也不超过20%,实在不清楚是哪里的问题,线程池是否并不能直接支持多核计算?该怎么办,
IDWithCompareFunction函数中主要就是大量的循环,遍历,二分查找,数组操作,一些linq以及wcf服务调用
先在这里谢谢了
这里感觉最大的可能就是wcf的等待了,其他地方都是cpu密集型的,请求多了cpu消耗就会上去,而wcf的io操作就不是这样了。
其次检查代码中是不是有很多互斥操作,这种也会导致cpu使用率上不来。
已经确定不是wcf,我把wcf调用注释掉了
笔记本最高cpu使用率50%,服务器最高最高25%
@MSky: 那最好贴下完整的代码
@Daniel Cai: 代码比较长,不让提交,IDWithCompareFunction函数超过10万字
@MSky: 两种可能性
1.你机器太强劲了,给的任务就只用这么多资源。
2.你代码中有很多没产出的代码,比如互斥量的使用(lock,waithandle,semaphore,countdownevent....),thread.sleep等
@Daniel Cai: 问题是很慢啊。。。线程池可以同时利用多核cpu么,我现在是把数据放在一个静态数组里面,多线程共享这个静态数组,以前是每次都是通过线程去数据库查询,以前都是95%使用率,我觉得是因为sql能自动使用多核cpu,现在是通过静态数组做二分查询,一个将近1000万数据的数组,感觉cpu完全就不转
@Daniel Cai: 程序里没有主动用任何加锁,sleep
我看资料说如果只是读取静态数组不修改,不会锁吧
@MSky: 针对你的描述,感觉你的需求是个标准的生产者消费者模式,而且你问题中给的代码片段也很类似。
那么有几个问题
1.你如何在生产的数据后通知给消费者的?或者说你给的代码片段在什么情况下会运行
2.你在把数据push到线程池后,生产者在干嘛?
3.委托对应的方法IDWithCompareFunction中,是不是纯粹的cpu计算?有没有写日志啥的?
4.委托对应的方法IDWithCompareFunction中,有没有出现线程间可以同时修改查看同一位置数据的情况?
5.你有没有哪个地方显示设置了线程池的最大线程数?
6.你所谓的静态数组做2分,这块用意是什么?1000万的数据全部堆内存里对后面对象创建也带来了不少压力啊。
7.你是64位程序还是32位?
你的问题的答案:
线程池当然可以用上其他核
@Daniel Cai: 64位的,我这是一个windows服务,通过timer去服务器取数据,计算完把数据扔回去,就这么简单,在没有异常的情况下,没有日志记录和IO操作,我现在唯一怀疑的就是这个将近1000万长度的静态数组,多个线程同时查询这个数组中的数据,会不会互相排斥等待?
@MSky: 如果你是一次性构建好1000万的数据,然后再给后面的消费者处理,不说别的,这里就会导致线程池中的线程处于饥饿状态。你应该中间隔离下,放一个队列,然后直接通过datareader方式拿取到一条往里面扔一条,后面消费者(线程池中的线程)直接竞争去队列拿数据处理。
你所谓的查询数组中的数据,我没太明白,结合你问题中给的代码片段(假设dtSampleSource就是你所谓的1000万数据),我只看到了遍历,没看到什么查询。
@Daniel Cai: 数组就是数据源,我要从这里面查询所有我需要用到的数据,因为是个频繁操作,每次从1000万数据表中查询几十万,效率太低,把所有数据放到数组中,直接通过二分法查询,效率要高20倍至少
private DataTable IDFunction(DataTable dtMatchSource, float[] masses, Model.Identify.Algo cAlgo) { try { bool[] isCCSUsed = new bool[CCSPCount]; DataTable dtMatch = new DataTable();//所有匹配项 dtMatch.Columns.Add("ID"); dtMatch.Columns.Add("Mass", typeof(float)); dtMatch.Columns.Add("Point", typeof(int)); for (int i = 0; i < masses.Length; i++) { CCSMassMatch(ref dtMatch, masses[i], ref isCCSUsed, cAlgo);//一个Mass的匹配集合 } var query = from t in dtMatch.AsEnumerable() group t by new { t1 = t.Field<string>("ID") } into m select new { ID = m.Key.t1, Matches = m.Count(), Points = m.Sum(n => n.Field<int>("Point")) }; if (query.ToList().Count > 0) { var list = query.ToList(); list.ForEach(q => { dtMatchSource.Rows.Add(q.ID, "", "", "", "", "", "", "", 0f, 0f, q.Matches, q.Points); }); } DataRow[] drFilterMatches = dtMatchSource.Select("Points>=" + cAlgo.IdentPointLimit, "Points Desc"); if (drFilterMatches.Length <= 0) { return dtMatchSource; } else { return drFilterMatches.CopyToDataTable(); } } catch (Exception ex) { throw; } }
private void CCSMassMatch(ref DataTable matchSource, float mass,ref bool[] isUsed, Model.Identify.Algo cAlgo) { int LeftLocation = 0;//左边界 int RightLocation = 0;//右边界 int index = 0;//临时游标 int BetweenLocation = 0;//区间内游标 try { float NowMass = -1f;//当前游标对应的mass LeftLocation = 0; RightLocation = CCSPCount - 1; index = 0; BetweenLocation = -1; float error = Convert.ToSingle(cAlgo.Error); float MassL = mass - ((mass * error)); float MassH = mass + ((mass * error)); Hashtable hashtable = new Hashtable(); while ((RightLocation - LeftLocation) > 1)//当前游标位置小于总长度 { /* * while循环内,通过重设左右边界,缩小取值区间 */ index = ((RightLocation - LeftLocation) / 2) + LeftLocation;//临时游标位置 if ((index == RightLocation) | (index == LeftLocation))//当临时游标等于总长度或当前游标 { break; } NowMass = CCSMasses[index];//当前临时游标的mass值 if (NowMass > MassH)//当前mass比右边界大 { RightLocation = index;//设置右边界 } else { if (NowMass < MassL)//当前mass比左边界小 { LeftLocation = index;//设置左边界 continue; } if ((NowMass >= MassL) & (NowMass <= MassH)) { BetweenLocation = index;//处于匹配范围内的游标 break; } } } if (BetweenLocation >= 0) { while ((NowMass >= MassL) & (index > 0))//当前mass大于左区间,游标左移 { index--; NowMass = CCSMasses[index]; } while ((NowMass <= MassH) & (index < CCSPCount)) { if (((NowMass >= MassL) & (NowMass <= MassH)) & !isUsed[index])//当处于区间内并且未使用 { matchSource.Rows.Add(CCSIDS[index], NowMass.ToString(), CCSPoint[index]); //string[] strArray = new string[] { CCSIDS[index], NowMass.ToString() }; //hashtable.Add(hashtable.Count, strArray); isUsed[index] = true; } index++; NowMass = CCSMasses[index]; } } } catch (Exception ex) { throw; } }
@Daniel Cai: CCSMassMatch函数大约要执行100-300次,每次执行都会往dtMatch中追加匹配的数据,最后通过分组查询,统计每个值的出现概率
isused数组是为了标记该位置的元素是否被使用过,被使用过的数据不能多次统计
@Daniel Cai: 这个二分法查询的是一个区间内的所有匹配值,不是一个点的值
假如mass传进来一个3000,那么3000*(1+0.0008)——3000*(1-0.0008)的值都会被查询出来
@Daniel Cai: 我开始逐步测试排除,我通过一个没有意义的代码,测试了,线程池的确可以充分利用所有cpu资源
for (int i = 0; i < 9999; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(test)); }
private void test( object obj) { int num = 0; for (int i = 0; i < 9999999; i++) { num++; } }
@Daniel Cai: 已经确定是我上面给你发的那两个函数满,只要一执行IDFunction,cpu就不会超过55%,如果把IDFunction注释掉,直接调用test函数,cpu可以飙到100%
很可能是因为你的单个IDFunction函数执行得非常快,根本就不是什么耗CPU的操作。就好像杀鸡焉用宰牛刀的道理一样。又比如一个屋子有N盏灯,你一个人在屋子里,开一盏灯就够用了,没必要开所有灯。纯属个人猜测,仅供参考!
@Daniel Cai: 您好,我终于找到问题所在了
for (int i = 0; i < masses.Length; i++) { CCSMassMatch(ref dtMatch, masses[i], ref isCCSUsed, cAlgo);//一个Mass的匹配集合 } var query = from t in dtMatch.AsEnumerable() group t by new { t1 = t.Field<string>("ID") } into m select new { ID = m.Key.t1, Matches = m.Count(), Points = m.Sum(n => n.Field<int>("Point")) }; if (query.ToList().Count > 0) { var list = query.ToList(); list.ForEach(q => { dtMatchSource.Rows.Add(q.ID, "", "", "", "", "", "", "", 0f, 0f, q.Matches, q.Points); }); }
这段代码,CCSMassMatch每次执行都会向dtMatch表追加大量数据,dtMatch的ID列是有重复值的,上面的代码是通过CCSMassMatch不考虑重复ID添加完以后,通过下面的group by ID分组统计count和point列的和,这个dtMatch中数据就有几十万到几百万,由于是一个非常频繁的操作,这样导致频繁的资源回收,从内存上看出,忽高忽低,导致cpu无法满负载运行,下面是我更改后的代码,现在速度提升将近20倍,cpu95-100运转
if (((NowMass >= MassL) & (NowMass <= MassH)) & !isUsed[index])//当处于区间内并且未使用 { DataRow dr = dtIDMatchSource.Rows.Find(CCSIDS[index]); if (dr == null) { dtIDMatchSource.Rows.Add(CCSIDS[index], "", "", "", "", "", "", "", 0f, 0f, 1, CCSPoint[index]); } else { dr["Matches"] = Convert.ToInt32(dr["Matches"]) + 1; dr["Points"] = Convert.ToInt32(dr["Points"]) + CCSPoint[index]; } //matchSource.Rows.Add(CCSIDS[index], NowMass.ToString(), CCSPoint[index]); isUsed[index] = true; } index++; NowMass = CCSMasses[index];
以前dtMatch存的是每条记录,然后再group by,现在我直接判断,如果ID存在,让point+=,matches++,这样dtMatch表的记录比原来缩小了将近100倍
线程池给了个参数设置数量——就像很多服务一样都提供了这个配置参数。为什么要要配置?——因为软件即使可以查询出cpu个,那它不知道cpu的性能,即使它知道性能它也不知道你的主要任务是什么。所以你需要设置这个参数使cpu来达到你的并发量的较好利用率。
比如你将线程池设置很小,那么你可能将看到部分CPU的负载较高。
建议去看下msdn中关于线程池中线程数量设置的描述,5k/core这是默认值,而且也不会建议自己去设置这个,除非有站的住脚的理由,否则设置这个无非是自己给自己找麻烦(你改小了发挥不了性能,你改大了没意义,托管线程一个1M,就不说cpu切换了,就光线程对象就可以给gc带来极大的负担了)。
@Daniel Cai: 别建议,同样一个CPU如果是Math服务,和一般的Http服务你试试看。
@花飘水流兮: 你说的什么东西?