【问题标题】:python中数组中类存储的多处理成员函数
【发布时间】:2022-01-22 10:10:49
【问题描述】:

我主要使用 C、C++ 进行编程,最近将一个项目转换为 python。除非我无法轻松地转换多处理。

在示例中,我有一个填充了一个球类的数组,该类具有一个名为 update 的成员函数,其中传入了 3 个变量。

就是这样。它存储在一个称为球的数组中。 我已经浏览了足够多的帖子文档和视频,但没有找到任何涵盖这几个方面的内容,但没有展示如何处理传入的变量。

理想情况下,我会创建一个流程拉动,让它在它们之间拆分工作。 我需要检索对象并更新原始进程空间中的对象。

不确定,但看起来可能更容易强制它输出一个元组,然后使用所有数据更新类,然后编写另一个函数来更新类。

我们非常感谢您反馈在 python 中执行此操作的最佳方法。我也欣赏性能胜过做某事的容易程度。毕竟,这就是这样做的意义所在。 提前致谢。

class Ball:
          
    def __init__(self,x,y,vx,vy,c):
        self.x=x
        self.y=y
        self.vx=vx
        self.vy=vy
        self.color=c
        return
    @classmethod
    def update(self,w,h,t):
        time = float(t)/float(1000000)
        #print(time)
        xp = float(self.vx)*float(time)
        yp= float(self.vy)*float(time)
        self.x += xp
        self.y += yp
        #print (str(xp) +"," +str(yp))
        if self.x<32:
            self.vx = 0 - self.vx
            self.x += (32-self.x)
        if self.y<32:
            self.vy = 0 - self.vy
            self.y += (32-self.y)
        if self.x+32>w:
            self.vx = 0 - self.vx
            self.x -= (self.x+32)-w
        if self.y+32>h:
            self.vy = 0 - self.vy
            self.y -= (self.y+32)-h
        return

类通过以下方法更新

def play_u(self):
    t = self.gt.elapsed_time()
    self.gt.set_timer()
    for i in self.balls:
        i.update(self.width,self.height,t)
    return

【问题讨论】:

  • 顺便说一句:与 C++ 一样,Python 支持类属性和实例属性,目前尚不清楚您对类 Ball 的意图是什么。例如,您的vx 属性被定义为类属性,并且在您的方法中,您根据这些是类方法的约定命名第一个参数cls,但这些方法没有用@classmethod 修饰,所以它们实际上是实例方法。因此,在方法@​​987654329@ 中,当您为cls.vx 赋值时,您不会 更新类属性。去掉类属性,将cls重命名为self
  • 但是如果你真的打算把所有东西都变成类属性并且你的方法要更新这些类属性,那么你必须用@classmethod装饰你的方法。但是,如果您要处理这些数组,那将没有意义,不是吗?
  • 多处理仅在将参数从一个地址空间传递到另一个地址空间时就会产生大量开销。为了让它变得有价值,update 完成的处理必须需要足够的 CPU 资源,以便通过并行性获得的节省抵消所产生的额外开销。我不确定这里会是这样。如果您更新问题以显示您实际上是如何多次调用update 将有所帮助,即发布接近minimal, reproducible example 的内容。
  • 我会根据您的建议进行样式更改。谢谢那部分。抱歉,刚刚习惯了python。搜索@classmethod。我不得不在另一个网站上找到它。这是我在 PEP8 文档中发现的典型内容。这是完整的项目。 github.com/Diconica/game_Engine 这是一个从 C++ 转换而来的游戏引擎 类对象数组是 OOP 编程的标准。你是想说python不能做OOP吗?这在所有其他 OOP 语言中都很正常,C++、C#、JAVA...
  • 顺便说一句,谢谢。坦率地说,到目前为止,我还不知道有两种不同的变量类型,例如类和实例。在其他语言中,变量在类中声明为它的成员。此处将其描述为共享变量的方式使其几乎像一个单例。

标签: python class multiprocessing member-functions


【解决方案1】:

这里有一个关于如何调用 update 来处理多个具有相同参数的 Ball 对象的想法。这里我使用multiprocessing.pool.Pool 类。

由于 Python 将 Ball 对象从主进程序列化/反序列化到池中将执行任务的进程,因此对对象的任何修改都不会反映在“存在”的对象副本中在主要过程中(如您所见)。但这并不妨碍update 返回一个已更新属性的列表(或元组),这些属性已被主进程用于更新其对象副本。

class Ball:
    # If this is a class constant, then it can and should stay here:
    radius = 32

    def __init__(self, x, y, vx, vy, c):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.color = c
        return

    def update(self, w, h, t):
        time = float(t) / 1000000.0
        #print(time)
        xp = float(self.vx) * float(time)
        yp = float(self.vy) * float(time)
        self.x += xp
        self.y += yp
        #print (str(xp) +"," +str(yp))
        if self.x < 32:
            self.vx = 0 - self.vx
            self.x += (32 - self.x)
        if self.y < 32:
            self.vy = 0 - self.vy
            self.y += (32 - self.y)
        if self.x + 32 > w:
            self.vx = 0 - self.vx
            self.x -= (self.x + 32) - w
        if self.y + 32 > h:
            self.vy = 0 - self.vy
            self.y -= (self.y + 32) - h
        # Return tuple of attributes that have changed
        # (Not used by serial benchmark)
        return (self.x, self.y, self.vx, self.vy)

    def __repr__(self):
        """
        Return internal dictionary of attributes as a string
        """
        return str(self.__dict__)

def prepare_benchmark():
    balls = [Ball(1, 2, 3, 4, 5) for _ in range(1000)]
    arg_list = (3.0, 4.0, 1.0)
    return balls, arg_list

def serial(balls, arg_list):
    for ball in balls:
        ball.update(*arg_list)

def parallel_updater(arg_list, ball):
    return ball.update(*arg_list)

def parallel(pool, balls, arg_list):
    from functools import partial

    worker = partial(parallel_updater, arg_list)
    results = pool.map(worker, balls)
    for idx, result in enumerate(results):
        ball = balls[idx]
        # unpack:
        ball.x, ball.y, ball.vx, ball.vy = result

def parallel2(pool, balls, arg_list):
    results = [pool.apply_async(ball.update, args=arg_list) for ball in balls]
    for idx, result in enumerate(results):
        ball = balls[idx]
        # unpack:
        ball.x, ball.y, ball.vx, ball.vy = result.get()

def main():
    import time

    # Serial performance:
    balls, arg_list = prepare_benchmark()
    t = time.perf_counter()
    serial(balls, arg_list)
    elapsed = time.perf_counter() - t
    print(balls[0])
    print('Serial elapsed time:', elapsed)

    print()
    print('-'*80)
    print()

    # Parallel performance using map
    # We won't even include the time it takes to create the pool
    from multiprocessing import Pool
    pool = Pool() # pool size is 8 on my desktop
    balls, arg_list = prepare_benchmark()
    t = time.perf_counter()
    parallel(pool, balls, arg_list)
    elapsed = time.perf_counter() - t
    print(balls[0])
    print('Parallel elapsed time:', elapsed)

    print()
    print('-'*80)
    print()

    # Parallel performance using apply_async
    balls, arg_list = prepare_benchmark()
    t = time.perf_counter()
    parallel2(pool, balls, arg_list)
    elapsed = time.perf_counter() - t
    print(balls[0])
    print('Parallel2 elapsed time:', elapsed)


    pool.close()
    pool.join()


# Required for windows
if __name__ == '__main__':
    main()

打印:

{'x': -29.0, 'y': -28.0, 'vx': 3, 'vy': 4, 'color': 5}
Serial elapsed time: 0.0018328999999999984

--------------------------------------------------------------------------------

{'x': -29.0, 'y': -28.0, 'vx': 3, 'vy': 4, 'color': 5}
Parallel elapsed time: 0.236945

--------------------------------------------------------------------------------

{'x': -29.0, 'y': -28.0, 'vx': 3, 'vy': 4, 'color': 5}
Parallel2 elapsed time: 0.1460790000000000

我对所有内容都使用了无意义的参数,但是您可以看到,当您拥有像 @ 这样微不足道的工作函数时,无法通过并行处理 1,000 个调用来补偿处理主进程对象的序列化/反序列化和更新的开销987654329@.

请注意,使用方法 apply_async 的基准 Parallel2 在这种情况下实际上比使用方法 map 的基准 Parallel 性能更高,这有点令人惊讶。我的猜测是,这部分是由于必须使用方法functools.partialarg_list 的形式将额外的、不变的wht 参数传递给工作函数@987654337 @,它提供了所需的附加函数调用。因此,基准 Parallel 必须为每次球更新进行总共两个函数调用。

【讨论】:

  • 谢谢,布布。当您@classmethod 时,我发现了 Class 和 Instance 变量的问题。它在 C++ 引擎中完成的方式。我们在游戏类的程序开始时生成池。我们每个流程核心使用一个。然后我们在每个循环的进程之间分配负载。每个线程或进程都有自己的队列,我们​​可以将任务转储到其中。这样,工作就在 CPU 之间分配。 Sean Parent 有一个很好的视频,涵盖了性能差异。 youtube.com/watch?v=zULU6Hhp42w 没有 async 的时间是 1.53e-6 Async 实际上会减慢它的速度。
  • 我猜 python 中的异步与 C++ 中的异步非常相似,它动态生成线程/进程。有没有办法像我们在 C++ 中那样做。我们是否在游戏开始时生成池和工作程序,每个进程都有自己的队列。然后在更新函数中转储我们想要的任何工作负载。从我一直在阅读的内容来看,当涉及到多处理时,您使用队列来表示与 c++ 不同的东西。我们用它来存储任务,而不是共享内存。该任务包含共享内存。
  • 没有。当您有 N 个进程/线程池(在我发布的演示中 N 为 2)时,您看不到底层的输入和输出队列。进程在一开始就被创建,它们每个都阻塞等待从输入队列中读取下一个任务(当使用map提交任务时,它会阻塞直到返回结果,或者@987654339 @ iterable 参数的 N 个元素所暗示的 N 个任务被分解为由 chunksize 参数确定的大小块,并以这些块的形式写入队列以最小化进程间传输的数量。(更多...)
  • 是的。我的结论是,您的更新方法的 CPU 密集程度不足以从多处理中受益。如果您认为这是合理的,您可以接受答案,直到出现更好的答案。
  • 考虑到通信的开销,那么在 python 进程中,我需要一个进程在一次调用中处理一种类型的所有更新。这意味着我可以在进程中更新 AI,一个完成所有物理,一个处理运动和碰撞,只要我在一次调用中将 1000 的所有数据发送给它。这样,调用较小的 balls.update 的 for 循环在进程中有效地完成,而不是在将其发送给它时完成。
猜你喜欢
  • 2022-01-06
  • 2021-09-10
  • 2013-10-12
  • 2019-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-09
  • 1970-01-01
相关资源
最近更新 更多