CSDN首页 空间 新闻 论坛 Blog 下载 读书 网摘 搜索 .NET Java 视频 接项目 求职 在线学习 买书 程序员 通知
可用分押宝游戏火热进行中... 专题改版:Java Web 专题
CSDN社区
搜索 收藏 打印 关闭
CSDN社区 >  VC/MFC >  进程/线程/DLL

请教一个多线程CPU性能的问题

楼主X_worm(蒙山大盗)2006-12-31 21:18:51 在 VC/MFC / 进程/线程/DLL 提问

最近一个问题一直解决不了,找不到关键所在,希望各位高手给点建议:  
  我写了一个编码线程,用来独立地对视频图像进行实时编码,为了测试,我将视频图像固定为简单的静止图像序列,我用的是H.264   baseline   profile编码标准,352*288大小,这样,假设在我的C1.7/512M机器上进行一路编码的CPU占用率5%左右,现在,我想同时进行两路编码,也就是互相独立地开两个同样的线程各自编码,于是问题出来了,CPU占用率直线上升,大部分情况下高达40%,当然偶尔也会降到10%左右。  
  这里需要说明的是我的解码线程是很独立的,不需要和其他线程同步,所以不存在同步开销。那为什么单路解码CPU占用不高,两路就高出那么多呢?如果单路5%两路15%也可以理解,但现在却是40%?  
   
  同样的问题,我还测试过我的解码线程,一般地,我开一个线程进行一路352*288大小25帧实时解码,占用CPU不超过2%,当我再增加一路变为2个线程解2路时CPU占用不超过5%,3个时一般不超过8%,但是当4个线程解4路时CPU又直线上升到了30%多?  
   
  到底是哪儿成了瓶颈呢?另外,我将线程中动态内存分配的地方改成开始时一次性创建私有堆分配内存,以避免共享内存访问的问题,但感觉性能影响不大,而且我改为不用堆而全部在堆栈内解码也不能解决上述问题。  
  我的感觉好像是一个进程的CPU占用率一旦达到某个值时再增加一个比较消耗CPU的线程,该进程的CPU占用率就不是量变了,而好像变成质变了,一下子提高了很多。 问题点数:100、回复次数:21Top

1 楼X_worm(蒙山大盗)回复于 2006-12-31 21:22:03 得分 0

来者有分,分不够可以再加,希望大家献计献策!Top

2 楼yjgx007(还不结帖?!听妈妈的话! http://www.geekclaw.com)回复于 2006-12-31 21:32:16 得分 10

建议用多个CPU处理,或者多核处理,  
  在单个CPU下,windows操作系统属于抢占式多任务模式,与实时操作系统不同,当你的一个编码主进程A开多个工作线程B1,B2,B3...Bn,这些工作线程需要消耗更多的CPU时间片,就是B1,B2,B3...Bn交替运行地占用CPU(并非同时运行的,除非使用多个CPU或多核,以达到真正的多线程同时处理)Top

3 楼yjgx007(还不结帖?!听妈妈的话! http://www.geekclaw.com)回复于 2006-12-31 21:43:41 得分 10

如果是工作线程中需要不断地new和delete在堆中分配/释放内存,可以考虑用内存池,或者直接用VirtualAlloc保留一块较大内存,需要时递交,不需要时释放,这个比在堆中分配/释放要快一点。Top

4 楼ProgrameMan(我要学汇编)回复于 2006-12-31 21:47:31 得分 10

怀疑是线程上下文切换导致的,线程越多开销越大效率就越低Top

5 楼yjgx007(还不结帖?!听妈妈的话! http://www.geekclaw.com)回复于 2006-12-31 21:56:43 得分 5

线程的上下文切换时间应该是很短暂的,楼主是不是长   时间CPU资源被大量占用?Top

6 楼X_worm(蒙山大盗)回复于 2006-12-31 22:47:19 得分 0

谢谢楼上各位的解答!  
  不过,用多颗CPU的方案不是我决定的,将来是要由用户决定的。  
  为了快速地操作内存,我在线程一开始就通过CreateHeap()创建了这个线程专用的私有堆并一次性分配好所有内存资源,但仍然不能解决问题,包括全部使用堆栈也不行。  
  线程切换开销是不可避免的,如果我的线程没有很大的计算量,即使进程再创建100个线程,总的CPU占用应该也不会很高吧?  
  我的线程是编码线程,而且是H.264的编码,计算量是很大的,所以CPU占用确实很大,但应该不是1路5%两路就是40%吧。难道都是这样?  
  元旦后我用VC的profile功能统计分析一下,看看到底是哪个函数用去了那么多CPU?  
   
  这个帖子一周内结贴,谢谢各位Top

7 楼ProgrameMan(我要学汇编)回复于 2007-01-01 10:28:22 得分 10

1.   缓存失败?  
        内存访问的代价是相差很大的。在某种特定的RISC体系结构中,如果数据位于数据缓存中,那么数据访问需要一个CPU时钟周期;如果数据位于主存中(缓存失败),那么需要8个时钟周期;如果数据在磁盘中(页面错误),那么需要400,000个时钟周期,尽管确切的时钟周期数量可能不同,但不同的处理器体系结构在总的关系上是相同的;缓存成功、缓存失败和页面错误之间的速度(消耗CPU时钟)差别是不同数量级的差别。因此,楼主的问题有可能是多个线程处理数据时(而且每个线程对内存的访问非常频繁),产生了大量的缓存失败甚至是页面错误,这点可以通过性能监视器来分析。  
   
  2.   上下文切换?  
        上下文切换和缓存之间的交互对程序性能(消耗CPU时钟)的影响常常是最为有害的。结构和缓存类型对系统性能的这种特性产生显著的影响,缓存可以根据访问他们所使用的地址类型分成两类。虚拟编址缓存使用来自进程虚拟地址空间的地址访问。虚拟地址与进程无关。这就是说,系统中的每个进程都有可能使用虚拟地址100,但是虚拟地址100的物理定位将可能是几百个不同的物理地址。纯粹以来与虚拟地址的缓存在每次上下文切换时都需要刷新/失效。这将意味着每次上下文切换之后都需要重建缓存上下文。(最廉价的上下文重建需要至少100个时钟周期)  
   
   
  我想通过对上下文的简单阐述,大家还会说上下文的切换是很快的吗?   因为一个上下文切换至少要涉及缓存重建,它是非常昂贵的。  
   
  我针对楼主出现的现象,简单给出一个伪表达式,仅供参考:  
   
  1线程消耗的CPU时钟数   =   100000   CPU时钟数   +   (上下文切换(按最小算)   100CPU时钟数   *   100次)   =   110000  
  2线程消耗的CPU时钟数   =   100000   *   2   +   (100CPU时钟数   *   200次)   =   220000  
  3线程消耗的CPU时钟数   =   100000   *   3   +   (100CPU时钟数   *   300次)   =   330000  
   
  上面我的表达式是理想化的,大家可以想象一下,每次进行上下文切换时如果出现大量的缓存失败甚至是页面错误将会是什么结果。  
   
   
  以上所说如有错误请大家批评指正。   谢谢  
   
   
  Top

8 楼ProgrameMan(我要学汇编)回复于 2007-01-01 10:38:38 得分 10

WIN32靠线程的优先级(达到抢占式多任务的目的)及分配给线程的CPU时间来调度线程。WIN32本身的许多应用程序也利用了多线程的特性,如任务管理器等。  
   
    本质而言,一个处理器同一时刻只能执行一个线程("微观串行")。WIN32多任务机制使得CPU好像在同时处理多个任务一样,实现了"宏观并行"。其多线程调度的机制为:  
   
    (1)运行一个线程,直到被中断或线程必须等待到某个资源可用;  
   
    (2)保存当前执行线程的描述表(上下文);  
   
    (3)装入下一执行线程的描述表(上下文);  
   
    (4)若存在等待被执行的线程,则重复上述过程。Top

9 楼DentistryDoctor(不在无聊中无奈,就在沉默中变态)回复于 2007-01-01 12:38:55 得分 10

多媒体方面的编解码,一般对(除CPU)系统资源比较敏感,但应该可以优化的。Top

10 楼yjgx007(还不结帖?!听妈妈的话! http://www.geekclaw.com)回复于 2007-01-01 13:02:48 得分 10

我写了一个用于测试上下文切换的小程序,你可以打开下面代码中一个注释段,   为测试当磁盘交换时,可能需要重建线程上下文?可能这并不是一个好的方式,这主要想测试下你所说的:  
  “如果数据在磁盘中(页面错误),那么需要400,000个时钟周期,尽管确切的时钟周期数量可能不同,但不同的处理器体系结构在总的关系上是相同的;”  
  如果打开这个注释段,每次切换的最大的消耗时间也不会超过400ms  
  我的机器配置   CPU:celeron   1.3G   RAM:641MB  
   
  我写的console程序,详细下载在:http://www.geekclaw.com/opensrc/SwitchContext.rar  
   
  #include   "stdafx.h"  
  #include   <afxwin.h>  
  #include   <winnt.h>  
  #include   <process.h>  
   
  HANDLE   hCompetition   =   CreateEvent(NULL,   TRUE,   TRUE,   "competition");  
  LARGE_INTEGER   g_lastPerformanceCount;  
  LARGE_INTEGER   g_frequency;  
  DWORD   g_lastThreadId;  
   
  #define   MAX_NUMBER_OF_THREADS 20  
  //   If   the   above   defined   MAX_NUMBER_OF_THREADS   is   more   bigger,   you   should   properly  
  //   reduce   the   number   of   the   pages   as   below   for   preventing   freezing   with   your   computer.  
  #define   MAX_EXCHANGE_PAGES 10000  
   
  void   __cdecl   threadProc(LPVOID   pvoid)  
  {  
  SYSTEM_INFO   stSysInfo;  
  GetSystemInfo(&stSysInfo);  
  DWORD   dwProcessorPageSize   =   stSysInfo.dwPageSize;  
   
  int   i   =   100;  
  while   (i--)  
  {  
  WaitForSingleObject(hCompetition,   INFINITE);  
  //   Here's   switching   to   current   context   of   the   thread  
  if   (g_lastThreadId   !=   GetCurrentThreadId())  
  {  
  CString   strFmt;  
  LARGE_INTEGER   g_curPerformanceCount;  
  ::QueryPerformanceCounter(&g_curPerformanceCount);  
  strFmt.Format("###   The   thread   context   from   %u   to   %   u   ###   Elapsed   time   to   switch   context:   %I64u   |   %lfms\n",    
  g_lastThreadId,   GetCurrentThreadId(),    
  (g_curPerformanceCount.QuadPart   -   g_lastPerformanceCount.QuadPart),    
  (double)(g_curPerformanceCount.QuadPart   -   g_lastPerformanceCount.QuadPart)/g_frequency.QuadPart*1000);  
  // OutputDebugString(strFmt);  
  printf(strFmt);  
  }  
  ResetEvent(hCompetition);  
   
  //   There   is   exchanging   data   between   physic   memory   and   disk   cache.  
  /*   Comment   out   it   for   testing   that   there   may   need   to   rebuild   the   context   of   each   thread.  
    *   of   course,   it   may   not   good   solution   for   this,   let   me   know   if   you   have   best   solution.  
    */  
  /*  
  int   nSize   =   MAX_EXCHANGE_PAGES*dwProcessorPageSize;    
  BYTE*   pBuff   =   (BYTE*)VirtualAlloc(NULL,   nSize,   MEM_RESERVE|MEM_COMMIT,   PAGE_READWRITE);  
  if   (pBuff   !=   NULL)  
  {  
  ZeroMemory(pBuff,   nSize);  
  VirtualFree(pBuff,   0,   MEM_RELEASE);  
  }*/  
   
  //   Reset   last   performance   count  
  ::QueryPerformanceCounter(&g_lastPerformanceCount);  
  g_lastThreadId   =   GetCurrentThreadId();  
  SetEvent(hCompetition);  
  }  
  }  
   
  int   main(int   argc,   char*   argv[])  
  {  
  HANDLE   hThreadsArray[MAX_NUMBER_OF_THREADS];  
  //   Before   starting   threads,   we   need   to   initialize    
  //   the   high-resolution   frequency   and   last   count   assoicated   with   hardware.  
  QueryPerformanceFrequency(&g_frequency);  
  QueryPerformanceCounter(&g_lastPerformanceCount);  
  g_lastThreadId   =   GetCurrentThreadId();  
  for   (int   i   =   0;   i   <   MAX_NUMBER_OF_THREADS;   ++i)  
  {  
  HANDLE   hThread   =   (HANDLE)::_beginthread(threadProc,   0,   NULL);  
  hThreadsArray[i]   =   hThread;  
  }  
   
  DWORD   dwRet;  
  dwRet   =   ::WaitForMultipleObjects(MAX_NUMBER_OF_THREADS,   hThreadsArray,   TRUE,   INFINITE);  
   
  MessageBox(NULL,   "End   the   testing!",   "SwitchContext",   MB_OK);  
   
  return   0;  
  }Top

11 楼X_worm(蒙山大盗)回复于 2007-01-01 20:33:01 得分 0

谢谢楼上各位,尤其是ProgrameMan(我要学汇编)和yjgx007(听妈妈的话,向两星挺进!   http://www.geekclaw.com),你们所说的对我很有帮助。  
  根据你们所言,我的问题很可能出在缓存和上下文切换上。  
  不过,有一点不明白,对于线程的上下文切换,理论上讲,不是只有我的线程才有上下文切换的,所有的线程当被分配到时间片时应该都有这个上下文切换,所以上下文切换的开销对所有的线程都是一样的吧?不管开销是大还是小?难道会不同的线程,上下文切换的开销会显著地差距很大?这一点我还不是很明白。我现在还是觉得上下文切换的开销对所有都是一样的。  
  yjgx007说:“如果打开这个注释段,每次切换的最大的消耗时间也不会超过400ms”,这个400ms的开销倒确实太大了,因为今天我不在公司,所以没法测试上面的程序。  
  缓存的问题我现在觉得倒是值得我重点关注一下,那么,又有哪些需要注意的呢?是否,我将我的线程使用的所有内存都强制提交到物理内存,不允许被交换到磁盘上,这样对性能会有所改善呢?而对于CPU自身的高速缓存,我又能做些什么改善呢?  
  还有,如果我对上下文切换的理解有误,又如何纠正并改善呢?  
  再次感谢各位!  
  Top

12 楼yjgx007(还不结帖?!听妈妈的话! http://www.geekclaw.com)回复于 2007-01-01 21:56:56 得分 5

在物理内存中缓存肯定是比在磁盘和内存中来回捣腾效率高的.Top

13 楼ProgrameMan(我要学汇编)回复于 2007-01-02 10:19:57 得分 15

不过,有一点不明白,对于线程的上下文切换,理论上讲,不是只有我的线程才有上下文切换的,所有的线程当被分配到时间片时应该都有这个上下文切换,所以上下文切换的开销对所有的线程都是一样的吧?不管开销是大还是小?难道会不同的线程,上下文切换的开销会显著地差距很大?这一点我还不是很明白。我现在还是觉得上下文切换的开销对所有都是一样的。  
   
  ************************************************************************************  
   
  应该是不一样的,我举一个例子你可能就明白了,例如,系统中运行了你的2个线程,其他的线程10个,这10个线程的代码中对内存的访问非常少,那么在这10个线程之间的上下文切换相对就是廉价的因为缓存失败或页面错误的机率就会低很多,但是当这些线程与你的线程进行上下文切换时就会存在  
  比较大的开销。  
   
   
  缓存的问题我现在觉得倒是值得我重点关注一下,那么,又有哪些需要注意的呢?是否,我将我的线程使用的所有内存都强制提交到物理内存,不允许被交换到磁盘上,这样对性能会有所改善呢?而对于CPU自身的高速缓存,我又能做些什么改善呢?  
   
  *******************************************************************************  
  个人观点:  
   
  1.   如你所说,提交到系统的物理内存是会改善页面错误的,  
  2.   在数据结构的设计上要使线程都要频繁访问的数据集中在连续的内存空间,例如:  
        你的线程中要使用两个全局类的两个实例  
   
          class   Test  
          {  
              public:  
                      int   i;  
                      char   buffer[1024];  
          }  
   
          class   Test1  
          {  
                public:  
                          bool   k;  
                          int   j;  
                       
            }  
   
          假设你的线程重要频繁的访问   Test   中的   i   和   Test1中的   j   ,而且往往是需要连续的访问  
          例如:         TestObj.i++;  
                            Test1Obj.j++;  
   
          那么我的建议是把   i   和   j   放到同一个   抽象的类中去使用,因为这样可以降低缓存失败的能性,当然这样做可能和   OO有些冲突,但我个人认为在性能关键的场合有时候是需要权衡的。  
          以上说的比较乱。对付看吧   呵呵  
   
   
   
   
   
   
   
  Top

14 楼ProgrameMan(我要学汇编)回复于 2007-01-02 10:22:45 得分 5

3.   就是策率了,例如缓式计算、延迟创建等等,都输入程序性能优化的方面(当然满足你的要求)。Top

15 楼X_worm(蒙山大盗)回复于 2007-01-03 13:01:28 得分 0

谢谢各位,现在看来,我需要关注的重点一是缓存失败,二是线程切换开销。Top

16 楼Analyst()回复于 2007-01-04 12:12:00 得分 0

主要问题集中在cache上,线程切换不会是主要问题。  
  一次cache失效的代价是几十到上百时钟周期,而线程切换代价是数千时钟周期,两者不在一个数量级上。并且两个解码线程是在两个核上跑,操作系统有线程亲缘性优化,这两个解码线程运行的核是不会被调换的,也就不会涉及线程上下文切换,至于其他非活动线程,被激活频率很低,可以忽略。  
  你的解码线程主要处理的是流数据,流数据的特点是要操作大量数据,这会对cache造成一定污染,处理不当会造成cache使用效率降低。但是CPU   cache对程序员来说还是透明的,尽管SSE以后的指令集提供了几条cache操作指令,但是针对cache的优化手段目前还是比较有限的。存储系统的架构还有待进一步的改进。  
  另外还有一点忘了说,CPU占用率的测量并不是很准确的,有时候结果会有很大出入,比较精确的测量还是用CPU周期测量,建议用专门的profile工具或者自己插入profile代码来得到精确的测量。  
  Top

17 楼wanglovec(阳光飞舞)回复于 2007-01-04 15:22:45 得分 0

MARKTop

18 楼shgmail()回复于 2007-01-04 16:06:44 得分 0

顶一下Top

19 楼ren970122(笨奔)回复于 2007-01-04 18:21:48 得分 0

都是大师之言.Top

20 楼argenCHN(【夷不谋夏,胡不乱华】)回复于 2007-01-05 00:31:34 得分 0

upTop

21 楼yyzhao21()回复于 2007-01-05 08:58:54 得分 0

markTop

相关问题

关键词

得分解答快速导航

  • 帖主:X_worm
  • yjgx007
  • yjgx007
  • ProgrameMan
  • yjgx007
  • ProgrameMan
  • ProgrameMan
  • DentistryDoctor
  • yjgx007
  • yjgx007
  • ProgrameMan
  • ProgrameMan

相关链接

  • Visual C++类图书
  • Visual C++类源码下载

广告也精彩

反馈

请通过下述方式给我们反馈
反馈
提问
网站简介|广告服务|VIP资费标准|银行汇款帐号|网站地图|帮助|联系方式|诚聘英才|English|问题报告
世纪乐知(北京)网络技术有限公司 版权所有, 京 ICP 证 020026 号
北京创新乐知广告有限公司 提供技术支持
Copyright © 2000-2007, CSDN.NET, All Rights Reserved
GongshangLogo