แชร์อาร์เรย์ Numpy ขนาดใหญ่ที่อ่านอย่างเดียวระหว่างกระบวนการหลายกระบวนการ


90

ฉันมี SciPy Array (เมทริกซ์) 60GB ฉันต้องแชร์ระหว่างmultiprocessing Processวัตถุมากกว่า5 ชิ้น ฉันเคยเห็น numpy-sharedmem และอ่านการสนทนานี้ในรายการ SciPy ดูเหมือนจะมีสองวิธีคือnumpy-sharedmemใช้ a multiprocessing.RawArray()และแมป NumPy dtypes ถึงctypes ตอนนี้numpy-sharedmemดูเหมือนจะเป็นทางไป แต่ฉันยังไม่เห็นตัวอย่างอ้างอิงที่ดี ฉันไม่ต้องการการล็อกใด ๆ เนื่องจากอาร์เรย์ (จริงๆแล้วคือเมทริกซ์) จะเป็นแบบอ่านอย่างเดียว ตอนนี้เนื่องจากขนาดของมันฉันต้องการหลีกเลี่ยงการทำสำเนา มันเสียงเหมือนวิธีที่ถูกต้องคือการสร้างเพียงสำเนาของอาร์เรย์เป็นsharedmemอาร์เรย์แล้วส่งผ่านไปยังProcessวัตถุ? คำถามเฉพาะสองสามข้อ:

  1. อะไรคือวิธีที่ดีที่สุดในการส่งแฮนเดิลแชร์ไปยังProcess()รายการย่อย ฉันต้องการคิวเพื่อส่งอาร์เรย์หนึ่งรอบหรือไม่? ท่อจะดีกว่าไหม? ฉันสามารถส่งเป็นอาร์กิวเมนต์ไปยังProcess()init ของคลาสย่อยได้หรือไม่ (โดยที่ฉันสมมติว่ามันถูกดอง)

  2. ในการสนทนาที่ฉันเชื่อมโยงไว้ข้างต้นมีการระบุว่าnumpy-sharedmemไม่ปลอดภัย 64 บิต? ฉันกำลังใช้โครงสร้างบางอย่างที่ไม่สามารถระบุแอดเดรสแบบ 32 บิตได้

  3. มีข้อแลกเปลี่ยนกับRawArray()แนวทางนี้หรือไม่? ช้ากว่า buggier?

  4. ฉันต้องการการแมป ctype-to-dtype สำหรับเมธอด numpy-sharedmem หรือไม่?

  5. ใครมีตัวอย่างโค้ด OpenSource ที่ทำแบบนี้บ้าง? ฉันเรียนรู้แบบลงมือปฏิบัติจริงและเป็นการยากที่จะทำงานนี้โดยไม่มีตัวอย่างที่ดีให้ดู

หากมีข้อมูลเพิ่มเติมที่ฉันสามารถให้เพื่อช่วยชี้แจงเรื่องนี้ให้กับผู้อื่นโปรดแสดงความคิดเห็นและฉันจะเพิ่ม ขอบคุณ!

สิ่งนี้จำเป็นต้องทำงานบน Ubuntu Linux และอาจเป็น Mac OS แต่การพกพาไม่ใช่เรื่องใหญ่


1
หากกระบวนการต่างๆกำลังจะเขียนไปยังอาร์เรย์นั้นให้คาดหวังว่าmultiprocessingจะทำสำเนาของสิ่งทั้งหมดสำหรับแต่ละกระบวนการ
tiago

3
@tiago: "ฉันไม่ต้องการการล็อกใด ๆ เนื่องจากอาร์เรย์ (จริงๆแล้วคือเมทริกซ์) จะอ่านได้อย่างเดียว"
ดร. Jan-Philip Gehrcke

1
@tiago: เช่นกันการประมวลผลหลายขั้นตอนไม่ได้ทำสำเนาตราบเท่าที่ไม่ได้บอกอย่างชัดเจน (ผ่านอาร์กิวเมนต์ไปยัง the target_function) ระบบปฏิบัติการจะคัดลอกบางส่วนของหน่วยความจำของผู้ปกครองไปยังพื้นที่หน่วยความจำของเด็กเมื่อมีการปรับเปลี่ยนเท่านั้น
Dr. Jan-Philip Gehrcke

3
นี่เป็นตัวอย่างที่ RawArray ตามที่ควรจะทำงานทั้งบนระวัง * และ Windows, และยังสนับสนุนการเขียนไปยังอาร์เรย์
jfs

ฉันเคยถามคำถามสองสาม ข้อเกี่ยวกับเรื่องนี้มาก่อน สามารถพบวิธีแก้ปัญหาของฉันได้ที่นี่: github.com/david-hoffman/peaks/blob/… (ขออภัยรหัสเป็นภัยพิบัติ)
David Hoffman

คำตอบ:


30

@Velimir Mlaker ให้คำตอบที่ดี ฉันคิดว่าฉันสามารถเพิ่มความคิดเห็นและตัวอย่างเล็ก ๆ น้อย ๆ ได้

(ฉันไม่พบเอกสารเกี่ยวกับ sharedmem มากนัก - นี่คือผลการทดลองของฉันเอง)

  1. คุณจำเป็นต้องส่งแฮนเดิลเมื่อกระบวนการย่อยเริ่มต้นหรือหลังจากเริ่มต้นแล้ว? ถ้ามันเป็นเพียงแค่อดีตคุณก็สามารถใช้targetและข้อโต้แย้งargs Processซึ่งอาจดีกว่าการใช้ตัวแปรส่วนกลาง
  2. จากหน้าการสนทนาที่คุณเชื่อมโยงดูเหมือนว่ามีการเพิ่มการรองรับ Linux 64 บิตไปยัง sharedmem ในขณะที่ย้อนกลับไปดังนั้นจึงอาจไม่ใช่ปัญหา
  3. ฉันไม่รู้เกี่ยวกับเรื่องนี้
  4. ไม่โปรดดูตัวอย่างด้านล่าง

ตัวอย่าง

#!/usr/bin/env python
from multiprocessing import Process
import sharedmem
import numpy

def do_work(data, start):
    data[start] = 0;

def split_work(num):
    n = 20
    width  = n/num
    shared = sharedmem.empty(n)
    shared[:] = numpy.random.rand(1, n)[0]
    print "values are %s" % shared

    processes = [Process(target=do_work, args=(shared, i*width)) for i in xrange(num)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print "values are %s" % shared
    print "type is %s" % type(shared[0])

if __name__ == '__main__':
    split_work(4)

เอาต์พุต

values are [ 0.81397784  0.59667692  0.10761908  0.6736734   0.46349645  0.98340718
  0.44056863  0.10701816  0.67167752  0.29158274  0.22242552  0.14273156
  0.34912309  0.43812636  0.58484507  0.81697513  0.57758441  0.4284959
  0.7292129   0.06063283]
values are [ 0.          0.59667692  0.10761908  0.6736734   0.46349645  0.
  0.44056863  0.10701816  0.67167752  0.29158274  0.          0.14273156
  0.34912309  0.43812636  0.58484507  0.          0.57758441  0.4284959
  0.7292129   0.06063283]
type is <type 'numpy.float64'>

คำถามที่เกี่ยวข้องนี้อาจเป็นประโยชน์


38

หากคุณใช้ Linux (หรือระบบที่รองรับ POSIX) คุณสามารถกำหนดอาร์เรย์นี้เป็นตัวแปรส่วนกลางได้ multiprocessingกำลังใช้fork()บน Linux เมื่อเริ่มกระบวนการย่อยใหม่ กระบวนการย่อยที่สร้างใหม่จะแชร์หน่วยความจำกับพาเรนต์โดยอัตโนมัติตราบเท่าที่ไม่เปลี่ยนแปลง ( กลไกการคัดลอกเมื่อเขียน )

เนื่องจากคุณกำลังพูดว่า "ฉันไม่ต้องการการล็อกใด ๆ เนื่องจากอาร์เรย์ (จริงๆแล้วคือเมทริกซ์) จะเป็นแบบอ่านอย่างเดียว" การใช้ประโยชน์จากพฤติกรรมนี้จะเป็นวิธีที่ง่ายและมีประสิทธิภาพมาก: กระบวนการย่อยทั้งหมดจะเข้าถึง ข้อมูลเดียวกันในหน่วยความจำกายภาพเมื่ออ่านอาร์เรย์จำนวนมากนี้

อย่าส่งอาร์เรย์ของคุณให้กับคอนProcess()สตรัคเตอร์สิ่งนี้จะสั่งmultiprocessingให้pickleข้อมูลไปยังเด็กซึ่งจะไม่มีประสิทธิภาพอย่างมากหรือเป็นไปไม่ได้ในกรณีของคุณ บน Linux ขวาหลังจากที่fork()เด็กเป็นสำเนาถูกต้องของผู้ปกครองใช้หน่วยความจำทางกายภาพเดียวกันดังนั้นสิ่งที่คุณต้องทำคือการทำให้แน่ใจว่างูหลามตัวแปร 'ที่มี' เมทริกซ์สามารถเข้าถึงได้จากภายในฟังก์ชั่นที่คุณมอบให้target Process()โดยทั่วไปคุณสามารถทำได้ด้วยตัวแปร 'global'

รหัสตัวอย่าง:

from multiprocessing import Process
from numpy import random


global_array = random.random(10**4)


def child():
    print sum(global_array)


def main():
    processes = [Process(target=child) for _ in xrange(10)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

ใน Windows - ซึ่งไม่สนับสนุนfork()- ใช้เรียกmultiprocessing API win32 CreateProcessมันสร้างกระบวนการใหม่ทั้งหมดจากไฟล์ปฏิบัติการที่กำหนด นั่นเป็นเหตุผลว่าทำไมบน Windows จึงจำเป็นต้องดองข้อมูลให้กับเด็กหากต้องการข้อมูลที่สร้างขึ้นระหว่างรันไทม์ของผู้ปกครอง


3
Copy-on-write จะคัดลอกหน้าที่มีตัวนับอ้างอิง (ดังนั้น python ที่แยกแต่ละตัวจะมีตัวนับอ้างอิงของตัวเอง) แต่จะไม่คัดลอกอาร์เรย์ข้อมูลทั้งหมด
ปล้น

1
ฉันจะเพิ่มว่าฉันประสบความสำเร็จกับตัวแปรระดับโมดูลมากกว่าตัวแปรส่วนกลาง ... คือเพิ่มตัวแปรลงในโมดูลในขอบเขตทั่วโลกก่อนที่จะแยก
ปล้น

5
คำเตือนสำหรับผู้ที่สะดุดกับคำถาม / คำตอบนี้: หากคุณบังเอิญใช้ OpenBLAS-linked Numpy สำหรับการทำงานแบบหลายเธรดตรวจสอบให้แน่ใจว่าได้ปิดการใช้งานมัลติเธรด (ส่งออก OPENBLAS_NUM_THREADS = 1) เมื่อใช้multiprocessingหรือกระบวนการย่อยอาจจบลงด้วยการหยุดทำงาน ( โดยทั่วไปจะใช้ 1 / n ของโปรเซสเซอร์หนึ่งตัวแทนที่จะเป็น n โปรเซสเซอร์) ในการดำเนินการพีชคณิตเชิงเส้นบนอาร์เรย์ / เมทริกซ์ส่วนกลางที่แบ่งใช้ ที่รู้จักกันในความขัดแย้งกับ multithreaded OpenBLASดูเหมือนว่าจะขยายไปยังหลามmultiprocessing
Dologan

1
ใครช่วยอธิบายได้ไหมว่าทำไม python ถึงไม่ใช้ OS forkเพื่อส่งผ่านพารามิเตอร์ที่กำหนดให้Processแทนที่จะทำให้เป็นอนุกรม นั่นคือไม่forkสามารถนำไปใช้กับกระบวนการหลักก่อนที่จะ childถูกเรียกเพื่อให้ค่าพารามิเตอร์ยังคงพร้อมใช้งานจากระบบปฏิบัติการ? ดูเหมือนจะมีประสิทธิภาพมากกว่าการทำให้เป็นอนุกรมหรือไม่?
สูงสุด

2
เราทุกคนทราบดีว่าfork()ไม่มีใน Windows มีการระบุไว้ในคำตอบของฉันและหลายครั้งในความคิดเห็น ฉันรู้ว่านี่เป็นคำถามเริ่มต้นของคุณและฉันได้ตอบความเห็นสี่ข้อข้างต้นนี้ : "การประนีประนอมคือการใช้วิธีการเดียวกันในการถ่ายโอนพารามิเตอร์บนทั้งสองแพลตฟอร์มโดยค่าเริ่มต้นเพื่อการบำรุงรักษาที่ดีขึ้นและเพื่อให้แน่ใจว่ามีพฤติกรรมที่เท่าเทียมกัน" ทั้งสองวิธีมีข้อดีและข้อเสียซึ่งเป็นเหตุผลว่าทำไมใน Python 3 จึงมีความยืดหยุ่นมากขึ้นสำหรับผู้ใช้ในการเลือกวิธี การสนทนานี้ไม่มีประสิทธิผลโดยไม่มีรายละเอียดการพูดคุยซึ่งเราไม่ควรทำที่นี่
Dr. Jan-Philip Gehrcke

24

คุณอาจสนใจโค้ดชิ้นเล็ก ๆ ที่ฉันเขียน: github.com/vmlaker/benchmark-sharedmem

ไฟล์เดียวที่น่าสนใจคือmain.py. เป็นเกณฑ์มาตรฐานของnumpy-sharedmem - รหัสเพียงแค่ส่งอาร์เรย์ (อย่างใดอย่างหนึ่งnumpyหรือsharedmem) ไปยังกระบวนการที่สร้างผ่าน Pipe คนงานเพียงแค่เรียกsum()ข้อมูล ฉันสนใจเพียงการเปรียบเทียบเวลาในการสื่อสารข้อมูลระหว่างการใช้งานทั้งสอง

ฉันยังเขียนอื่น ๆ , รหัสที่ซับซ้อนมากขึ้น: github.com/vmlaker/sherlock

ที่นี่ฉันใช้โมดูลnumpy-sharedmemสำหรับการประมวลผลภาพแบบเรียลไทม์ด้วย OpenCV - รูปภาพเป็นอาร์เรย์ NumPy ตามcv2API ที่ใหม่กว่าของ OpenCV รูปภาพที่อ้างอิงตามความเป็นจริงถูกแชร์ระหว่างกระบวนการต่างๆผ่านอ็อบเจกต์พจนานุกรมที่สร้างขึ้นจากmultiprocessing.Manager(ซึ่งต่างจากการใช้ Queue หรือ Pipe) ฉันได้รับการปรับปรุงประสิทธิภาพที่ยอดเยี่ยมเมื่อเปรียบเทียบกับการใช้อาร์เรย์ NumPy ธรรมดา

ท่อเทียบกับคิว :

จากประสบการณ์ของฉัน IPC with Pipe เร็วกว่า Queue และนั่นก็สมเหตุสมผลเนื่องจาก Queue เพิ่มการล็อกเพื่อให้ปลอดภัยสำหรับผู้ผลิต / ผู้บริโภคหลายราย ท่อไม่ได้ แต่ถ้าคุณมีเพียงสองกระบวนการที่พูดคุยไปมาก็ปลอดภัยที่จะใช้ Pipe หรือตามที่เอกสารอ่าน:

... ไม่มีความเสี่ยงจากการทุจริตจากกระบวนการที่ใช้ปลายท่อต่างกันในเวลาเดียวกัน

sharedmemความปลอดภัย :

ปัญหาหลักเกี่ยวกับsharedmemโมดูลคือความเป็นไปได้ที่หน่วยความจำจะรั่วไหลเมื่อออกจากโปรแกรมที่ไม่เหมาะสม นี้จะอธิบายในการอภิปรายที่มีความยาวที่นี่ แม้ว่าเมื่อวันที่ 10 เมษายน 2011 Sturla กล่าวถึงการแก้ไขหน่วยความจำรั่ว แต่ฉันก็ยังคงพบการรั่วไหลตั้งแต่นั้นมาโดยใช้ทั้ง repos ซึ่งเป็นของ Sturla Molden บน GitHub ( github.com/sturlamolden/sharedmem-numpy ) และ Chris Lee-Messer บน Bitbucket ( bitbucket.org/cleemesser/numpy-sharedmem )


ขอบคุณข้อมูลมาก อย่างไรก็ตามการรั่วไหลของหน่วยความจำsharedmemดูเหมือนจะเป็นเรื่องใหญ่ มีโอกาสในการแก้ปัญหาหรือไม่
Will

1
นอกเหนือจากการสังเกตการรั่วไหลแล้วฉันยังไม่ได้ค้นหามันในรหัส ฉันได้เพิ่มคำตอบของฉันภายใต้ "ความปลอดภัยที่ใช้ร่วมกัน" ด้านบนผู้ดูแล repos โอเพ่นซอร์สสองรายการของsharedmemโมดูลเพื่อการอ้างอิง
Velimir Mlaker

15

หากอาร์เรย์ของคุณมีขนาดใหญ่คุณสามารถnumpy.memmapใช้ได้ ตัวอย่างเช่นหากคุณมีอาร์เรย์เก็บไว้ในดิสก์เช่น'test.array'คุณสามารถใช้กระบวนการพร้อมกันเพื่อเข้าถึงข้อมูลในนั้นได้แม้ในโหมด "เขียน" แต่กรณีของคุณจะง่ายกว่าเนื่องจากคุณต้องใช้โหมด "การอ่าน" เท่านั้น

การสร้างอาร์เรย์:

a = np.memmap('test.array', dtype='float32', mode='w+', shape=(100000,1000))

จากนั้นคุณสามารถเติมอาร์เรย์นี้ในลักษณะเดียวกับที่คุณทำกับอาร์เรย์ธรรมดา ตัวอย่างเช่น:

a[:10,:100]=1.
a[10:,100:]=2.

aข้อมูลจะถูกเก็บไว้ในดิสก์เมื่อคุณลบตัวแปร

ในภายหลังคุณสามารถใช้กระบวนการต่างๆที่จะเข้าถึงข้อมูลในtest.array:

# read-only mode
b = np.memmap('test.array', dtype='float32', mode='r', shape=(100000,1000))

# read and writing mode
c = np.memmap('test.array', dtype='float32', mode='r+', shape=(100000,1000))

คำตอบที่เกี่ยวข้อง:


ดังนั้นในกรณีนี้กระบวนการทั้งหมดจะสามารถเข้าถึงnp.memmapวัตถุเดียวกันได้โดยไม่ต้องจำลองแบบและไม่ต้องผ่านวัตถุอย่างใด?
Ataxias

3

คุณอาจพบว่ามีประโยชน์ในการดูเอกสารประกอบสำหรับpyroเช่นถ้าคุณแบ่งงานของคุณได้อย่างเหมาะสมคุณสามารถใช้เพื่อดำเนินการส่วนต่างๆบนเครื่องต่าง ๆ รวมทั้งบนคอร์ต่างๆในเครื่องเดียวกัน


0

ทำไมไม่ใช้มัลติเธรด ทรัพยากรของกระบวนการหลักสามารถใช้ร่วมกันได้โดยเธรดโดยกำเนิดดังนั้นการแสดงหลายเธรดจึงเป็นวิธีที่ดีกว่าในการแบ่งปันวัตถุที่เป็นของกระบวนการหลัก

หากคุณกังวลเกี่ยวกับกลไก GIL ของ python คุณอาจใช้วิธีnogilการnumba.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.