您的位置:首页 > 运维架构 > Linux

转--Linux和Windows创建进程速度比较

2012-03-02 10:51 411 查看
编辑器加载中...《Unix编程艺术》倡导多进程架构,书中认为“相对独立地址空间的轻量级进程,线程是个糟糕的替代;线程是那些进程生成昂贵、IPC功能薄弱的操作系统的概念”;“基于线程的程序不仅产生普通的竞争问题,而且产生了新一类bug:时序依赖。”。确实Unix对线程确实不怎么看重,属于Unix文化范畴的Nginx、Python和Chrome都采取多进程的策略。Nginx默认情况下使用多进程模型,在生产环境下也不开启多线程(已经向淘宝网相关技术人员求证过)。Python引入GIL锁使得Python上的多线程是伪多线程,但是这大大提高了单线程程序的效率。Chrome是第一个采用多进程架构的浏览器,这种新架构不仅证明了多进程也可以拥有非常快的速度,并且具有更高的安全性。Chrome在效率和安全性上都是当前浏览器领域的翘楚。

对于Linux而言,确实没有太多必要使用多线程。主要有以下原因:
1、Linux本身采用的是轻量级进程作为线程。创建线程的开销同创建进程的开销差不多。
2、线程频繁地竞争临界资源导致效率低下。
3、Linux拥有高效的IPC功能。通过共享内存,进程可以高效共享数据。
4、多进程架构安全性更佳。

下面分别测试了Windows和Linux创建进程的速度。Unix是在不断发展过程引入多线程功能的,Windows则是作为多线程操作系统而设计。可以预见Unix创建进程的速度会比Windows快得多。

子进程序执行的代码如下:
int main(void)
{
return 0;
}

Windows测试代码如下:

?View Code C
#include
#include
#include

int main(void)
{
TCHAR cmdline[] = _T("TestPro");

PROCESS_INFORMATION pi;
STARTUPINFO si;

ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );

int n = 1000;

DWORD t0 = GetTickCount();

for (int i=0; i
#include
#include

#include

#define NUM 1000

int main()
{
pid_t pid;

struct timeval begin, end;

int i;

gettimeofday(&begin, NULL);

for (i = 0; i < NUM; ++i)
{
//创建子进程
pid = fork();

if (pid == -1)
{
perror("fork");
exit(-1);
}
//子进程立即退出
else if (pid == 0)
{
if (execlp("./TestPro", NULL, NULL) < 0)
{
perror("execlp");
}
}
}

gettimeofday(&end, NULL);

printf("%dms\n", ((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec) / 1000);

return 0;
}
测试硬件平台为Core 2 Duo T5450 1.66GHz,1GB内存。操作系统为Windows XP SP3/Ubuntu 10.04。测试时关闭所有窗口,断开网络,只开启一个终端运行测试程序。

Windows五次运行的结果如下:
8015ms
7968ms
7968ms
8016ms
7984ms

Linux五次运行的结果如下:
291ms
281ms
292ms
281ms
277ms

Windows创建一个进程大约需8ms左右,而Linux创建一个进程大约需0.28ms,二者相差大约28倍(因为vfork减少了一些拷贝过程,因此使用vfork速度还能再快一点点)。在测试过程中,我发现Linux一个进程至多只能创建1.5万个子进程左右,否者就会出现”Resource temporarily unavailable”这个错误。系统没有限制max user processes。

下面是使用strace得到的系统调用统计结果:

?View Code TEXT
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.001750 1 1229 229 clone
0.00 0.000000 0 1 read
0.00 0.000000 0 1 write
0.00 0.000000 0 2 open
0.00 0.000000 0 2 close
0.00 0.000000 0 1 execve
0.00 0.000000 0 3 3 access
0.00 0.000000 0 1 brk
0.00 0.000000 0 2 gettimeofday
0.00 0.000000 0 1 munmap
0.00 0.000000 0 4 mprotect
0.00 0.000000 0 7 mmap2
0.00 0.000000 0 3 fstat64
0.00 0.000000 0 1 set_thread_area
------ ----------- ----------- --------- --------- ----------------
100.00 0.001750 1258 232 total
可以看到clone一共调用了1229次,其中有229次失败了。使用下面的代码测试Linux下线程的创建速度。

?View Code C
#include
#include

#include

#include

#define NUM 1000

void* callback(void* p)
{
return NULL;
}

int main()
{
pthread_t tid;

struct timeval begin, end;

int i;

int ret;

gettimeofday(&begin, NULL);

for (i = 0; i < NUM; ++i)
{
//创建线程
ret = pthread_create(&tid, NULL, (void*)callback, NULL);

if (ret == -1)
{
perror("pthread_create");
}
}

gettimeofday(&end, NULL);

printf("%dms\n", ((end.tv_sec - begin.tv_sec) * 1000000 + end.tv_usec - begin.tv_usec) / 1000);

return 0;
}
创建1000个线程仅需29ms左右,系统调用统计如下。可以发现主要的时间都花在mmap2系统调用了,clone仅仅调用380个左右,有点奇怪。

?View Code TEXT
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000306 0 1011 620 mmap2
0.00 0.000000 0 2 read
0.00 0.000000 0 1 write
0.00 0.000000 0 3 open
0.00 0.000000 0 3 close
0.00 0.000000 0 1 execve
0.00 0.000000 0 4 4 access
0.00 0.000000 0 3 brk
0.00 0.000000 0 2 gettimeofday
0.00 0.000000 0 1 munmap
0.00 0.000000 0 380 clone
0.00 0.000000 0 1 uname
0.00 0.000000 0 385 mprotect
0.00 0.000000 0 2 rt_sigaction
0.00 0.000000 0 1 rt_sigprocmask
0.00 0.000000 0 1 getrlimit
0.00 0.000000 0 4 fstat64
0.00 0.000000 0 2 1 futex
0.00 0.000000 0 1 set_thread_area
0.00 0.000000 0 1 set_tid_address
0.00 0.000000 0 1 set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00 0.000306 1810 625 total
昨天同三百同学交流了下,他马上就看了glibc中相关的源代码,然后就发现当mmap2调用失败的情况下,就不会再调用clone了。这样确实吻合得很好,因为mmap2调用失败的次数(620)和clone的调用次数(380)加起来刚好是1000次。这里调用mmap2多于1000次,多出来的应该是加载动态库导致的。pthread_create调用成功返回0,否者返回一个非零值,这点同一般的系统调用和库函数调用失败返回-1不一样,所以上面代码判断返回值为-1认为调用失败是错误的。正确的情况应该是判断返回值是否为非零值。经过测试,发现在我的机器上一个进程最多只能创建300多个线程。在使用Intel Xeon E5420(64位CPU,内存8GB)的RHEL AS 4服务器上创建10000个线程都没有问题。

这篇文章参照了windows和linux进程启动速度比较,不过原文中有一些不妥之处:
1、使用System启动子程序。Linux下System函数会创建子进程,这样Linux创建的进程比Windows多了一倍。应该使用exec系统调用。
2、子程序代码偏多,使用malloc和free等库函数,干扰了实验结果。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: