在Python中使用Ray轻松执行分布式计算
介绍分布式计算的概念,并使用开源的Python库Ray来编写可以在分布式系统上工作的可扩展代码。
我们在之前的文章中已经看到了如何使用线程和多进程来并发地进行我们的计算。即使是有多个CPU核心的单处理器计算机(一个处理器有一个或多个核心,一台计算机有一个或多个处理器)也会给人一种能够同时运行多个任务的错觉。当我们拥有多个处理器时,那么我们就可以真正以并行的方式执行我们的计算。
并行计算还是分布式计算?
并行计算是非常有用的,而且几乎是现代计算中的必需品,目的是为了实现最大的性能。我们将长期运行的计算分成小块,并将其分包给不同的处理器。这种策略使我们能够在相同的时间内进行更多的计算。对于构建基于GUI的应用程序,总是需要对系统进行并行设计,以便一个线程能够保持可用,以更新GUI并响应用户的输入。
并行计算和分布式计算的区别在于,对于并行计算,多个处理器驻扎在同一主板上。分布式计算则同时使用多台计算机来解决一个问题。现代分布式系统能够通过网络(LAN/WAN)进行通信。分布式计算的优势在于其价格和可扩展性。如果我们需要更多的力量,那么我们可以很容易地增加更多的计算机。
从本质上讲,并行计算和分布式计算的架构是非常相似的。主要的区别来自于拥有分布式内存空间,而不是分布式计算的共享内存空间。有一个软件层可以为我们的应用程序提供一个统一的逻辑(而不是物理)内存空间,可以帮助我们在分布式计算中运行为并行计算编写的代码。
在这篇文章中,我们将看到我们如何使用一个开源的Python库Ray来帮助我们执行并行和分布式计算。Ray采用Pythonic函数和类,并将它们转化为分布式设置的任务和角色。我们将只看到函数的例子,然而对于类来说,其概念是非常相似的。
用以下方法安装Raypip
这将安装支持仪表板和集群启动器的Ray。
pip install 'ray[default]'
如果你想要最小的安装。
pip install -U ray
在Ray中用任务进行并行计算
让我们执行我们在前一篇文章中使用concurrent.futures 运行的一个例子,并与我们使用ray 对同一任务的运行进行比较。
import timeimport concurrent.futuresStime = time.perf_counter()tasks = []sleepTimes = [0.1, 0.2, 0.1, 0.5, 0.7, 0.9, 0.5, 0.4, 1.5, 1.3, 1.0, 0.3, 0.7, 0.6, 0.3, 0.8]print(f"Total time of sleep: {sum(sleepTimes)} for {len(sleepTimes)} tasks")def my_awesome_function(sleepTime=0.1): time.sleep(sleepTime) return f"Sleep time {sleepTime}"all_results = []with concurrent.futures.ProcessPoolExecutor() as executor: tasks = [executor.submit(my_awesome_function, sleep) for sleep in sleepTimes] for ff in concurrent.futures.as_completed(tasks): all_results.append(ff.result())print(f"Finished in {time.perf_counter()-Stime:.2f}")
这将返回
$ python test_ray.py Total time of sleep: 9.9 for 16 tasks Finished in 1.65
这个任务需要9.9秒的时间才能依次完成。因为我们进行了并行执行,所以在我的电脑上,我们在1.65秒内完成了这项工作。请注意,这个时间对你的电脑来说可能有所不同。
现在,让我们用Ray做同样的工作。我们首先使用ray.init() 来初始化Ray。装饰性的ray.remote ,将Python函数转换为一个可以远程异步执行的函数。它立即返回可以并行执行的函数的N个副本。
import timeimport rayimport concurrent.futuresStime = time.perf_counter()tasks = []sleepTimes = [0.1, 0.2, 0.1, 0.5, 0.7, 0.9, 0.5, 0.4, 1.5, 1.3, 1.0, 0.3, 0.7, 0.6, 0.3, 0.8]print(f"Total time of sleep: {sum(sleepTimes)} for {len(sleepTimes)} tasks")# Start Ray.ray.init()@ray.remote #convert to a function that can be executed remotely and asynchronouslydef my_awesome_function(sleepTime=0.1): time.sleep(sleepTime) return f"Sleep time {sleepTime}"tasks = []for sleep in sleepTimes: tasks.append(my_awesome_function.remote(sleep))all_results = ray.get(tasks)print(f"Finished in {time.perf_counter()-Stime:.2f}")
这将返回
Total time of sleep: 9.9 for 16 tasks Finished in 3.18
由于开销的原因,有一些延迟,但对于大型计算来说,这变得可以忽略不计。
大型计算的聚合值
Ray可以很容易地用于聚合多个值,这对于建立一个大型的应用程序是至关重要的,因为我们需要在多台机器上进行聚合计算。对于大型计算,Ray可以将聚合的运行时间从线性改为对数。
让我们看一个例子。
import timeimport rayimport numpy as npStime = time.perf_counter()@ray.remotedef create_matrix(size): return np.random.normal(size=size)@ray.remotedef multiply_matrices(x, y): return np.dot(x, y)@ray.remotedef sum_matrices(x, y): return np.add(x, y)m1 = create_matrix.remote([1000, 1000])m2 = create_matrix.remote([1000, 1000])m3 = create_matrix.remote([1000, 1000])m4 = create_matrix.remote([1000, 1000])m12 = multiply_matrices.remote(m1, m2)m34 = multiply_matrices.remote(m3, m4)a12_34 = sum_matrices.remote(m12, m34)## ResultsMM = ray.get(a12_34)print(f"Finished in {time.perf_counter()-Stime:.2f}")
在上面的例子中,我们首先创建四个矩阵,将它们分组为两个,将组中的矩阵相乘,然后将每组的乘法结果相加。在这里,乘法是平行进行的,然后将结果汇总,得到求和。
参考文献
- 安装雷
- Pierfederici, F. (2016).用Python进行分布式计算.In Journal of Physics A: Mathematical and Theoretical (Vol. 44, Issue 8).Packt出版有限公司。
- Modern Parallel and Distributed Python:Ray的快速教程