网上对于python中的GIL锁的讨论有很多,在工作中发现多线程运行CPU密集型程序时,效率极其低下。现在将python的GIL锁的优化性能对比方案整理如下(GIL锁的原理这里不再累述)
1. 环境和功能描述
在linux下读取一个目录下的20个相同大小的测试文件,将字符串转成json文件重新存储到新的的20个文件中
cpu信息:硬件个数:1, 核心数:16,线程数:32
2. 单线程顺序执行
耗时1m10s 单个cpu线程使用率100%
2. 多线程效率分析
理论上多线程执行效率应该更高,但实际上效率却成倍减低
同时1线程:1m12s 每运行完一个线程cpu切换
同时2线程:2m7s 2个cpu同时运行不同线程程序,每个cpu使用率70%左右, 每运行完一个线程程序,cpu切换
同时4线程:2m51s 4个cpu同时运行不同线程程序,每个cpu使用率50%左右, 每运行完一个线程程序,cpu切换
同时8线程:2m53s 8个cpu同时运行不同线程程序,每个cpu使用率40%左右, 每运行完一个线程程序,cpu切换
同时16线程:3m10s 16个cpu同时运行不同线程程序,每个cpu使用率20%左右, 每运行完一个线程程序,cpu切换
同时20线程:3m10s 20个cpu同时运行不同线程程序,每个cpu使用率14%左右, 每运行完一个线程程序,cpu切换
效率低下的原因,是因为GIL锁决定了在同一时刻只可能有一个线程在运行程序,当运行的指令数达到一定值的时候才释放GIL锁,给其他线程竞争使用
在这里,就不用线程池做实验了,因为原理是差不多的
3. 优化方案一:进程池
同时20个进程:5s 20个cpu同时运行不同进程程序,每个cpu使用率100%
def logs_to_jsons_of_processing_pool(procId, logFile):
print '\tStart %d processing [%s]' % (procId, logFile)
(filepath, tempfilename) = os.path.split(logFile)
(shotname, extension) = os.path.splitext(tempfilename)
file_name_output = JSON_MAN_OUTPUT_PATH + shotname + '.json'
convert_log_to_json(logFile, file_name_output)
print '\tFinished %d processing [%s]' % (procId, file_name_output)
def load_log_to_json_processing_pool():
fileList = []
get_file_list_by_dir(fileList, LOCAL_LOG_PATH_g1, EXT_LOG)
logCnt = len(fileList)
print 'file cnt = %d' % logCnt
fileList.sort()
pool = multiprocessing.Pool(processes = logCnt)
for i in range(logCnt):
pool.apply_async(logs_to_jsons_of_processing_pool, (i, fileList[i]))
print("%d processes have got!" % logCnt)
pool.close()
pool.join()
print("run finished!")
4. 优化方案二:python中嵌入C代码屏蔽GIL锁
写一个C代码:
将C编译成so动态库文件:
gcc -fPIC logjson.c -shared -o liblogjson.so
同时运行20个C线程:耗时6ms,没错是6个毫秒!