# Ray：评估代码性能

## 一个基础分析示例

import ray
import time
# Our time-consuming remote function
@ray.remote
def func():
time.sleep(0.5)

# 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()))

# This loop is more proper in Ray
def ex2():
list2 = []
for i in range(5):
list2.append(func.remote())
ray.get(list2)

# 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的时间戳测试时间性能

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

@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:'ex1' args:[(), {}] took: 2.5083 seconds |
| func:'ex2' args:[(), {}] took: 1.0032 seconds |
| func:'ex3' args:[(), {}] took: 2.0039 seconds |

@ray.remote
def func(): # A single call takes 0.5 seconds
time.sleep(0.5)
def ex1():  # Took Ray 2.5 seconds
list1 = []
for i in range(5):
list1.append(ray.get(func.remote()))
def ex2():  # Took Ray 1 second
list2 = []
for i in range(5):
list2.append(func.remote())
ray.get(list2)

ex1()实际上是Ray中常见的用户错误。在将func()的结果添加到列表之前，不需要执行ray.get。相反，驱动程序应该将所有可并行调用的远程函数调用发送给Ray，然后再等待通过ray.get接收结果。只需使用这种简单的时间测试就可以发现ex1()的次优行为。

# A local function that must run in serial
def other_func():
time.sleep(0.3)
def ex3():  # Took Ray 2 seconds, vs. ex1 taking 2.5 seconds
list3 = []
for i in range(5):
other_func()
list2.append(func.remote())
ray.get(list3)

ex2()的显着加速是可能的，因为ex2()在理论上是完全可并行化的：如果我们给了5个CPU，那么对func()的所有5个调用都可以并行运行。然而，ex3()发生的事情是每个对func()的并行调用都会被等待0.3秒，以便本地的other_func()完成。

ex3()因此是阿姆达尔定律的一种表现形式：从应用程序并行化的理论上，可行的最快执行时间仅限制于以串行方式运行所有串行部件所需的时间。

## 使用外部分析器（Line Profiler）进行分析

pip install line_profiler

line_profiler需要将分析的每个驱动程序代码段作为其自己的独立功能。方便的是，我们已经通过将每个循环函数定义为自己的函数来实现。告诉line_profiler要分析哪些函数，只需将@profile装饰器添加到ex1()，ex2()和ex3()。请注意，无需将line_profiler导入Ray应用程序：

@profile  # 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()

kernprof -l your_script_here.py

python -m line_profiler your_script_here.py.lprof

返回结果中 具体 含义：

Total Time：测试代码的总运行时间

Line:代码行号
Hits：表示每行代码运行的次数
Time：每行代码运行的总时间
Per Hits：每行代码运行一次的时间
% Time：每行代码运行时间的百分比

## 使用Python的CProfile进行分析

import cProfile  # Added import statement
def ex1():
list1 = []
for i in range(5):
list1.append(ray.get(func.remote()))
def main():
ray.init()
cProfile.run('ex1()')  # Modified call to ex1
cProfile.run('ex2()')
cProfile.run('ex3()')
if __name__ == "__main__":
main()

ncalls ： 函数的被调用次数，如果这一列有两个值，就表示有递归调用，第二个值是原生调用次数，第一个值
是总调用次数。
tottime ：函数总计运行时间，除去函数中调用的函数运行时间
percall ：函数运行一次的平均时间，等于tottime/ncalls
cumtime ：函数总计运行时间，含调用的函数运行时间
percall ：函数运行一次的平均时间，等于cumtime/ncalls
filename:lineno(function) 函数所在的文件名，函数的行号，函数名

## 使用cProfile分析Ray的Actors

# Our actor
@ray.remote
class Sleeper(object):
def __init__(self):
self.sleepValue = 0.5

# Equivalent to func(), but defined within an actor
def actor_func(self):
time.sleep(self.sleepValue)

def ex4():
# This is suboptimal in Ray, and should only be used for the sake of this example
actor_example = Sleeper.remote()

five_results = []
for i in range(5):
five_results.append(actor_example.actor_func.remote())

# Wait until the end to call ray.get()
ray.get(five_results)

def main():
ray.init()
cProfile.run('ex4()')
if __name__ == "__main__":
main()

def ex4():
# Modified to create five separate Sleepers
five_actors = [Sleeper.remote() for i in range(5)]

# Each call to actor_func now goes to a different Sleeper
five_results = []
for actor_example in five_actors:
five_results.append(actor_example.actor_func.remote())

ray.get(five_results)

## 在Ray的时间轴中可视化任务

Ray带有自己的可视化Web UI，可视化用户提交给Ray的并行任务。

pip install jupyter ipywidgets bokeh

Ray的Web UI尝试在localhost的8888端口上运行，如果失败则尝试其余的连续端口，直到找到一个打开的端口。在上面的例子中，它已在端口8897上打开。

def main():
ray.init()
ex1()
ex2()
ex3()

# Require user input confirmation before exiting
hang = input('Examples finished executing. Press enter to exit:')
if __name__ == "__main__":
main()

（为此示例添加了ex1()，ex2()和ex3()突出显示的颜色框）

• 广告
• 抄袭
• 版权
• 政治
• 色情
• 无意义
• 其他

120