在 CAD 应用程序中的进程之间共享 class 个实例
sharing class instance among processes in CAD application
我正在尝试在我的 rendering/modeling(CAD) 应用程序中实现多处理功能。我确实了解一些 threading
,但对 multiprocessing
还是很陌生。所以同时存在设计问题和学习问题。
我想先解决学习问题,然后再解决真正的设计问题,因为这有助于理解真正的设计问题。
为了概括我的设计问题,我编写了以下示例,包括 Pool
和 Manager
:
import multiprocessing as mp
from multiprocessing.managers import BaseManager
import time
class A:
def __init__(self):
self.count = 0
def accum(self):
self.count += 1
def get(self):
return self.count
class MyManager(BaseManager):
pass
MyManager.register('kkk', A)
def count(obj, id):
print(id, 'start')
for _ in range(100_000):
obj.accum()
return id
if __name__ == '__main__':
print('run single process')
st = time.time()
c = 0
for _ in range(10):
for __ in range(100_000):
c += 1
print(c, f'elapse: {time.time()-st}')
print()
print('run multi process')
st = time.time()
pool = mp.Pool()
with MyManager() as manager:
obj = manager.kkk()
ps = [pool.apply_async(count, args=(obj, i)) for i in range(10)]
print('waiting...')
pool.close()
pool.join()
print(obj.get(), f'elapse: {time.time()-st}')
这是来自 python 多处理文档的略微修改的测试代码。
结果如下:
run single process
1000000 elapse: 0.10909914970397949
run multi process
waiting...
0 start
1 start
4 start
3 start
2 start
5 start
6 start
7 start
8 start
9 start
1000000 elapse: 36.15581750869751
所以,多处理在这里很慢。我读过这个和那个但不能真正建立关于多处理的直觉。这个结果是酸洗造成的吗?还是我错过了其他东西?我知道这不是通过多处理执行的好任务。但是,有没有办法在这种类型的多处理场景中缩短执行时间?
这个例子的主要关注点是找出通过进程共享对象的好方法。当主进程绘制一个帧(如电影文件中的连续图像)时,我认为如果应用程序可以将后台任务委托给另一个进程会很好。任务可以是修改几何实体或测试各种几何属性。显然,这些后台进程必须能够访问预先存在的对象,例如 model
、window
、camera
等才能完成其工作。这些对象又大又复杂,很难退化为简单的列表或其他原语。
那么在这种情况下我可以采取什么方法呢?提前为相当模棱两可的提问道歉。
在这种情况下,缓慢与您的工作函数 count
的参数的 pickling 和 unpickling 无关,而是第一个参数 obj
的性质。我来解释一下:
您只调用了传递给它的辅助函数 10 次 obj
和 i
,它们必须被 pickle 和 unpickled 10 次。这些论点是什么? i
只是一个整数,但 obj
是对 代理 的引用,用于 class A
的实例。同样,pickling 和 unpickling 引用相对微不足道 与为您正在执行的 10 个任务提交中的每一个调用代理调用 100,000 次 相比。如果 A
实例是每个子进程的本地实例(当然,如果是的话,您将无法在 所有 进程之间共享一个实例),那么每个方法调用不会那么贵。但是现在您在代理上调用该方法,导致该方法实际在位于使用语句 obj = manager.kkk()
创建的主进程中的实例上执行。本质上,函数 count
中的语句 obj.accum()
成为远程方法调用。 那个 是花了很多时间的。
更新
我将程序修改为进行 4 次而不是 10 次迭代(我只有 8 个内核,寿命很短),我的时间安排是:
run single process
400000 elapse: 0.0310208797454834
run multi process
waiting...
0 start
1 start
3 start
2 start
400000 elapse: 22.726988077163696
然后我修改了 worker 函数 count
以忽略传递的 obj
参数并仅递增一个局部整数变量,如下所示:
def count(obj, id):
print(id, 'start')
c = 0
for _ in range(100_000):
c += 1
return id
这些是新的时间安排:
run single process
400000 elapse: 0.03300070762634277
run multi process
waiting...
0 start
2 start
3 start
1 start
0 elapse: 0.19895458221435547
很明显,所有时间都花在了执行 obj.accum()
上。由于仅创建进程池所花费的时间,多处理版本仍然需要相当长的时间。如果在创建池之后和提交 4 个任务之前启动时钟 运行,我们将得到:
run single process
400000 elapse: 0.03700995445251465
run multi process
waiting...
0 start
1 start
3 start
2 start
0 elapse: 0.03301692008972168
多处理版本现在略有改进。来回传输参数和结果仍然存在开销。我们必须让开销值得。如果我们执行 4_000_000 次迭代而不是 100_000 次迭代,我们得到:
run single process
16000000 elapse: 1.3639805316925049
run multi process
waiting...
0 start
1 start
2 start
3 start
0 elapse: 0.30300235748291016
我正在尝试在我的 rendering/modeling(CAD) 应用程序中实现多处理功能。我确实了解一些 threading
,但对 multiprocessing
还是很陌生。所以同时存在设计问题和学习问题。
我想先解决学习问题,然后再解决真正的设计问题,因为这有助于理解真正的设计问题。
为了概括我的设计问题,我编写了以下示例,包括 Pool
和 Manager
:
import multiprocessing as mp
from multiprocessing.managers import BaseManager
import time
class A:
def __init__(self):
self.count = 0
def accum(self):
self.count += 1
def get(self):
return self.count
class MyManager(BaseManager):
pass
MyManager.register('kkk', A)
def count(obj, id):
print(id, 'start')
for _ in range(100_000):
obj.accum()
return id
if __name__ == '__main__':
print('run single process')
st = time.time()
c = 0
for _ in range(10):
for __ in range(100_000):
c += 1
print(c, f'elapse: {time.time()-st}')
print()
print('run multi process')
st = time.time()
pool = mp.Pool()
with MyManager() as manager:
obj = manager.kkk()
ps = [pool.apply_async(count, args=(obj, i)) for i in range(10)]
print('waiting...')
pool.close()
pool.join()
print(obj.get(), f'elapse: {time.time()-st}')
这是来自 python 多处理文档的略微修改的测试代码。 结果如下:
run single process
1000000 elapse: 0.10909914970397949
run multi process
waiting...
0 start
1 start
4 start
3 start
2 start
5 start
6 start
7 start
8 start
9 start
1000000 elapse: 36.15581750869751
所以,多处理在这里很慢。我读过这个和那个但不能真正建立关于多处理的直觉。这个结果是酸洗造成的吗?还是我错过了其他东西?我知道这不是通过多处理执行的好任务。但是,有没有办法在这种类型的多处理场景中缩短执行时间?
这个例子的主要关注点是找出通过进程共享对象的好方法。当主进程绘制一个帧(如电影文件中的连续图像)时,我认为如果应用程序可以将后台任务委托给另一个进程会很好。任务可以是修改几何实体或测试各种几何属性。显然,这些后台进程必须能够访问预先存在的对象,例如 model
、window
、camera
等才能完成其工作。这些对象又大又复杂,很难退化为简单的列表或其他原语。
那么在这种情况下我可以采取什么方法呢?提前为相当模棱两可的提问道歉。
在这种情况下,缓慢与您的工作函数 count
的参数的 pickling 和 unpickling 无关,而是第一个参数 obj
的性质。我来解释一下:
您只调用了传递给它的辅助函数 10 次 obj
和 i
,它们必须被 pickle 和 unpickled 10 次。这些论点是什么? i
只是一个整数,但 obj
是对 代理 的引用,用于 class A
的实例。同样,pickling 和 unpickling 引用相对微不足道 与为您正在执行的 10 个任务提交中的每一个调用代理调用 100,000 次 相比。如果 A
实例是每个子进程的本地实例(当然,如果是的话,您将无法在 所有 进程之间共享一个实例),那么每个方法调用不会那么贵。但是现在您在代理上调用该方法,导致该方法实际在位于使用语句 obj = manager.kkk()
创建的主进程中的实例上执行。本质上,函数 count
中的语句 obj.accum()
成为远程方法调用。 那个 是花了很多时间的。
更新
我将程序修改为进行 4 次而不是 10 次迭代(我只有 8 个内核,寿命很短),我的时间安排是:
run single process
400000 elapse: 0.0310208797454834
run multi process
waiting...
0 start
1 start
3 start
2 start
400000 elapse: 22.726988077163696
然后我修改了 worker 函数 count
以忽略传递的 obj
参数并仅递增一个局部整数变量,如下所示:
def count(obj, id):
print(id, 'start')
c = 0
for _ in range(100_000):
c += 1
return id
这些是新的时间安排:
run single process
400000 elapse: 0.03300070762634277
run multi process
waiting...
0 start
2 start
3 start
1 start
0 elapse: 0.19895458221435547
很明显,所有时间都花在了执行 obj.accum()
上。由于仅创建进程池所花费的时间,多处理版本仍然需要相当长的时间。如果在创建池之后和提交 4 个任务之前启动时钟 运行,我们将得到:
run single process
400000 elapse: 0.03700995445251465
run multi process
waiting...
0 start
1 start
3 start
2 start
0 elapse: 0.03301692008972168
多处理版本现在略有改进。来回传输参数和结果仍然存在开销。我们必须让开销值得。如果我们执行 4_000_000 次迭代而不是 100_000 次迭代,我们得到:
run single process
16000000 elapse: 1.3639805316925049
run multi process
waiting...
0 start
1 start
2 start
3 start
0 elapse: 0.30300235748291016