ฉันจะเพิ่มหน่วยความจำอย่างชัดเจนใน Python ได้อย่างไร


387

ฉันเขียนโปรแกรม Python ที่ทำงานกับไฟล์อินพุตขนาดใหญ่เพื่อสร้างวัตถุสองสามล้านชิ้นที่เป็นรูปสามเหลี่ยม อัลกอริทึมคือ:

  1. อ่านไฟล์อินพุต
  2. ประมวลผลไฟล์และสร้างรายการของรูปสามเหลี่ยมแสดงโดยจุดยอดของพวกเขา
  3. เอาท์พุทจุดยอดในรูปแบบ OFF: รายการจุดยอดแล้วตามด้วยรายการรูปสามเหลี่ยม รูปสามเหลี่ยมจะถูกแทนด้วยดัชนีในรายการจุดยอด

ความต้องการของ OFF ที่ฉันพิมพ์รายการจุดยอดทั้งหมดก่อนที่ฉันจะพิมพ์สามเหลี่ยมหมายความว่าฉันต้องเก็บรายการสามเหลี่ยมในหน่วยความจำก่อนที่ฉันจะเขียนเอาต์พุตไปยังไฟล์ ในขณะเดียวกันฉันได้รับข้อผิดพลาดของหน่วยความจำเนื่องจากขนาดของรายการ

เป็นวิธีที่ดีที่สุดในการบอก Python ว่าฉันไม่ต้องการข้อมูลอีกต่อไปและสามารถปลดปล่อยได้อย่างไร


11
ทำไมไม่พิมพ์สามเหลี่ยมออกมาเป็นไฟล์กลางแล้วอ่านมันอีกครั้งเมื่อคุณต้องการ
อลิซเพอร์เซลล์

2
คำถามนี้อาจเป็นสองสิ่งที่แตกต่างกันมาก ข้อผิดพลาดเหล่านั้นมาจากกระบวนการ Python เดียวกันซึ่งในกรณีนี้เราสนใจเกี่ยวกับการเพิ่มหน่วยความจำไปยังฮีทของ Python หรือว่ามาจากกระบวนการที่แตกต่างกันในระบบ
Charles Duffy

คำตอบ:


453

ตามเอกสารหลามอย่างเป็นทางการคุณสามารถบังคับให้เก็บขยะที่จะปล่อยหน่วยความจำ unreferenced gc.collect()กับ ตัวอย่าง:

import gc
gc.collect()

19
อย่างไรก็ตามมีการเก็บขยะบ่อยๆยกเว้นในบางกรณีที่ผิดปกติดังนั้นฉันไม่คิดว่ามันจะช่วยอะไรมากมาย
Lennart Regebro

24
โดยทั่วไปแล้วควรหลีกเลี่ยง gc.collect () ตัวรวบรวมขยะรู้วิธีการทำงาน ที่กล่าวว่าถ้า OP อยู่ในสถานการณ์ที่เขาจัดสรรคืนวัตถุจำนวนมากในทันที (เช่นในล้าน) gc.collect อาจพิสูจน์ได้ว่ามีประโยชน์
เจสันเบเกอร์

164
จริง ๆ แล้วการเรียกgc.collect()ตัวเองเมื่อสิ้นสุดลูปสามารถช่วยหลีกเลี่ยงการแตกแฟรกเมนต์ซึ่งจะช่วยให้ประสิทธิภาพดีขึ้น ฉันเคยเห็นสิ่งนี้สร้างความแตกต่างอย่างมีนัยสำคัญ (~ 20% runtime IIRC)
RobM

38
ฉันใช้ python 3.6 โทรgc.collect()หลังจากโหลดดาต้าดาต้าแพนด้าจาก hdf5 (500k แถว) ลดการใช้หน่วยความจำจาก 1.7GB เป็น 500MB
John

15
ฉันต้องโหลดและประมวลผลอาร์เรย์ numpy จำนวน 25GB ในระบบที่มีหน่วยความจำ 32GB การใช้del my_arrayตามด้วยgc.collect()หลังจากการประมวลผลอาเรย์เป็นวิธีเดียวที่หน่วยความจำได้รับการปล่อยตัวจริงและกระบวนการของฉันยังมีชีวิตอยู่เพื่อโหลดอาเรย์ต่อไป
David

113

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

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

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


31
ฉันแน่ใจว่าต้องการดูตัวอย่างเล็กน้อยของเรื่องนี้
Aaron Hall

3
อย่างจริงจัง. @AaronHall พูดอะไร
Noob Saibot

17
@AaronHall มีตัวอย่างเล็กน้อยพร้อมใช้multiprocessing.Managerแทนที่จะใช้ไฟล์เพื่อใช้สถานะที่ใช้ร่วมกัน
user4815162342

48

delคำสั่งอาจจะมีการใช้งาน แต่ IIRC มันก็ไม่ได้รับประกันว่าจะเพิ่มหน่วยความจำ เอกสารอยู่ที่นี่ ... และทำไมมันจะไม่นำออกอยู่ที่นี่

ฉันเคยได้ยินผู้คนบนระบบ Linux และ Unix ที่ใช้กระบวนการไพ ธ อนเพื่อทำงานบางอย่างรับผลแล้วก็ฆ่ามัน

บทความนี้มีบันทึกในตัวรวบรวมขยะ Python แต่ฉันคิดว่าการขาดการควบคุมหน่วยความจำคือข้อเสียของหน่วยความจำที่มีการจัดการ


IronPython และ Jython จะเป็นอีกทางเลือกหนึ่งในการหลีกเลี่ยงปัญหานี้หรือไม่?
Esteban Küber

@voyager: ไม่มันจะไม่เกิดขึ้น และไม่มีภาษาอื่นใดเลย ปัญหาคือเขาอ่านข้อมูลจำนวนมากในรายการและข้อมูลมีขนาดใหญ่เกินไปสำหรับหน่วยความจำ
Lennart Regebro

1
มันอาจจะแย่กว่านั้นภายใต้ IronPython หรือ Jython ในสภาพแวดล้อมเหล่านั้นคุณไม่ได้รับประกันว่าจะมีการปล่อยหน่วยความจำหากไม่มีสิ่งใดที่อ้างอิงอยู่
เจสันเบเกอร์

@voyager ใช่เพราะเครื่องเสมือน Java มองหาหน่วยความจำที่ว่าง สำหรับ JVM นั้น Jython นั้นไม่มีอะไรพิเศษ ในทางกลับกัน JVM มีส่วนแบ่งในตัวเองเช่นคุณต้องประกาศล่วงหน้าว่ากองใหญ่สามารถใช้
ศ. Falken ผิดสัญญา

32

Python เป็นที่เก็บขยะดังนั้นหากคุณลดขนาดรายการของคุณมันจะเรียกคืนหน่วยความจำ คุณยังสามารถใช้คำสั่ง "del" เพื่อกำจัดตัวแปรโดยสมบูรณ์:

biglist = [blah,blah,blah]
#...
del biglist

18
นี่คือและไม่เป็นความจริง ในขณะที่การลดขนาดของรายการช่วยให้สามารถเรียกคืนหน่วยความจำได้ไม่มีการรับประกันว่าจะเกิดอะไรขึ้น
user142350

3
ไม่ แต่โดยปกติแล้วจะช่วยได้ อย่างไรก็ตามในขณะที่ฉันเข้าใจคำถามที่นี่ปัญหาคือเขาต้องมีวัตถุจำนวนมากที่เขามีหน่วยความจำไม่เพียงพอก่อนที่จะประมวลผลพวกเขาทั้งหมดถ้าเขาอ่านพวกเขาลงในรายการ การลบรายการก่อนที่จะทำการประมวลผลเสร็จสิ้นไม่น่าจะเป็นโซลูชันที่มีประโยชน์ ;)
Lennart Regebro

3
เงื่อนไขหน่วยความจำไม่เพียงพอ / หน่วยความจำไม่เพียงพอจะเรียก "การเรียกใช้ฉุกเฉิน" ของตัวรวบรวมขยะหรือไม่
Jeremy Friesner

4
biglist = [] จะปล่อยหน่วยความจำหรือไม่
neouyghur

3
ใช่ถ้ารายการเก่าไม่ได้อ้างอิงโดยอย่างอื่น
Ned Batchelder

22

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

ในกรณีของคุณเมื่อคุณต้องการรายการที่มีขนาดใหญ่โดยทั่วไปคุณจะต้องจัดระเบียบรหัสใหม่โดยทั่วไปจะใช้เครื่องกำเนิดไฟฟ้า / ตัววนซ้ำแทน ด้วยวิธีนี้คุณไม่จำเป็นต้องมีรายการขนาดใหญ่ในหน่วยความจำเลย

http://www.prasannatech.net/2009/07/introduction-python-generators.html


1
หากวิธีนี้เป็นไปได้ก็น่าจะคุ้มค่า แต่ควรสังเกตว่าคุณไม่สามารถเข้าถึงตัววนซ้ำแบบสุ่มซึ่งอาจทำให้เกิดปัญหา
เจสันเบเกอร์

นั่นเป็นเรื่องจริงและหากจำเป็นก็ให้ทำการเข้าถึงชุดข้อมูลขนาดใหญ่แบบสุ่มมีแนวโน้มว่าจะต้องใช้ฐานข้อมูลบางประเภท
Lennart Regebro

คุณสามารถใช้ตัววนซ้ำเพื่อแยกชุดย่อยแบบสุ่มของตัววนซ้ำอื่น ๆ ได้อย่างง่ายดาย
S.Lott

จริง แต่คุณจะต้องวนซ้ำทุกอย่างเพื่อให้ได้เซ็ตย่อยซึ่งจะช้ามาก
Lennart Regebro

21

( delอาจเป็นเพื่อนของคุณเนื่องจากเป็นเครื่องหมายของวัตถุที่ถูกลบเมื่อไม่มีการอ้างอิงถึงพวกเขาตอนนี้บ่อยครั้งที่ล่าม CPython เก็บหน่วยความจำนี้ไว้เพื่อใช้ในภายหลังดังนั้นระบบปฏิบัติการของคุณอาจไม่เห็นหน่วยความจำ "อิสระ")

บางทีคุณอาจไม่พบปัญหาหน่วยความจำใด ๆ ในตอนแรกโดยใช้โครงสร้างที่กะทัดรัดกว่าสำหรับข้อมูลของคุณ ดังนั้นรายการของตัวเลขมีหน่วยความจำมีประสิทธิภาพน้อยกว่ารูปแบบที่ใช้ตามมาตรฐานarrayโมดูลหรือบุคคลที่สามnumpyโมดูล คุณจะประหยัดหน่วยความจำด้วยการใส่จุดยอดในอาร์เรย์ NumPy 3xN และสามเหลี่ยมของคุณในอาร์เรย์ N-element


ใช่มั้ย? การรวบรวมขยะของ CPython นั้นใช้การคำนวณซ้ำ ไม่ใช่การทำเครื่องหมาย - และ - กวาดเป็นระยะ (สำหรับการใช้งาน JVM ทั่วไป) แต่จะลบบางสิ่งทันทีที่จำนวนการอ้างอิงนับเป็นศูนย์ รอบเท่านั้น (ที่ refcounts จะเป็นศูนย์ แต่ไม่ใช่เพราะลูปในทรีอ้างอิง) ต้องการการบำรุงรักษาเป็นระยะ delไม่ได้ทำสิ่งใด ๆ ที่เพิ่งกำหนดค่าที่แตกต่างให้กับชื่อทั้งหมดที่อ้างถึงวัตถุจะไม่
Charles Duffy

ฉันเห็นว่าคุณมาจากไหน: ฉันจะอัปเดตคำตอบตามนั้น ฉันเข้าใจว่าล่าม CPython ใช้งานได้จริงในระดับกลาง: delปลดปล่อยหน่วยความจำจากมุมมองของ Python แต่โดยทั่วไปไม่ได้มาจากมุมมองของ C runtime library หรือ OS ' อ้างอิง: stackoverflow.com/a/32167625/4297 , effbot.org/pyfaq/...
Eric O Lebigot

เห็นด้วยกับเนื้อหาของลิงก์ของคุณ แต่สมมติว่า OP กำลังพูดถึงข้อผิดพลาดที่ได้รับจากกระบวนการ Python เดียวกันความแตกต่างระหว่างการเพิ่มหน่วยความจำไปยังฮีปกระบวนการท้องถิ่นและระบบปฏิบัติการดูเหมือนจะไม่เกี่ยวข้องกัน ( การทำให้ฮีปว่างทำให้มีพื้นที่ว่างสำหรับการจัดสรรใหม่ภายในกระบวนการ Python นั้น) และสำหรับสิ่งนั้นdelก็มีประสิทธิภาพเท่าเทียมกันกับการออกจากขอบเขตการมอบหมายใหม่และอื่น ๆ
Charles Duffy

11

ฉันมีปัญหาคล้ายกันในการอ่านกราฟจากไฟล์ การประมวลผลรวมถึงการคำนวณเมทริกซ์โฟลตขนาด 200 000x200 000 (หนึ่งบรรทัดต่อครั้ง) ที่ไม่พอดีกับหน่วยความจำ การพยายามเพิ่มหน่วยความจำระหว่างการคำนวณโดยใช้การgc.collect()แก้ไขด้านที่เกี่ยวข้องกับหน่วยความจำของปัญหา แต่มันทำให้เกิดปัญหาประสิทธิภาพ: ฉันไม่รู้ว่าทำไมถึงแม้ว่าจำนวนหน่วยความจำที่ใช้ยังคงไม่เปลี่ยนแปลงการโทรใหม่แต่ละครั้งgc.collect()ใช้เวลานานกว่า อันก่อนหน้า ดังนั้นการรวบรวมขยะอย่างรวดเร็วค่อนข้างใช้เวลาส่วนใหญ่ในการคำนวณ

ในการแก้ไขปัญหาหน่วยความจำและประสิทธิภาพฉันเปลี่ยนไปใช้เคล็ดลับมัลติเธรดที่ฉันอ่านที่ไหนสักแห่ง (ขออภัยด้วยที่ฉันไม่พบโพสต์ที่เกี่ยวข้องอีกต่อไป) ก่อนที่ฉันจะอ่านแต่ละบรรทัดของไฟล์ในforลูปขนาดใหญ่ประมวลผลและรันgc.collect()ทุก ๆ ครั้งเพื่อเพิ่มพื้นที่หน่วยความจำ ตอนนี้ฉันเรียกใช้ฟังก์ชันที่อ่านและประมวลผลไฟล์ในเธรดใหม่ เมื่อเธรดสิ้นสุดหน่วยความจำจะถูกปล่อยโดยอัตโนมัติโดยไม่มีปัญหาเรื่องประสิทธิภาพ

ใช้งานได้จริงเช่นนี้:

from dask import delayed  # this module wraps the multithreading
def f(storage, index, chunk_size):  # the processing function
    # read the chunk of size chunk_size starting at index in the file
    # process it using data in storage if needed
    # append data needed for further computations  to storage 
    return storage

partial_result = delayed([])  # put into the delayed() the constructor for your data structure
# I personally use "delayed(nx.Graph())" since I am creating a networkx Graph
chunk_size = 100  # ideally you want this as big as possible while still enabling the computations to fit in memory
for index in range(0, len(file), chunk_size):
    # we indicates to dask that we will want to apply f to the parameters partial_result, index, chunk_size
    partial_result = delayed(f)(partial_result, index, chunk_size)

    # no computations are done yet !
    # dask will spawn a thread to run f(partial_result, index, chunk_size) once we call partial_result.compute()
    # passing the previous "partial_result" variable in the parameters assures a chunk will only be processed after the previous one is done
    # it also allows you to use the results of the processing of the previous chunks in the file if needed

# this launches all the computations
result = partial_result.compute()

# one thread is spawned for each "delayed" one at a time to compute its result
# dask then closes the tread, which solves the memory freeing issue
# the strange performance issue with gc.collect() is also avoided

1
ฉันสงสัยว่าทำไมคุณถึงใช้ `// '` แทนที่จะเป็น # ใน Python เพื่อแสดงความคิดเห็น
JC Rocamonde

ฉันสับสนระหว่างภาษา ขอบคุณสำหรับข้อสังเกตฉันได้ปรับปรุงไวยากรณ์
Retzod

9

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

ไม่มีวิธีใดที่จะบอก Python โดยตรงให้เพิ่มหน่วยความจำ ความจริงของเรื่องนั้นคือถ้าคุณต้องการระดับการควบคุมที่ต่ำคุณจะต้องเขียนส่วนขยายใน C หรือ C ++

ที่กล่าวว่ามีเครื่องมือบางอย่างที่จะช่วยในเรื่องนี้:


3
gc.collect () และ del gc.garbage [:] ทำงานได้ดีเมื่อฉันใช้หน่วยความจำจำนวนมาก
Andrew Scott Evans

3

หากคุณไม่สนใจเกี่ยวกับการนำจุดยอดกลับมาใช้ใหม่คุณสามารถมีไฟล์เอาต์พุตสองไฟล์ - หนึ่งไฟล์สำหรับจุดยอดและอีกหนึ่งไฟล์สำหรับรูปสามเหลี่ยม จากนั้นผนวกไฟล์สามเหลี่ยมเข้ากับไฟล์จุดสุดยอดเมื่อคุณทำเสร็จแล้ว


1
ฉันคิดว่าฉันสามารถเก็บจุดยอดในหน่วยความจำและพิมพ์รูปสามเหลี่ยมออกไปยังไฟล์แล้วพิมพ์จุดสุดยอดได้เฉพาะตอนท้าย อย่างไรก็ตามการกระทำของการเขียนรูปสามเหลี่ยมลงในไฟล์เป็นการระบายประสิทธิภาพที่ยิ่งใหญ่ มีวิธีการเพิ่มความเร็วใด ๆที่ขึ้นมา?
นาธาน Fellman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.