actor传输过程中会有残余的隐性内存占用,阻碍后续程序运行?怎么彻底清除内存呢?

这里进行性能测试的时候遇到了问题,head在不同的节点上定义两个actor,一个发送数据一个接受数据,接受方受到数据就表示收到了然后丢弃。按理来说可以在剩余内存的空间内持续测试,但结果是之前的发送会留下参与内存,最终导致内存溢出,ray自动kill进程。
代码如下:
import ray
ray.init(address=‘auto’)
import time
@ray.remote
class MyActor:
def init(self, actor_id):
self.actor_id = actor_id

def send_message(self, message, target_actor):
    # 将消息打包成一个对象并发送给目标actor
    message_id = message
    beg_time=time.time()
    target_actor.receive_message.remote(message, self.actor_id,beg_time)
    del message
    del target_actor

def receive_message(self, message, sender_id,beg_time):
    # 从对象ID中获取消息对象并进行处理
    del message
    t=time.time()
    t=t-beg_time
    print("收到来自"+str(sender_id)+"耗时:"+str(t))
    del t
    del sender_id
    del beg_time

创建两个actor并相互发送消息

actor1 = MyActor.options(scheduling_strategy= ray.util.scheduling_strategies.NodeAffinitySchedulingStrategy(
node_id=‘2f00d246b672ccf5ce7d8eb1aea40a73f21a9e71d10b4923904d1849’,
soft=False,) ).remote(1)
actor2 = MyActor.options(scheduling_strategy=ray.util.scheduling_strategies.NodeAffinitySchedulingStrategy(
node_id= ‘5a8d371e969e4fe5e3a76a704bd0e349b7a5b5e3b9c985dfb45e846a’,
soft=False,)).remote(2)
for i in range(20):
print(i)
data = b"0" * 1024 * 1024 *1024 *2
actor1.send_message.remote(data, actor2)
del data
一开始的情况:


几次传输之前的情况:
image

我大概知道了,你这里是在没有等待 task 结束的情况下就把后面的task一起发出去了,这样会导致你 raylet 上 object manager 的压力比较高,比如 raylet 中间的 pull/push 模块,我们默认的限制的是最大 pull request size 和的上限是 4GB,push 的 push request size 是 2GB,看着图上的来到是合理的,你可以先试着改成同步的:

  • send_message:
def send_message(self, message, target_actor):
    # 将消息打包成一个对象并发送给目标actor
    message_id = message
    beg_time=time.time()
    ref = target_actor.receive_message.remote(message, self.actor_id,beg_time)
    ray.get(ref)
    del message
    del target_actor
  • driver:
for i in range(20):
    print(i)
    data = b"0" * 1024 * 1024 *1024 *2
    ref = actor1.send_message.remote(data, actor2)
    ray.get(ref)
    del data

您的意思是,会溢出是因为下一步和之前的异步同时进行了?

不是同时,执行的时候应该是会排队的,但是 task 一次都会发出来,会导致 raylet 这边 object manager 压力增大,这个内存我怀疑是 raylet 推上去的,你可以 top 看看呗

问题是,按理来说我发送数据完成,内存不会增加占用啊?我直接试了一下,当我发送的数据较小,就能一直发送。而之前我不用循环,单次进行数据发送的时候,比如我发一次6G的数据并丢弃,但内存占用还是上升了,每次都会上升,等我第四次发送的时候就会报错。

这个事情你控制不了,数据发送和接受是 raylet 上的 pull/push manager 来干的

数据被序列化存在 raylet 上了,只是你的堆内存没有了,数据还是会存在 object store 里面的,如果你 object store 没有设置的话,shared-memory 默认是内存的 10%,即使这个数据在磁盘上,在send出去的时候还是会先读到内存里面,为了不 oom 是做了限制的,你这个机器看起来就 8GB,这套默认配置可能不适合你的场景,得调一下

那我没有很明白。就是我们公司的需求是,不同物理机器上的两个actor一个发送数据然后,一个接受数据之后立马丢弃释放内存空间,理论上应该是可以一直持续的,为什么还有残余的内存占用?

我觉得你这个例子还不能证明有 “残余”,我觉得你这个是在传输过程中 oom 了,你的传输并没有完成,所以建议你改成同步的,这样带宽的最大值应该可以控制一下

抱歉,我还是没有完全弄明白,我自己修正了代码,把传入数据的生成写入了actor的方法里面,然后我发现当我这里设定发送数据为1G或2G的时候,可以一直死循环下去,但当我设定5,6G就会OOM。

@ray.remote
class MyActor:
def init(self, actor_id):
self.actor_id = actor_id

def send_message(self,target_actor):
    # 将消息打包成一个对象并发送给目标actor
    beg_time=time.time()
    message=b'0'*1024*1024*1024*5 #修改这里的5则是生成传输数据的大小
    c=target_actor.receive_message.remote(message,self.actor_id,beg_time)
    beg_time=None
    target_actor=None
    del message
    del beg_time
    del target_actor
    return c

def receive_message(self, message, sender_id,beg_time):
    # 从对象ID中获取消息对象并进行处理
    del message
    t=time.time()-beg_time
    print("收到来自"+str(sender_id)+"耗时:"+str(t))
    t=None
    del t
    del sender_id
    del beg_time
    return 'get'

执行语句是:

while 1:
D=ray.get(actor1.send_message.remote(actor2))
print(ray.get(D))
del D
我的配置在dashboard里是(两个从节点关闭重启过):

最典型的,我是用jupyter的Python编辑调用ray,当我终止并ray.kill()杀死两个actor和ray.shutdown()并且关闭内核完成job之后,内存居然还有几个G的占用,我不知道这是为什么?是会自动把前几次的数据保存在内存不会释放吗?

这几个 G 的占用是哪里拿出来的?dashboard 上读取的?分析内存你用 top 看,把 top 输出截图出来,看看吃内存的进程是哪个

我觉得 5、6G oom 非常 make sense 啊,你actor进程里面5个G,raylet进程在传输数据(这块在极限压力下可能回到4+2GB)你机器内存也就8G

dashboard的显示是:


而两台机器各自的top指令是:
发送actor所在lvm4:

接受actor所在lvm05:

看上去都是raylet占据了主要内存,我现在已经kill了actor和ray。shutdown(),job全部结束。

lvm05 上 raylet 进程的信息没有,你 -p 打一下 raylet 的进程信息

你在 head 上跑一下 ray memory 看一下

head节点上命令ray memory:

lvm5上:
image

@Aslan 先检查一下你的 object store size 是不是2GB:

$ cd /tmp/ray/session_latest/logs
$ grep "Allowing the Plasma store to use up to" raylet.out

如果是的话就 make sense 了,首先, ray memory 命令可以看出所有的 object 都 release 掉了,这个 2GB 的shred memory就是划给 object store 的专用空间。

object store 在设计上的时候就是从内存中划走连续的一部分,这个是不会归还的,所以此时:

  • lm5 上 raylet 的内存开销只有 RES-SHR 大概只有200MB,我理解也还 ok
  • lm4 上看起来有 1.9 GB,你观察一下,他会下降么?

似乎是,但我没明白


我比较关心 lm4 上的 raylet 在两个 actor 全被 kill 之后,driver 进程上执行完 ray.shutdown 之后,他的 RES 是不是可以回落到 2 GB 附近,你用 top 看一下

额,似乎不会,我一开始给你发的图就是kill和shutdown之后,我没有再启动过ray.init

我仔细看了官方教材关于memory manage的部分,感觉还是难以解释。raylet应该是一个控制进程,本身不会占用多少内存,object store memory和创建的对象文件才是占据内存的主要部分。为什么这里我尽量都释放,且OSM清零了的情况下,raylet会占据空间如此之大。