本文档适用于想要了解如何在Ray上运行时评估其代码性能的Ray用户。分析代码的性能对于确定性能瓶颈或找出可能无法正确并行化的代码的位置非常有用。如果你有兴趣查明为什么你的Ray应用程序可能无法实现预期的加速,仔细阅读本文。
一个基础分析示例
尝试分析一个简单的例子,并比较编写简单循环的不同方式会如何影响性能。
作为计算密集且可能运行比较慢的函数的代表,我们将远程函数定义为只睡眠0.5秒:
import ray
import time
# Our time-consuming remote function
@ray.remote
def func():
time.sleep(0.5)
在我们的示例中,我们希望将远程函数func()调用五次,并将每个调用的结果存储到列表中。 为了比较以不同方式循环调用我们的远程函数的性能,我们可以在driver脚本上将每个循环版本定义为单独函数。
对于第一个版本ex1,循环的每次迭代都会调用远程函数,然后调用ray.get以尝试将当前结果存储到列表中,如下所示:
# This loop is suboptimal in Ray, and should only be used for the sake of this example
def ex1():
list1 = []
for i in range(5):
list1.append(ray.get(func.remote()))
对于第二个版本ex2,循环的每次迭代都会调用远程函数,并将其存储到列表中,而不是每次调用ray.get。循环结束后使用ray.get,准备处理func()的结果:
# This loop is more proper in Ray
def ex2():
list2 = []
for i in range(5):
list2.append(func.remote())
ray.get(list2)
最后,对于一个不可如此并行化的示例,创建第三个版本ex3,其中driver必须在每次调用远程函数func()之间调用本地函数:
# A local function executed on the driver, not on Ray
def other_func():
time.sleep(0.3)
def ex3():
list3 = []
for i in range(5):
other_func()
list3.append(func.remote())
ray.get(list3)
使用Python的时间戳测试时间性能
合理地检查三个循环函数性能的一种方法是简单地计算完成每个循环所需的时间。我们可以使用python的内置时间模块来完成这个任务。
time模块包含一个有用的time()函数,它在调用时以unix时间返回当前时间戳。我们可以创建一个通用的函数装饰器,在每个循环函数之前和之后调用time()来打印每个循环总体占用的时间:
# This is a generic wrapper for any driver function you want to time
def time_this(f):
def timed_wrapper(*args, **kw):
start_time = time.time()
result = f(*args, **kw)
end_time = time.time()
# Time taken = end_time - start_time
print('| func:%r args:[%r, %r] took: %2.4f seconds |' % \
(f.__name__, args, kw, end_time - start_time))
return result
return timed_wrapper
为了每次调用循环函数ex1()时总是打印出循环运行了多长时间,我们可以用函数装饰器调用time_this包装器。这可以类似地对函数ex2()和ex3()进行:
@time_this # Added decorator
def ex1():
list1 = []
for i in range(5):
list1.append(ray.get(func.remote()))
def main():
ray.init()
ex1()
ex2()
ex3()
if __name__ == "__main__":
main()
然后,运行这三个循环应该产生类似于这样的输出:
| func:'ex