在很早的时候,就听网上的文章说:
python有GIL,所以在单进程内,即使使用多线程也无法利用到多核的优势,同一时刻,python的字节码只会运行在一个cpu上。
以前也是奉为真理,直到今天在对自己的python server做性能测试的时候,发现一个python进程的cpu居然达到了120%。
当用c++编程的时候,如果使用多线程,那么确实进程cpu超过100%非常正常,但是对python来说,似乎这样就和网上的文章冲突了。
所以还是决定自己亲身试验一下,编写代码如下:
from thread import start_new_thread def worker(): while 1: #print 1 pass for it in range(0, 15): start_new_thread(worker, ()) raw_input()
运行环境为: centos6.4 64位, python 2.7.
得到的结果如下:
可以清楚的看到,pid为31199的python进程cpu达到了787.9%,接近理论能达到的最大值 800%。
而上方的8个cpu也分别达到了近100%的利用率。
如果只是按照以上测试结果,确实可以得出的结论:python使用单进程,多线程确实能够使用到多核cpu,并不是网上传的结论。
但是,还是希望如果有读者对这块有更深入的研究能够进行批评指正,谢谢~
8月15日补充
感谢 la.onger 等几位博友的讨论,现在增加一个测试,用来测试纯cpu计算用一个线程or多个线程完成的总时间的差别,代码如下:
import time from threading import Thread LOOPS = 1000000 THREAD_NUM = 10 STEP_SIZE = 94753434 class Test(object): num = 1 def work(self): for it in xrange(0, LOOPS): if self.num > STEP_SIZE: self.num -= STEP_SIZE else: self.num += STEP_SIZE def one_thread_test(self): self.num = 1 begin_time = time.time() for v in xrange(0, THREAD_NUM): self.work() print 'time passed: ', time.time() - begin_time def multi_thread_test(self): self.num = 1 t_list = [] begin_time = time.time() for v in xrange(0, THREAD_NUM): t = Thread(target=self.work) t.start() t_list.append(t) for it in t_list: it.join() print 'time passed: ', time.time() - begin_time t = Test() t.one_thread_test() t.multi_thread_test()
输入结果如下:
time passed: 3.44264101982 time passed: 7.22910785675
使用多线程后,比不用多线程还慢
为了与c++版做对比,也开发了c++代码如下:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <iostream>
#include <memory>
#include <sstream>
#include <algorithm>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <sys/time.h>
#include <pthread.h>
using namespace std;
#define LOOPS 1000000
#define THREAD_NUM 10
#define STEP_SIZE 94753434
class Test
{
public:
Test() {}
virtual ~Test() {}
void one_thread_test() {
this->num = 1;
gettimeofday(&m_tpstart,NULL);
for (size_t i = 0; i < THREAD_NUM; ++i)
{
work();
}
gettimeofday(&m_tpend,NULL);
long long timeuse=1000000*(long long)(m_tpend.tv_sec-m_tpstart.tv_sec)+m_tpend.tv_usec-m_tpstart.tv_usec;//微秒
printf("time passed: %f\n", ((double)timeuse) / 1000000);
}
void multi_thread_test() {
this->num = 1;
int ret;
vector<pthread_t> vecThreadId;//所有thread的id
pthread_attr_t attr;
pthread_attr_init (&attr);
pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
gettimeofday(&m_tpstart,NULL);
pthread_t threadId;
for (int i = 0; i < THREAD_NUM; i++)
{
ret= pthread_create(&threadId, &attr, Test::static_run_work, (void*)this);
if(ret!=0){
pthread_attr_destroy (&attr);
}
vecThreadId.push_back(threadId);
}
pthread_attr_destroy (&attr);
for(vector<pthread_t>::iterator it = vecThreadId.begin(); it != vecThreadId.end(); ++it)
{
pthread_join(*it, NULL);
}
gettimeofday(&m_tpend,NULL);
long long timeuse=1000000*(long long)(m_tpend.tv_sec-m_tpstart.tv_sec)+m_tpend.tv_usec-m_tpstart.tv_usec;//微秒
printf("time passed: %f\n", ((double)timeuse) / 1000000);
}
void work() {
for (size_t i = 0; i < LOOPS; ++i) {
if (this->num > STEP_SIZE) {
this->num -= STEP_SIZE;
}
else {
this->num += STEP_SIZE;
}
}
}
static void* static_run_work(void *args) {
Test* t = (Test*) args;
t->work();
return NULL;
}
public:
int64_t num;
struct timeval m_tpstart,m_tpend;
};
int main(int argc, char **argv)
{
Test test;
test.one_thread_test();
test.multi_thread_test();
return 0;
}
输出结果如下:
time passed: 0.036114 time passed: 0.000513
可见,c++版确实性能提高了非常多。
由此可见,python的多线程编程,在多核cpu利用上确实差一些。
la.onger on #
博主再试试?GIL是全局资源锁,所以,如果没有涉及到资源的调用,是不会体现的。另外,如果线程进行的是简单运算,由于运算速度太快,导致线程间请求和释放GIL间隔太短,所以也不能观察到。我尝试了无限乘2,多少能够看到与博主不同的现象了。
Reply
Dante on #
看到这个结果我也很惊讶,所以在macbook和centos上都测试了一遍。。我之前测试server性能的时候,用的是ThreadingTcpServer,cpu到了120%。
Reply
la.onger on #
我想说的是,这种测试结果并不靠谱。因为top是一段时间内的统计结果,所以我们看top的统计的时候,并不能看到在微观的某一个时刻有多少个核心在同时进行运算。所以我们只能把问题放大化,形成规模,去看规模化后的性能。
Reply
Dante on #
嗯,同意,其实也是在考虑有没有好一点的测试方法呢?
Reply
la.onger on #
我下面贴的代码就可以说明问题了,我机器上cpu占有接近100。网上有很多这样的讨论,无非就是做ab组对比,同样的大数计算,a组是thread,用了多长时间,b组是multiprocessing,用了多长时间。
Reply
Dante on #
好像没贴上代码?能否贴下我也去测试一下
Reply
la.onger on #
我在下一层楼贴的就是https://gist.github.com/laonger/48e2a3821c12b803029f
Reply
Dante on #
嗯啊,测出来是在102%左右
Reply
Dante on #
已经更新文章:)
Reply
shelper on #
至少在windows下,貌似不是這樣子的在windows python 3.4 下也不是這樣子的surprised
Reply
Dante on #
暂时没windows系统呢,等装上之后再试试
Reply
z拽_ on #
换成 while 1==1:pass 试试。。。。
Reply
Dante on #
还是那台机器,cpu现在是243%左右。。
Reply
z拽_ on #
如果查看汇编码,会发现这里被python特殊优化过~~~但愿我没记错~
Reply
la.onger on #
嗯,单线程的时候,可以一个线程霸占一个核心,多线程的时候,十多个线程排队,轮流使用核心,算上等待,线程切换开销,所以速度就降下来了
Reply
Dante on #
确实如此
Reply
东方号的加加林 on #
#one_thread_test time passed: 3.15800595284#multi_thread_test time passed: 10.1716201305#multi_process_test time passed: 0.358360052109博主可以试试multiprocessing,这个速度快了不少
Reply
Dante on #
是的,多进程是能够用到多核cpu的:)
Reply
痤疮的原因 on #
学习了
Reply
dingxinglong on #
我也是发现用多线程之后效率反而变低了,不符合直觉。观察发现,上下文切换很高。楼主的图中也很明显,%80+都耗在system了,这个不合理,计算密集型的应该%95以上都是user的。博主以后可以多注意这个数据。把所有CPU跑满也可能是瞎忙 : )
Reply
Dante on #
赞,之前确实没有注意到这个,多谢提醒~
Reply
python on #
计算密集型用python的多线程效果不明显的I/O密集型才能看出效果的
Reply
lr on #
python在多线程方面很挫,我们一般用多进程的方式搞python server,不过性能还是比较废的。我们一般用thrift搞rpc server,最近google又开源了grpc,感觉rpc服务器这一块就再也不用自己造轮子了,有时候用c++写个server出来也是很快的。另外这些框架都能支持多语言接口,用起来非常方便。。
Reply
Dante on #
嗯,我们现在的实现方式也是多进程python,而且python只是拿来做worker用,性能要求高的server还是用c++来写
Reply
卖鞋垫的狼孩 on #
嗨,我用thrift搞python server,用TThreadPoolServer,测试了一下,还是用不到多核。请问thrift能否用多进程的方式启动python server呢?
Reply
Dante on #
这个没有用过。不过想用多进程,一般几种方法:一种是通过fork socket;一种是单个socket,用多个process来做worker。
Reply
仲晨 on #
我试了下,直接while 1空循环,能达到700%多,而加入一条+运算,就迅速降低到200%了,增加更多,慢慢接近100%。
正常情况下是每100个python指令作为一个时间片切换单元。
但一个纯空循环的话,估计应该有解释器处理,直接就没有任何操作了,导致时间片极短,那700%多的cpu,实际上是在极短的时间片中间,8个核在同时争抢锁,系统进行调度,而消耗的。
Reply
大蟒蛇 on #
说得对!
Reply
Dante on #
多谢指教!
Reply
petanne on #
刚好可以解释sysuse特别高的原因,赞
Reply