下用程序如何实现利用多核CPU各内核之间通过一起跑程序

2添加评论分享收藏感谢收起Win10系统下多核CPU怎么指定程序在哪个核心运行
Win10系列软件最新版本下载   相信大家现在使用的都是双核以上CPU,多核CPU即有多个核心可以一起工作来提高计算效率,默认这些都是由系统自动分配的。不过通过手动指定的方式,我们也可以指定程序在哪个核心中运行。虽然需要这样使用的情况并不多,有兴趣的朋友还是一样来看看吧。   1、首先打开任务管理器(右键&&任务栏&&启动任务管理器)   2、切换到&详细信息&选卡,在需要设置的进程上单击右键。选择&设置相关性&;   3、在&处理器相关&界面勾选指定CPU ,点击确定即可。(默认是勾选所有处理器的,如果你觉得该程序过于消耗资源,可以只勾选一个)   其实是不是特别简单呢?是的 在Win7/win8系统中也是可以这样操作的,以上操作可以帮助用户合理的使用CPU资源。
软件论坛帖子排行
最新资讯离线随时看
聊天吐槽赢奖品博客分类:
在我接触JavaScript(无论浏览器还是NodeJS)的时间里,总是遇到有朋友有多线程的需求。而在NodeJS方面,有朋友甚至直接说到,NodeJS是单线程的,无法很好的利用多核CPU。
诚然,在前端的浏览器中,由于前端的JavaScript与UI占据同一线程,执行JavaScript确实为UI响应造成了一定程度上的麻烦。但是,除非用到超大的循环语句执行JavaScript,或是用阻塞式的Ajax,或是太过频繁的定时器执行外,JavaScript并没有给前端应用带来明显的问题,所以也很少有朋友抱怨JavaScript是单线程而不能很好利用多核CPU的问题,因为没有因此出现性能瓶颈。
但是,我们可以用Ajax和Web Worker回应这个误解。当Ajax请求发送之后,除非是同步请求,否则其余的JavaScript代码会很快被执行到。在Ajax发送完成,直到接收到响应的这段时间里,这个网络请求并不会阻塞JavaScript的执行,而网络请求已经发生,这是必然的事。那么,答案就很明显了,JavaScript确实是执行在单线程上的,但是,整个Web应用执行的宿主(浏览器)并非以单线程的方式在执行。而Web Worker的产生,就是直接为了解决JavaScript与UI占用同一线程造成的UI响应问题的,它能新开一条线程去执行JavaScript。
同理,NodeJS中的JavaScript也确实是在单线程上执行,但是作为宿主的NodeJS,它本身并非是单线程的,NodeJS在I/O方面有动用到一小部分额外的线程协助实现异步。程序员没有机会直接创建线程,这也是有的同学想当然的认为NodeJS的单线程无法很好的利用多核CPU的原因,他们甚至会说,难以想象由多人一起协作开发一个单线程的程序。
NodeJS 封装了内部的异步实现后,导致程序员无法直接操作线程,也就造成所有的业务逻辑运算都会丢到JavaScript的执行线程上,这也就意味着,在高并发请求的时候,I/O的问题是很好的解决了,但是所有的业务逻辑运算积少成多地都运行在JavaScript线程上,形成了一条拥挤的JavaScript运算线程。NodeJS的弱点在这个时候会暴露出来,单线程执行运算形成的瓶颈,拖慢了I/O的效率。这大概可以算得上是密集运算情况下无法很好利用多核 CPU的缺点。这条拥挤的JavaScript线程,给I/O形成了性能上限。
但是,事情又并非绝对的。回到前端浏览器中,为了解决线程拥挤的情况,Web Worker应运而生。而同样,Node也提供了child_process.fork来创建Node的子进程。在一个Node进程就能很好的解决密集 I/O的情况下,fork出来的其余Node子进程可以当作常驻服务来解决运算阻塞的问题(将运算分发到多个Node子进程中上去,与Apache创建多个子进程类似)。当然child_process/Web Worker的机制永远只能解决单台机器的问题,大的Web应用是不可能一台服务器就能完成所有的请求服务的。拜NodeJS在I/O上的优势,跨OS的多Node之间通信的是不算什么问题的。解决NodeJS的运算密集问题的答案其实也是非常简单的,就是将运算分发到多个CPU上。请参考文章后的multi-node的性能测试,可以看到在多Node进程的情景下,响应请求的速度被大幅度提高(感谢CNode社区的snoopy友情测试)。
在文章的写作过程中,Node最新发布的0.6.0版本,新增了cluster模块。该模块的作用是可以通过fork的方式创建出多个子进程实例,这些实例会自动共享相同的侦听端口。你可以根据当前计算机上的CPU数量来创建相应的实例数,以此达到分发请求,充分利用CPU的目的。详情请参阅。在之前的解决运算密集问题中,工程师需要multi-node这样的库或者其他方案去手动分发请求,在cluster模块的支持下,可以释放掉工程师在解决此问题上的大部分精力。
事件式编程
延续上一节的讨论。我们知道NodeJS/JavaScript具有异步的特性,从NodeJS的API设计中可以看出来,任何涉及I/O的操作,几乎都被设计成事件回调的形式,且大多数的类都继承自EventEmitter。这么做的好处有两个,一个是充分利用无阻塞I/O的特性,提高性能;另一个好处则是封装了底层的线程细节,通过事件消息留出业务的关注点给编程者,从而不用关注多线程编程里牵扯到的诸多技术细节。
从现实的角度而言,事件式编程也更贴合现实。举一个业务场景为例:家庭主妇在家中准备中餐,她需要完成两道菜,一道拌黄瓜,一道西红柿蛋汤。以PHP为例,家庭主妇会先做完拌黄瓜,接着完成西红柿蛋汤,是以顺序/串行执行的。但是现在突然出了一点意外,凉拌黄瓜需要的酱油用光了,需要她儿子出门帮她买酱油回来。那么PHP家庭主妇在叫她儿子出门打酱油的这段时间都是属于等待状态的,直到酱油买回来,才会继续下一道菜的制作。那么,在NodeJS的家庭主妇又会是怎样一个场景呢,很明显,在等待儿子打酱油回来的时间里,她可以暂停凉拌黄瓜的制作,而直接进行西红柿蛋汤的过程,儿子打完酱油回来,继续完成她的凉拌黄瓜。没有浪费掉等待的时间。实例伪代码如下:
var mother = new People();
var child = new People();
child.buySoy(function (soy) {
mother.cook("cucumber", soy);
mother.cook("tomato");
接下来,将上面这段代码转换为基于事件/任务异步模式的代码:
var proxy = new EventProxy();
var mother = new People();
proxy.bind("cook_cucumber", function (soy) {
mother.cook("cucumber", soy);
proxy.bind("cook_tomato", function () {
mother.cook("tomato");
var child = new People();
child.buySoy(function (soy) {
proxy.trigger("cucumber", soy);
proxy.trigger("tomato");
代码量多了很多,但是业务逻辑点都是很清楚的:通过bind方法预定义了cook_cucumber和cook_tomato两个任务。这里的bind方法可以认为是await的消息式实现,需要第一个参数来标识该任务的名字,流程在执行的过程中产生的消息会触发这些任务执行。可以看出,事件式编程中,用户只需要关注它所需要的几个业务事件点就可以,中间的等待都由底层为你调配好了。这里的代码只是举例事件/任务异步模式而用,在简单的场景中,第一段代码即可。做NodeJS的编程,会更感觉是在做现实的业务场景设计和任务调度,没有顺序保证,程序结构更像是一个状态机。
个人觉得在事件式编程中,程序员需要转换一下思维,才能接受和发挥好这种异步/无阻塞的优势。同样,这种事件式编程带来的一个问题就在于业务逻辑是松散和碎片式的。这对习惯了顺序式,Promise式编程的同学而言,接受它是比较痛苦的事情,而且这种散布的业务逻辑对于非一开始就清楚设计的人而言,阅读存在相当大的问题。
我提到事件式编程更贴近于现实生活,是更自然的,所以这种编程风格也导致你的代码跟你的生活一样,是一件复杂的事情。幸运的是,自己的生活要自己去面对,对于一个项目而言,并不需要每个人都去设计整个大业务逻辑,对于架构师而言,业务逻辑是明了的,借助事件式编程带来的业务逻辑松耦合的好处,在设定大框架后,将业务逻辑划分为适当的粒度,对每一个实现业务点的程序员而言,并没有这个痛苦存在。二八原则在这个地方非常有效。
深度嵌套回调问题
JavaScript/NodeJS 对单个异步事件的处理十分容易,但容易出现问题出现的地方是“多个异步事件之间的结果协作”。以NodeJS服务端渲染页面为例,渲染需要数据,模板,本地化资源文件,这三个部分都是要通过异步来获取的,原生代码的写法会导致嵌套,因为只有这样才能保证渲染的时候数据,模板,本地化资源都已经获取到了。但问题是,这三个步骤之间实际是无耦合的,却因为原生代码没有promise的机制,将可以并行执行(充分利用无阻塞I/O)的步骤,变成串行执行的过程,直接降低了性能。代码如下:
var render = function (template, data) {
_.template(template, data);
$.get("template", function (template) { // something
$.get("data", function (data) { // something
$.get("l10n", function (l10n) { // something
render(template, data);
面对这样的代码,许多工程师都表示不爽。这个弱点也形成了对NodeJS推广的一个不大不小的障碍。对于追求性能和维护性的同学,肯定不满足于以上的做法。本人对于JavaScript的事件和回调都略有偏爱,并且认为事件,回调,并行,松耦合是可以达成一致的。以下一段代码是用实现的:
var proxy = new EventProxy();
var render = function (template, data, l10n) {
_.template(template, data);
proxy.assign("template", "data", "l10n", render);
$.get("template", function (template) { // something
proxy.trigger("template", template);
$.get("data", function (data) { // something
proxy.trigger("data", data);
$.get("l10n", function (l10n) { // something
proxy.trigger("l10n", l10n);
代码量看起来比原生实现略多,但是从逻辑而言十分清晰。模板、数据、本地化资源并行获取,性能上的提高不言而喻,assign方法充分利用了事件机制来保证最终结果的正确性。在事件,回调,并行,松耦合几个点上都达到期望的要求。
关于更多EventProxy的细节可参考其。
深度回调问题的延伸
EventProxy解决深度回调的方式完全基于事件机制,这需要建立在事件式编程的认同上,那么必然也存在对事件式编程不认同的同学,而且习惯顺序式,promise式,向其推广bind/trigger模式实在难以被他们接受。和是目前比较成熟的同步式编程的解决方案。可以通过同步式的思维来进行编程,最终执行的代码是通过编译后的目标代码,以此通过工具来协助用户转变思维。
对于优秀的东西,我们不能因为其表面的瑕疵而弃之不用,总会有折衷的方案来满足需求。NodeJS在实时性方面的功效有目共睹,即便会有一些明显的缺点,但是随着一些解决方案的出现,相信没有什么可以挡住其前进的脚步。
附录(多核环境下的并发测试)
服务器环境:
网络环境:内网
压力测试服务器:
服务器系统:Linux 2.6.18
服务器配置:Intel(R) Xeon(TM) CPU 3.40GHz 4 CPUS
NodeJS版本: v0.4.12
客户端测试环境:
发包工具:apache 2.2.19自带的ab测试工具
服务器系统:Linux 2.6.18
服务器配置:Pentium(R) Dual-Core CPU E5800 @ 3.20GHz 2CPUS
单线程Node代码:
var http = require('http');
var server = http.createServer(function (request, response) {
var j = 0;
for (var i = 0; i & 100000; i++) {
j += 2 / 3;
response.end(j + '');
server.listen(8881);
console.log('Server running at http://10.1.10.150:8881/');
四进程Node代码:
var http = require('http');
var server = http.createServer(function (request, response) {
var j = 0;
for (var i = 0; i & 100000; i++) {
j += 2 / 3;
response.end(j + '');
var nodes = require("./lib/multi-node").listen({
port: 8883,
}, server);
console.log('Server running at http://10.1.10.150:8883/');
这里简单介绍一下multi-node这个插件,这个插件就是利用require("child_process").spawn()方法来创建多个子线程。由于浮点计算和字符串拼接都是比较耗CPU的运算,所以这里我们循环10W次,每次对j加上0.66666。最后比较一下,开多子进程node到底比单进程node在CPU密集运算上快多少。
以下是测试结果:
单进程:只开一个node.js进程。
多子进程:开一个node.js进程,并且开3个它的子进程。
3000/30:代表命令 ./ab -c 3000 -t 30 http://10.1.10.150:8888/。3000个客户端,最多发30秒,最多发5W个请求。
RPS:代表每秒处理请求数,并发的主要指标。
TPQ:每个请求处理的时间,单位毫秒。
Fail:代表平均处理失败请求个数。
80% Req:代表80%的请求在多少毫秒内返回。
从结果及图1~3上看:开多个子进程可以显著缓解node.js的CPU利用率不足的情况,提高node.js的CPU密集计算能力。
图1:单个进程的node.js在压力测试下的情况,无法充分利用4核CPU的服务器性能。
图2:多进程node,可以充分利用4核CPU榨干服务器的性能。
图3:多子进程截图,可以看到一共跑了4个进程。
转自 infoQ 田永强
浏览 18035
浏览: 106308 次
来自: 深圳
博主你好, 假如我知道 农历 是 6 16(数据库中存的是这个 ...
lz口中的主力浏览器仅包括IE6吧。测试过几个浏览器啊。。。。 ...
看上去比较简陋,是纯html的表单设计器还是基于某些js框架的 ...
能给分享一下源码么?我想参考下么,万分感谢啊
szwangd ...
源码 参考下 么
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'利用多核多线程进行程序优化
样例程序程序功能:求从1一直到 APPLE_MAX_VALUE () 相加累计的和,并赋值给 apple 的 a 和 b
;求 orange 数据结构中的 a[i]+b[i ] 的和,循环 ORANGE_MAX_VALUE(1000000) 次。说明:
由于样例程序是从实际应用中抽象出来的模型,所以本文不会进行 test.a=test.b= test.b+sum 、中间变量(查找表)等类似的优化。
以下所有程序片断均为部分代码,完整代码请参看本文最下面的附件。清单 1. 样例程序#define ORANGE_MAX_VALUE
#define APPLE_MAX_VALUE
#define MSECOND
struct apple
struct orange
int a[ORANGE_MAX_VALUE];
int b[ORANGE_MAX_VALUE];
int main (int argc, const char * argv[]) {
// insert code here...
struct orange test1;
for(sum=0;sum&APPLE_MAX_VALUE;sum++)
for(index=0;index&ORANGE_MAX_VALUE;index++)
sum += test1.a[index]+test1.b[index];
}K-Best 测量方法在检测程序运行时间这个复杂问题上,将采用 Randal E.Bryant 和 David R. O’Hallaron 提出的 K 次最优测量方法。假设重复的执行一个程序,并纪录 K 次最快的时间,如果发现测量的误差 ε 很小,那么用测量的最快值表示过程的真正执行时间, 称这种方法为“ K 次最优(K-Best)方法”,要求设置三个参数:K:
要求在某个接近最快值范围内的测量值数量。ε
测量值必须多大程度的接近,即测量值按照升序标号 V1, V2, V3,
… , Vi, … ,同时必须满足(1+ ε)Vi &= Vk
在结束测试之前,测量值的最大数量。按照升序的方式维护一个 K 个最快时间的数组,对于每一个新的测量值,如果比当前 K 处的值更快,则用最新的值替换数组中的元素 K ,然后再进行升序排序,持续不断的进行该过程,并满足误差标准,此时就称测量值已经收敛。如果 M 次后,不能满足误差标准,则称为不能收敛。在接下来的所有试验中,采用 K=10,ε=2%,M=200 来获取程序运行时间,同时也对 K 次最优测量方法进行了改进,不是采用最小值来表示程序执行的时间,而是采用 K 次测量值的平均值来表示程序的真正运行时间。由于采用的误差 ε 比较大,在所有试验程序的时间收集过程中,均能收敛,但也能说明问题。为了可移植性,采用 gettimeofday() 来获取系统时钟(system clock)时间,可以精确到微秒。测试环境硬件:联想 Dual-core 双核机器,主频 2.4G,内存 2G软件:Suse Linunx Enterprise 10,内核版本:linux-2.6.16软件优化的三个层次医生治病首先要望闻问切,然后才确定病因,最后再对症下药,如果胡乱医治一通,不死也残废。说起来大家都懂的道理,但在软件优化过程中,往往都喜欢犯这样的错误。不分青红皂白,一上来这里改改,那里改改,其结果往往不如人意。一般将软件优化可分为三个层次:系统层面,应用层面及微架构层面。首先从宏观进行考虑,进行望闻问切,即系统层面的优化,把所有与程序相关的信息收集上来,确定病因。确定病因后,开始从微观上进行优化,即进行应用层面和微架构方面的优化。
系统层面的优化:内存不够,CPU 速度过慢,系统中进程过多等
应用层面的优化:算法优化、并行设计等
微架构层面的优化:分支预测、数据结构优化、指令优化等软件优化可以在应用开发的任一阶段进行,当然越早越好,这样以后的麻烦就会少很多。在实际应用程序中,采用最多的是应用层面的优化,也会采用微架构层面的优化。将某些优化和维护成本进行对比,往往选择的都是后者。如分支预测优化和指令优化,在大型应用程序中,往往采用的比较少,因为维护成本过高。本文将从应用层面和微架构层面,对样例程序进行优化。对于应用层面的优化,将采用多线程和 CPU 亲和力技术;在微架构层面,采用 Cache 优化。并行设计利用并行程序设计模型来设计应用程序,就必须把自己的思维从线性模型中拉出来,重新审视整个处理流程,从头到尾梳理一遍,将能够并行执行的部分识别出来。可以将应用程序看成是众多相互依赖的任务的集合。将应用程序划分成多个独立的任务,并确定这些任务之间的相互依赖关系,这个过程被称为分解(Decomosition)。分解问题的方式主要有三种:任务分解、数据分解和数据流分解。关于这部分的详细资料,请参看参考资料一。仔细分析样例程序,运用任务分解的方法 ,不难发现计算 apple 的值和计算 orange 的值,属于完全不相关的两个操作,因此可以并行。改造后的两线程程序:清单 2. 两线程程序void* add(void* x)
for(sum=0;sum&APPLE_MAX_VALUE;sum++)
((struct apple *)x)-&a +=
((struct apple *)x)-&b +=
return NULL;
int main (int argc, const char * argv[]) {
// insert code here...
struct orange test1={{0},{0}};
pthread_t ThreadA;
pthread_create(&ThreadA,NULL,add,&test);
for(index=0;index&ORANGE_MAX_VALUE;index++)
sum += test1.a[index]+test1.b[index];
pthread_join(ThreadA,NULL);
}更甚一步,通过数据分解的方法,还可以发现,计算 apple 的值可以分解为两个线程,一个用于计算 apple a 的值,另外一个线程用于计算 apple b 的值(说明:本方案抽象于实际的应用程序)。但两个线程存在同时访问 apple 的可能性,所以需要加锁访问该数据结构。改造后的三线程程序如下:清单 3. 三线程程序struct apple
pthread_rwlock_t rwL
void* addx(void* x)
pthread_rwlock_wrlock(&((struct apple *)x)-&rwLock);
for(sum=0;sum&APPLE_MAX_VALUE;sum++)
((struct apple *)x)-&a +=
pthread_rwlock_unlock(&((struct apple *)x)-&rwLock);
return NULL;
void* addy(void* y)
pthread_rwlock_wrlock(&((struct apple *)y)-&rwLock);
for(sum=0;sum&APPLE_MAX_VALUE;sum++)
((struct apple *)y)-&b +=
pthread_rwlock_unlock(&((struct apple *)y)-&rwLock);
return NULL;
int main (int argc, const char * argv[]) {
// insert code here...
struct orange test1={{0},{0}};
pthread_t ThreadA,ThreadB;
pthread_create(&ThreadA,NULL,addx,&test);
pthread_create(&ThreadB,NULL,addy,&test);
for(index=0;index&ORANGE_MAX_VALUE;index++)
sum+=test1.a[index]+test1.b[index];
pthread_join(ThreadA,NULL);
pthread_join(ThreadB,NULL);
}这样改造后,真的能达到我们想要的效果吗?通过 K-Best 测量方法,其结果让我们大失所望,如下图:图 1. 单线程与多线程耗时对比图为什么多线程会比单线程更耗时呢?其原因就在于,线程启停以及线程上下文切换都会引起额外的开销,所以消耗的时间比单线程多。为什么加锁后的三线程比两线程还慢呢?其原因也很简单,那把读写锁就是罪魁祸首。通过 Thread Viewer 也可以印证刚才的结果,实际情况并不是并行执行,反而成了串行执行,如图2:图 2. 通过 Viewer 观察三线程运行情况其中最下面那个线程是主线程,一个是 addx 线程,另外一个是 addy 线程,从图中不难看出,其他两个线程为串行执行。通过数据分解来划分多线程,还存在另外一种方式,一个线程计算从1到
APPLE_MAX_VALUE/2 的值,另外一个线程计算从 APPLE_MAX_VALUE/2+1 到
APPLE_MAX_VALUE 的值,但本文会弃用这种模型,有兴趣的读者可以试一试。在采用多线程方法设计程序时,如果产生的额外开销大于线程的工作任务,就没有并行的必要。线程并不是越多越好,软件线程的数量尽量能与硬件线程的数量相匹配。最好根据实际的需要,通过不断的调优,来确定线程数量的最佳值。加锁与不加锁针对加锁的三线程方案,由于两个线程访问的是 apple 的不同元素,根本没有加锁的必要,所以修改 apple 的数据结构(删除读写锁代码),通过不加锁来提高性能。测试结果如下:图 3. 加锁与不加锁耗时对比图其结果再一次大跌眼镜,可能有些人就会越来越糊涂了,怎么不加锁的效率反而更低呢?将在针对 Cache 的优化一节中细细分析其具体原因。在实际测试过程中,不加锁的三线程方案非常不稳定,有时所花费的时间相差4倍多。要提高并行程序的性能,在设计时就需要在较少同步和较多同步之间寻求折中。同步太少会导致错误的结果,同步太多又会导致效率过低。尽量使用私有锁,降低锁的粒度。无锁设计既有优点也有缺点,无锁方案能充分提高效率,但使得设计更加复杂,维护操作困难,不得不借助其他机制来保证程序的正确性。针对 Cache 的优化在串行程序设计过程中,为了节约带宽或者存储空间,比较直接的方法,就是对数据结构做一些针对性的设计,将数据压缩 (pack) 的更紧凑,减少数据的移动,以此来提高程序的性能。但在多核多线程程序中,这种方法往往有时会适得其反。数据不仅在执行核和存储器之间移动,还会在执行核之间传输。根据数据相关性,其中有两种读写模式会涉及到数据的移动:写后读和写后写 ,因为这两种模式会引发数据的竞争,表面上是并行执行,但实际只能串行执行,进而影响到性能。处理器交换的最小单元是 cache 行,或称 cache 块。在多核体系中,对于不共享 cache 的架构来说,两个独立的 cache 在需要读取同一 cache 行时,会共享该 cache 行,如果在其中一个 cache 中,该 cache 行被写入,而在另一个 cache 中该 cache 行被读取,那么即使读写的地址不相交,也需要在这两个 cache 之间移动数据,这就被称为 cache 伪共享,导致执行核必须在存储总线上来回传递这个 cache 行,这种现象被称为“乒乓效应”。同样地,当两个线程写入同一个 cache 的不同部分时,也会互相竞争该 cache 行,也就是写后写的问题。上文曾提到,不加锁的方案反而比加锁的方案更慢,就是互相竞争 cache 的原因。在 X86 机器上,某些处理器的一个 cache 行是64字节,具体可以参看 Intel 的参考手册。既然不加锁三线程方案的瓶颈在于 cache,那么让 apple 的两个成员 a 和 b 位于不同的 cache 行中,效率会有所提高吗?修改后的代码片断如下:清单 4. 针对Cache的优化struct apple
char c[128];
/*32,64,128*/
};测量结果如下图所示:图 4. 增加 Cache 时间耗时对比图小小的一行代码,尽然带来了如此高的收益,不难看出,我们是用空间来换时间。当然读者也可以采用更简便的方法: __attribute__((__aligned__(L1_CACHE_BYTES))) 来确定 cache 的大小。如果对加锁三线程方案中的 apple 数据结构也增加一行类似功能的代码,效率也是否会提升呢?性能不会有所提升,其原因是加锁的三线程方案效率低下的原因不是 Cache 失效造成的,而是那把锁。在多核和多线程程序设计过程中,要全盘考虑多个线程的访存需求,不要单独考虑一个线程的需求。在选择并行任务分解方法时,要综合考虑访存带宽和竞争问题,将不同处理器和不同线程使用的数据放在不同的 Cache 行中,将只读数据和可写数据分离开。CPU 亲和力CPU 亲和力可分为两大类:软亲和力和硬亲和力。Linux 内核进程调度器天生就具有被称为 CPU 软亲和力(affinity) 的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。但不代表不会进行小范围的迁移。CPU 硬亲和力是指进程固定在某个处理器上运行,而不是在不同的处理器之间进行频繁的迁移。这样不仅改善了程序的性能,还提高了程序的可靠性。从以上不难看出,在某种程度上硬亲和力比软亲和力具有一定的优势。但在内核开发者不断的努力下,2.6内核软亲和力的缺陷已经比2.4的内核有了很大的改善。在双核机器上,针对两线程的方案,如果将计算 apple 的线程绑定到一个 CPU 上,将计算 orange 的线程绑定到另外一个 CPU 上,效率是否会有所提高呢?程序如下: 清单 5. CPU 亲和力struct apple
struct orange
int a[ORANGE_MAX_VALUE];
int b[ORANGE_MAX_VALUE];
inline int set_cpu(int i)
CPU_ZERO(&mask);
if(2 &= cpu_nums)
CPU_SET(i,&mask);
if(-1 == sched_setaffinity(gettid(),sizeof(&mask),&mask))
return -1;
void* add(void* x)
if(-1 == set_cpu(1))
return NULL;
for(sum=0;sum&APPLE_MAX_VALUE;sum++)
((struct apple *)x)-&a +=
((struct apple *)x)-&b +=
return NULL;
int main (int argc, const char * argv[]) {
// insert code here...
struct orange test1;
cpu_nums = sysconf(_SC_NPROCESSORS_CONF);
if(-1 == set_cpu(0))
return -1;
pthread_create(&ThreadA,NULL,add,&test);
for(index=0;index&ORANGE_MAX_VALUE;index++)
sum+=test1.a[index]+test1.b[index];
pthread_join(ThreadA,NULL);
}测量结果为:图 5. 采用硬亲和力时间对比图(两线程)其测量结果正是我们所希望的,但花费的时间还是比单线程的多,其原因与上面分析的类似。进一步分析不难发现,样例程序大部分时间都消耗在计算 apple 上,如果将计算 a 和 b 的值,分布到不同的 CPU 上进行计算,同时考虑 Cache 的影响,效率是否也会有所提升呢?图 6. 采用硬亲和力时间对比图(三线程)从时间上观察,设置亲和力的程序所花费的时间略高于采用 Cache 的三线程方案。由于考虑了 Cache 的影响,排除了一级缓存造成的瓶颈,多出的时间主要消耗在系统调用及内核上,可以通过 time 命令来验证:#time ./unlockcachemultiprocess
#time ./affinityunlockcacheprocess
0m0.008s通过设置 CPU 亲和力来利用多核特性,为提高应用程序性能提供了捷径。同时也是一把双刃剑,如果忽略负载均衡、数据竞争等因素,效率将大打折扣,甚至带来事倍功半的结果。在进行具体的设计过程中,需要设计良好的数据结构和算法,使其适合于应用的数据移动和处理器的性能特性。总结根据以上分析及实验,对所有改进方案的测试时间做一个综合对比,如下图所示:图 7. 各方案时间对比图单线程原始程序平均耗时:1.049046s,最慢的不加锁三线程方案平均耗时:2.217413s,最快的三线程( Cache 为128)平均耗时:0.826674s,效率提升约26%。当然,还可以进一步优化,让效率得到更高的提升。从上图不难得出结论:采用多核多线程并行设计方案,能有效提高性能,但如果考虑不全面,如忽略带宽、数据竞争及数据同步不当等因素,效率反而降低,程序执行越来越慢。如果抛开本文开篇时的限制,采用上文曾提到的另外一种数据分解模型,同时结合硬亲和力对样例程序进行优化,测试时间为0.54s,效率提升了92%。软件优化是一个贯穿整个软件开发周期,从开始设计到最终完成一直进行的连续过程。在优化前,需要找出瓶颈和热点所在。正如最伟大的 C 语言大师 Rob Pike 所说:如果你无法断定程序会在什么地方耗费运行时间,瓶颈经常出现在意想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在。将这句话送给所有的优化人员,和大家共勉。参考资料
请参考书籍《多核程序设计技术》,了解更多关于多线程设计的理念
请参考书籍《软件优化技术》,了解更多关于软件优化的技术
请参考书籍《UNIX编程艺术》, 了解更多关于软件架构方面的知识
参考文章《》,了解更多关于CPU亲和力的信息
参考文章《》,了解更多关于CPU亲和力的信息
下载资源 (project.tar.gz | 15KB)
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=http://www.ibm.com/developerworks/js/artrating/SITE_ID=10Zone=LinuxArticleID=352219ArticleTitle=利用多核多线程进行程序优化publish-date=

我要回帖

更多关于 多核cpu 的文章

 

随机推荐