3

Я использовал этот script (см. Код в конце), чтобы оценить, является ли глобальный объект общим или скопированным, когда родительский процесс разветвляется.Многопроцессорность: почему массив numpy используется совместно с дочерними процессами, а список скопирован?

Вкратце, сценарий создает глобальный объект data, а дочерний процесс обрабатывает итерацию по data. Сценарий также отслеживает использование памяти для оценки того, был ли объект скопирован в дочерние процессы.

Вот результаты:

  1. data = np.ones((N,N)). Операция в дочернем процессе: data.sum(). Результат: data является общий (no copy)
  2. data = list(range(pow(10, 8))). Операция в дочернем процессе: sum(data). Результат: data Номер копировано.
  3. data = list(range(pow(10, 8))). Операция в дочернем процессе: for x in data: pass. Результат: data Номер копировано.

Результат 1) ожидается из-за копирования на запись. Я немного озадачен результатами 2) и 3). Почему data скопирован?


Script

source

import multiprocessing as mp 
import numpy as np 
import logging 
import os 

logger = mp.log_to_stderr(logging.WARNING) 

def free_memory(): 
    total = 0 
    with open('/proc/meminfo', 'r') as f: 
     for line in f: 
      line = line.strip() 
      if any(line.startswith(field) for field in ('MemFree', 'Buffers', 'Cached')): 
       field, amount, unit = line.split() 
       amount = int(amount) 
       if unit != 'kB': 
        raise ValueError(
         'Unknown unit {u!r} in /proc/meminfo'.format(u = unit)) 
       total += amount 
    return total 

def worker(i): 
    x = data.sum() # Exercise access to data 
    logger.warn('Free memory: {m}'.format(m = free_memory())) 

def main(): 
    procs = [mp.Process(target = worker, args = (i,)) for i in range(4)] 
    for proc in procs: 
     proc.start() 
    for proc in procs: 
     proc.join() 

logger.warn('Initial free: {m}'.format(m = free_memory())) 
N = 15000 
data = np.ones((N,N)) 
logger.warn('After allocating data: {m}'.format(m = free_memory())) 

if __name__ == '__main__': 
    main() 

Подробные результаты

Run 1 выход

[WARNING/MainProcess] Initial free: 25.1 GB [WARNING/MainProcess] After allocating data: 23.3 GB [WARNING/Process-2] Free memory: 23.3 GB [WARNING/Process-4] Free memory: 23.3 GB [WARNING/Process-1] Free memory: 23.3 GB [WARNING/Process-3] Free memory: 23.3 GB

Run 2 Выход

[WARNING/MainProcess] Initial free: 25.1 GB [WARNING/MainProcess] After allocating data: 21.9 GB [WARNING/Process-2] Free memory: 12.6 GB [WARNING/Process-4] Free memory: 12.7 GB [WARNING/Process-1] Free memory: 16.3 GB [WARNING/Process-3] Free memory: 17.1 GB

Run 3 Выход

[WARNING/MainProcess] Initial free: 25.1 GB [WARNING/MainProcess] After allocating data: 21.9 GB [WARNING/Process-2] Free memory: 12.6 GB [WARNING/Process-4] Free memory: 13.1 GB [WARNING/Process-1] Free memory: 14.6 GB [WARNING/Process-3] Free memory: 19.3 GB

ответ

3

Они все копирования на записи. Что вам не хватает в том, что когда вы делаете, например,

for x in data: 
    pass 

счетчик ссылок на каждый объект, содержащийся в data временно увеличивается на 1, по одному за раз, так как x связан с каждым объектом, в свою очередь. Для объектов int, refcount в CPython является частью основного макета объекта, поэтому объект копируется (вы сделали мутировать его, потому что пересчет изменяется).

Чтобы сделать что-то более похожее на корпус numpy.ones, попробуйте, например.,

data = [1] * 10**8 

Тогда есть только один уникальный объект ссылается много (10**8) раз в списке, так что есть очень мало, чтобы скопировать (RefCount одного и того же объекта получает приращение и уменьшается во много раз).

+0

Хорошо, поэтому я не могу повторить или сделать простой поиск без запуска копий. Разве это не делает копирование на запись близким к бесполезному? –

+2

COW (copy-on-write) - это концепция ОС, которую Python наследует от 'fork()' на платформах, поддерживающих эту функцию. COW не был разработан с учетом многопроцессорности Python (mp), и mp Python не был разработан с учетом COW ;-) COW - это специфичная для платформы вещь, которая может быть или не быть полезной, в зависимости от приложения. Обратите внимание, что для многих типов объектов в CPython, refcount _not_ хранится «с основной массой данных объекта». В любом случае, COW первоначально была изобретена для реализации exec-after-fork, где преимущество заключается в том, что большая часть данных родительского процесса никогда не упоминается. –