近来接触一超算小集群,那咱用起来就不能和在单机上一样了,要充分发挥其高性能、集群的优势。故以Python程序为例,研究调用多核多CPU甚至多服务器进行并行计算的问题,留下此笔记。
本次学习以蒙特卡罗方法计算圆周率的小程序来做基础,测试它在普通模式下、调用多线程模式下、调用多进程模式下运行所需的时间。测试平台是在集群内的一点节点,即其中的一台服务器,硬件配置为8颗Intel Xeon 8260 2.4GHz/24c,操作系统CentOS Linux release 7.5.1804 (Core) ,python版本为3.9.12。(如果,我说是如果,如果我把这机器搬回家看看电影、玩玩挖雷…………)
先列出程序源代码。
一、普通模式
很多python教程上都有的一个小程序,在普通电脑上可以很容易调试成功。分别测试了300‘000、3‘000’000、30‘000’000、300‘000’000个点。
#用蒙特卡罗方法计算圆周率,思路是在图形中随机抛置大量的点, 计算落在1/4圆内的点的数量。
import random
import time
n = 300000000 #点的个数,点越多PI越精确,计算量越大、时间越长。
old_time = time.time() #记录开始时间
def monte_carlo_pi_part(n):
count=0
for i in range(n):
x = random.random() #随机生成的一个实数,它在[0,1)范围内。
y = random.random()
if x * x + y* y <=1: #如果点(x,y)离坐标系原点的距离小于等于1,计数一次。
count +=1
return count
if __name__ == '__main__':
print('Run across normal state')
count = monte_carlo_pi_part(n)
print('Estimated value of Pi:: ', count / (n * 1) * 4)
current_time = time.time() #记录结束时间
print("time is " + str(current_time - old_time) + "s")#输出程序运行时间
二、多线程模式
因采用多线程(m个),故将计算任务进行分割,每个线程算一部分点(n/m个)。最后将每个线程统计的落入圆内的点数求和,再算出PI。
测试了30‘000’000点/5线程、30‘000’000点/10线程、300‘000’000点/1线程、300‘000’000点/5线程、300‘000’000点/10线程。
# 运用蒙特卡罗方法计算圆周率,思路是在图形中随机抛置大量的点, 计算落在1/4圆内的点的数量。
# 测试多线程,每个线程各计算n/m个点。
import random
import time
from concurrent.futures import ThreadPoolExecutor
n = 30000000 #点的个数,点越多PI越精确,计算量越大、时间越长。
m = 2 #线程数
results = []
def monte_carlo_pi_part(n):
count=0
for i in range(n):
x = random.random() #随机生成的一个实数,它在[0,1)范围内。
y = random.random()
if x * x + y* y <=1: #如果点(x,y)离坐标系原点的距离 小于等于1,计数一次。
count +=1
return count
def run_mainthread():
pool = ThreadPoolExecutor(max_workers=m) # 创建一个包含m条线程的线程池
for i in range(m):
Thr = pool.submit(monte_carlo_pi_part,int(n/m)) # submit异步提交任务
results.append(Thr)
pool.shutdown()
if __name__ == '__main__':
s = 0
print('Run across ' + str(m) + ' thread')
old_time = time.time() #记录开始时间
run_mainthread()
for res in results:
s = s + res.result()
print('Estimated value of Pi:: ', s/n*4)
current_time = time.time() #记录结束时间
print("time is " + str(current_time - old_time) + "s")#输出程序运行时间
三、多进程模式
同样是蒙特卡罗方法计算圆周率的小程序,但是程序调用了m个进程来运行,每个进程来计算n/m个点,最后将每个进程统计的落入圆内的点数求和,再算出PI。
测试了300‘000’000点/1进程、300‘000’000点/10进程、300‘000’000点/100进程、3‘000‘000’000点/10进程、3’000‘000’000点/100进程、
# 运用蒙特卡罗方法计算圆周率,思路是在图形中随机抛置大量的点, 计算落在1/4圆内的点的数量。
# 测试多进程,每个进程各计算n/m个点。
import random
import time
from multiprocessing import Pool
results = []
n = 300000000 #点的个数,点越多PI越精确,计算量越大、时间越长。
m = 1 #电脑的CPU核数
def monte_carlo_pi_part(n):
count=0
for i in range(n):
x = random.random() #随机生成的一个实数,它在[0,1)范围内。
y = random.random()
if x * x + y* y <=1: #如果点(x,y)离坐标系原点的距离小于等于1,计数一次。
count +=1
return count
def run_mainprocess():
pool = Pool(processes=m) #引入进程池,设m个进程。
for i in range(m):
result = pool.apply_async(monte_carlo_pi_part,(int(n/m),)) #每个进程各计算n/m个点
results.append(result)
pool.close()
pool.join()
if __name__ == '__main__':
s = 0
print('Run across ' + str(m) + ' Process')
old_time = time.time() #记录开始时间
run_mainprocess()
for res in results:
s = s + res.get()
print('Estimated value of Pi:: ', s/n * 4)
current_time = time.time() #记录结束时间
print("node="+str(n)+"\nprocess="+str(m)+"\ntime is " + str(current_time - old_time) + "s")#输出程序运行时间
四、测试结果
计算的点数 | 普通 模式 | 线程数 | 进程数 | ||||
1 | 5 | 10 | 1 | 10 | 100 | ||
300‘000 | 0.0805 | ||||||
3’000‘000 | 0.7159 | ||||||
30’000‘000 | 7.4717 | 7.6830 | 7.3803 | ||||
300’000‘000 | 74.9718 | 74.6107 | 76.4408 | 74.1721 | 75.9172 | 7.8311 | 1.2749 |
3’000‘000’000 | 77.234 | 8.4092 |
注:表格中的小数为程序运算所需的时间。
结论:从表中可看出线程数的多少,对计算时间没有影响,而进程数的多少却有显著的不同。Python的线程是伪线程,由于GIL的限制,同时只能有一个线程运行,这不适合计算密集型任务,只能应用于IO密集型任。不过调用多进程要将计算任务进行分割,调整程序设计。
笔记到此告一段落,多台服务器没找到方法,哎!学习累啊!
听说有dask、ray……
那又是什么?
下次笔记吧!……
后话,当我测试100个cpu核心时,服务器的散热风扇开始暴力运行,发出刺耳的声音,一度我认为要出问题了,多亏时间短,计算完了,风扇的转速也就降下来了。这带回家…………
下次我测了个时间长点的计算任务,测一下风扇的性能…………嘿嘿!