ฉันจะโปรไฟล์การใช้หน่วยความจำใน Python ได้อย่างไร?


230

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

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


ทำซ้ำโปรไฟล์แนะนำหน่วยความจำ Python ใด? . IMHO คำตอบที่ดีที่สุดในปี 2019 คือmemory_profiler
vladkha

คำตอบ:


118

สิ่งนี้ได้รับคำตอบแล้วที่นี่: Python memory profiler

โดยทั่วไปคุณทำอะไรแบบนั้น (อ้างจากGuppy-PE ):

>>> from guppy import hpy; h=hpy()
>>> h.heap()
Partition of a set of 48477 objects. Total size = 3265516 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  25773  53  1612820  49   1612820  49 str
     1  11699  24   483960  15   2096780  64 tuple
     2    174   0   241584   7   2338364  72 dict of module
     3   3478   7   222592   7   2560956  78 types.CodeType
     4   3296   7   184576   6   2745532  84 function
     5    401   1   175112   5   2920644  89 dict of class
     6    108   0    81888   3   3002532  92 dict (no owner)
     7    114   0    79632   2   3082164  94 dict of type
     8    117   0    51336   2   3133500  96 type
     9    667   1    24012   1   3157512  97 __builtin__.wrapper_descriptor
<76 more rows. Type e.g. '_.more' to view.>
>>> h.iso(1,[],{})
Partition of a set of 3 objects. Total size = 176 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0      1  33      136  77       136  77 dict (no owner)
     1      1  33       28  16       164  93 list
     2      1  33       12   7       176 100 int
>>> x=[]
>>> h.iso(x).sp
 0: h.Root.i0_modules['__main__'].__dict__['x']
>>> 

6
เอกสารปลาหางนกยูงอย่างเป็นทางการเป็นบิตน้อย; สำหรับแหล่งข้อมูลอื่น ๆ ดูตัวอย่างนี้และเรียงความ heapy
tutuDajuju

14
Guppy ดูเหมือนจะไม่ได้รับการดูแลรักษาอีกต่อไปดังนั้นฉันขอแนะนำว่าให้ลดระดับคำตอบและตอบหนึ่งในคำตอบอื่นแทน
robguinness

1
@robguinness การลดระดับหมายความว่าคุณลงคะแนนหรือไม่ ดูเหมือนจะไม่ยุติธรรมเพราะมันมีค่า ณ เวลาใดเวลาหนึ่ง ฉันคิดว่าการแก้ไขที่ด้านบนระบุว่าไม่ถูกต้องสำหรับเหตุผล X และเพื่อดูคำตอบ Y หรือ Z แทน ฉันคิดว่าแนวทางการปฏิบัตินี้เหมาะสมกว่า
WinEunuuchs2Unix

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

92

งูหลาม 3.4 tracemallocรวมถึงโมดูลใหม่: มันให้สถิติรายละเอียดเกี่ยวกับรหัสที่จัดสรรหน่วยความจำมากที่สุด นี่คือตัวอย่างที่แสดงสามบรรทัดแรกที่จัดสรรหน่วยความจำ

from collections import Counter
import linecache
import os
import tracemalloc

def display_top(snapshot, key_type='lineno', limit=3):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        # replace "/path/to/module/file.py" with "module/file.py"
        filename = os.sep.join(frame.filename.split(os.sep)[-2:])
        print("#%s: %s:%s: %.1f KiB"
              % (index, filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f KiB" % (total / 1024))


tracemalloc.start()

counts = Counter()
fname = '/usr/share/dict/american-english'
with open(fname) as words:
    words = list(words)
    for word in words:
        prefix = word[:3]
        counts[prefix] += 1
print('Top prefixes:', counts.most_common(3))

snapshot = tracemalloc.take_snapshot()
display_top(snapshot)

และนี่คือผลลัพธ์:

Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
Top 3 lines
#1: scratches/memory_test.py:37: 6527.1 KiB
    words = list(words)
#2: scratches/memory_test.py:39: 247.7 KiB
    prefix = word[:3]
#3: scratches/memory_test.py:40: 193.0 KiB
    counts[prefix] += 1
4 other: 4.3 KiB
Total allocated size: 6972.1 KiB

หน่วยความจำรั่วไม่รั่วเมื่อใด

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

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

from collections import Counter
import linecache
import os
import tracemalloc
from time import sleep


def count_prefixes():
    sleep(2)  # Start up time.
    counts = Counter()
    fname = '/usr/share/dict/american-english'
    with open(fname) as words:
        words = list(words)
        for word in words:
            prefix = word[:3]
            counts[prefix] += 1
            sleep(0.0001)
    most_common = counts.most_common(3)
    sleep(3)  # Shut down time.
    return most_common


def main():
    tracemalloc.start()

    most_common = count_prefixes()
    print('Top prefixes:', most_common)

    snapshot = tracemalloc.take_snapshot()
    display_top(snapshot)


def display_top(snapshot, key_type='lineno', limit=3):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        # replace "/path/to/module/file.py" with "module/file.py"
        filename = os.sep.join(frame.filename.split(os.sep)[-2:])
        print("#%s: %s:%s: %.1f KiB"
              % (index, filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f KiB" % (total / 1024))


main()

เมื่อฉันเรียกใช้เวอร์ชั่นนั้นการใช้งานหน่วยความจำลดลงจาก 6MB เหลือ 4KB เพราะฟังก์ชั่นจะปล่อยหน่วยความจำทั้งหมดเมื่อเสร็จแล้ว

Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
Top 3 lines
#1: collections/__init__.py:537: 0.7 KiB
    self.update(*args, **kwds)
#2: collections/__init__.py:555: 0.6 KiB
    return _heapq.nlargest(n, self.items(), key=_itemgetter(1))
#3: python3.6/heapq.py:569: 0.5 KiB
    result = [(key(elem), i, elem) for i, elem in zip(range(0, -n, -1), it)]
10 other: 2.2 KiB
Total allocated size: 4.0 KiB

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

from collections import Counter
import linecache
import os
import tracemalloc
from datetime import datetime
from queue import Queue, Empty
from resource import getrusage, RUSAGE_SELF
from threading import Thread
from time import sleep

def memory_monitor(command_queue: Queue, poll_interval=1):
    tracemalloc.start()
    old_max = 0
    snapshot = None
    while True:
        try:
            command_queue.get(timeout=poll_interval)
            if snapshot is not None:
                print(datetime.now())
                display_top(snapshot)

            return
        except Empty:
            max_rss = getrusage(RUSAGE_SELF).ru_maxrss
            if max_rss > old_max:
                old_max = max_rss
                snapshot = tracemalloc.take_snapshot()
                print(datetime.now(), 'max RSS', max_rss)


def count_prefixes():
    sleep(2)  # Start up time.
    counts = Counter()
    fname = '/usr/share/dict/american-english'
    with open(fname) as words:
        words = list(words)
        for word in words:
            prefix = word[:3]
            counts[prefix] += 1
            sleep(0.0001)
    most_common = counts.most_common(3)
    sleep(3)  # Shut down time.
    return most_common


def main():
    queue = Queue()
    poll_interval = 0.1
    monitor_thread = Thread(target=memory_monitor, args=(queue, poll_interval))
    monitor_thread.start()
    try:
        most_common = count_prefixes()
        print('Top prefixes:', most_common)
    finally:
        queue.put('stop')
        monitor_thread.join()


def display_top(snapshot, key_type='lineno', limit=3):
    snapshot = snapshot.filter_traces((
        tracemalloc.Filter(False, "<frozen importlib._bootstrap>"),
        tracemalloc.Filter(False, "<unknown>"),
    ))
    top_stats = snapshot.statistics(key_type)

    print("Top %s lines" % limit)
    for index, stat in enumerate(top_stats[:limit], 1):
        frame = stat.traceback[0]
        # replace "/path/to/module/file.py" with "module/file.py"
        filename = os.sep.join(frame.filename.split(os.sep)[-2:])
        print("#%s: %s:%s: %.1f KiB"
              % (index, filename, frame.lineno, stat.size / 1024))
        line = linecache.getline(frame.filename, frame.lineno).strip()
        if line:
            print('    %s' % line)

    other = top_stats[limit:]
    if other:
        size = sum(stat.size for stat in other)
        print("%s other: %.1f KiB" % (len(other), size / 1024))
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f KiB" % (total / 1024))


main()

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

2018-05-29 10:34:34.441334 max RSS 10188
2018-05-29 10:34:36.475707 max RSS 23588
2018-05-29 10:34:36.616524 max RSS 38104
2018-05-29 10:34:36.772978 max RSS 45924
2018-05-29 10:34:36.929688 max RSS 46824
2018-05-29 10:34:37.087554 max RSS 46852
Top prefixes: [('con', 1220), ('dis', 1002), ('pro', 809)]
2018-05-29 10:34:56.281262
Top 3 lines
#1: scratches/scratch.py:36: 6527.0 KiB
    words = list(words)
#2: scratches/scratch.py:38: 16.4 KiB
    prefix = word[:3]
#3: scratches/scratch.py:39: 10.1 KiB
    counts[prefix] += 1
19 other: 10.8 KiB
Total allocated size: 6564.3 KiB

หากคุณใช้ Linux คุณอาจพบว่า/proc/self/statmมีประโยชน์มากกว่าresourceโมดูล


นี่เป็นสิ่งที่ยอดเยี่ยม แต่ดูเหมือนว่าจะพิมพ์เฉพาะสแน็ปช็อตในช่วงเวลาเมื่อฟังก์ชันภายในส่งคืน "count_prefixes ()" กล่าวอีกนัยหนึ่งถ้าคุณมีการโทรยาว ๆ เช่นlong_running()ในcount_prefixes()ฟังก์ชันค่าสูงสุดของ RSS จะไม่ถูกพิมพ์จนกว่าจะlong_running()ส่งคืน หรือฉันเข้าใจผิด?
robguinness

ฉันคิดว่าคุณเข้าใจผิด @robguinness memory_monitor()ทำงานอยู่ในหัวข้อที่แยกต่างหากจากcount_prefixes()ดังนั้นวิธีเดียวที่หนึ่งสามารถส่งผลกระทบต่อคนอื่น ๆ เป็น GIL memory_monitor()และคิวข้อความที่ผมส่งผ่านไปยัง ฉันสงสัยว่าเมื่อมีการcount_prefixes()โทรsleep()มันจะกระตุ้นให้เธรดบริบทสลับไปมา หากคุณlong_running()ไม่ได้จริงใช้เวลานานมากแล้วบริบทด้ายอาจจะไม่เปลี่ยนจนกว่าคุณจะตีกลับการโทรในsleep() count_prefixes()หากไม่เหมาะสมให้โพสต์คำถามใหม่และลิงก์จากที่นี่
Don Kirkby

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

31

หากคุณต้องการดูการใช้หน่วยความจำของวัตถุเท่านั้น ( ตอบคำถามอื่น ๆ )

มีโมดูลที่เรียกว่าPymplerซึ่งมีasizeof โมดูลอยู่

ใช้ดังต่อไปนี้:

from pympler import asizeof
asizeof.asizeof(my_object)

sys.getsizeofมันไม่เหมือนกับวัตถุที่คุณสร้างขึ้นเอง

>>> asizeof.asizeof(tuple('bcd'))
200
>>> asizeof.asizeof({'foo': 'bar', 'baz': 'bar'})
400
>>> asizeof.asizeof({})
280
>>> asizeof.asizeof({'foo':'bar'})
360
>>> asizeof.asizeof('foo')
40
>>> asizeof.asizeof(Bar())
352
>>> asizeof.asizeof(Bar().__dict__)
280
>>> help(asizeof.asizeof)
Help on function asizeof in module pympler.asizeof:

asizeof(*objs, **opts)
    Return the combined size in bytes of all objects passed as positional arguments.

1
สิ่งนี้เกี่ยวข้องกับ RSS หรือไม่
pg2455

1
@mousecoder: ซึ่ง RSS ที่en.wikipedia.org/wiki/RSS_(disambiguation) ? เว็บฟีด อย่างไร?
serv-inc

2
@ serv-inc ขนาดชุด Residentแต่ฉันสามารถหาหนึ่งกล่าวถึงในแหล่ง Pympler และพูดถึงที่ดูเหมือนจะไม่เชื่อมโยงโดยตรงกับasizeof
jkmartindale

1
@ ตัวแปลงรหัสหน่วยความจำที่รายงานโดยasizeofสามารถมีส่วนร่วมกับ RSS ได้ ฉันไม่แน่ใจว่าคุณหมายถึงอะไรโดย "เกี่ยวข้องกับ"
OrangeDog

1
@ serv-inc เป็นไปได้อาจเป็นกรณีที่เฉพาะเจาะจงมาก แต่สำหรับ usecase ของฉันในการวัดพจนานุกรมหลายมิติขนาดใหญ่ฉันพบtracemallocวิธีแก้ปัญหาที่มีขนาดต่ำกว่าเร็วขึ้น
ulkas

22

การเปิดเผยข้อมูล:

  • ใช้ได้กับ Linux เท่านั้น
  • รายงานหน่วยความจำที่ใช้โดยกระบวนการปัจจุบันโดยรวมไม่ใช่ฟังก์ชั่นส่วนบุคคลภายใน

แต่ดีเพราะความเรียบง่าย:

import resource
def using(point=""):
    usage=resource.getrusage(resource.RUSAGE_SELF)
    return '''%s: usertime=%s systime=%s mem=%s mb
           '''%(point,usage[0],usage[1],
                usage[2]/1024.0 )

เพียงใส่using("Label")ตำแหน่งที่คุณต้องการดูว่าเกิดอะไรขึ้น ตัวอย่างเช่น

print(using("before"))
wrk = ["wasting mem"] * 1000000
print(using("after"))

>>> before: usertime=2.117053 systime=1.703466 mem=53.97265625 mb
>>> after: usertime=2.12023 systime=1.70708 mem=60.8828125 mb

6
"การใช้หน่วยความจำของฟังก์ชันที่กำหนด" ดังนั้นแนวทางของคุณจึงไม่ได้ช่วย
Glaslos

โดยดูที่usage[2]คุณกำลังมองหาที่ru_maxrssซึ่งเป็นเพียงส่วนหนึ่งของกระบวนการซึ่งเป็นถิ่นที่อยู่ สิ่งนี้จะไม่ช่วยได้มากนักหากกระบวนการสลับไปยังดิสก์แม้แต่บางส่วน
หลุยส์

8
resourceเป็นโมดูลเฉพาะของ Unix ที่ไม่ทำงานภายใต้ Windows
Martin

1
หน่วยของru_maxrss(นั่นคือusage[2]) เป็น kB resource.getpagesize()ไม่หน้าดังนั้นจึงไม่มีความจำเป็นที่จะต้องคูณว่าจำนวนโดย
Tey '

1
สิ่งนี้ไม่ได้พิมพ์ออกมาสำหรับฉัน
quantumpotato

7

เนื่องจากคำตอบที่ได้รับการยอมรับและคำตอบที่ได้รับการโหวตสูงสุดถัดไปในความเห็นของฉันมีปัญหาบางอย่างฉันอยากจะเสนอคำตอบอีกหนึ่งคำที่อิงกับคำตอบของ Ihor B. ด้วยการปรับเปลี่ยนเล็กน้อย แต่มีความสำคัญ

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

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

ฉันยังแก้ไขผลลัพธ์เพื่อให้คุณได้รับ RSS, VMS และหน่วยความจำที่แชร์ ฉันไม่สนใจเกี่ยวกับค่า "ก่อน" และ "หลัง" แต่มีเพียงเดลต้าเท่านั้นดังนั้นฉันจึงลบค่าเหล่านั้น (ถ้าคุณเปรียบเทียบกับคำตอบของ Ihor B. )

รหัสการทำโปรไฟล์

# profile.py
import time
import os
import psutil
import inspect


def elapsed_since(start):
    #return time.strftime("%H:%M:%S", time.gmtime(time.time() - start))
    elapsed = time.time() - start
    if elapsed < 1:
        return str(round(elapsed*1000,2)) + "ms"
    if elapsed < 60:
        return str(round(elapsed, 2)) + "s"
    if elapsed < 3600:
        return str(round(elapsed/60, 2)) + "min"
    else:
        return str(round(elapsed / 3600, 2)) + "hrs"


def get_process_memory():
    process = psutil.Process(os.getpid())
    mi = process.memory_info()
    return mi.rss, mi.vms, mi.shared


def format_bytes(bytes):
    if abs(bytes) < 1000:
        return str(bytes)+"B"
    elif abs(bytes) < 1e6:
        return str(round(bytes/1e3,2)) + "kB"
    elif abs(bytes) < 1e9:
        return str(round(bytes / 1e6, 2)) + "MB"
    else:
        return str(round(bytes / 1e9, 2)) + "GB"


def profile(func, *args, **kwargs):
    def wrapper(*args, **kwargs):
        rss_before, vms_before, shared_before = get_process_memory()
        start = time.time()
        result = func(*args, **kwargs)
        elapsed_time = elapsed_since(start)
        rss_after, vms_after, shared_after = get_process_memory()
        print("Profiling: {:>20}  RSS: {:>8} | VMS: {:>8} | SHR {"
              ":>8} | time: {:>8}"
            .format("<" + func.__name__ + ">",
                    format_bytes(rss_after - rss_before),
                    format_bytes(vms_after - vms_before),
                    format_bytes(shared_after - shared_before),
                    elapsed_time))
        return result
    if inspect.isfunction(func):
        return wrapper
    elif inspect.ismethod(func):
        return wrapper(*args,**kwargs)

ตัวอย่างการใช้งานสมมติว่าโค้ดด้านบนถูกบันทึกเป็นprofile.py:

from profile import profile
from time import sleep
from sklearn import datasets # Just an example of 3rd party function call


# Method 1
run_profiling = profile(datasets.load_digits)
data = run_profiling()

# Method 2
@profile
def my_function():
    # do some stuff
    a_list = []
    for i in range(1,100000):
        a_list.append(i)
    return a_list


res = my_function()

ซึ่งจะส่งผลให้ผลลัพธ์คล้ายกับด้านล่าง:

Profiling:        <load_digits>  RSS:   5.07MB | VMS:   4.91MB | SHR  73.73kB | time:  89.99ms
Profiling:        <my_function>  RSS:   1.06MB | VMS:   1.35MB | SHR       0B | time:   8.43ms

หมายเหตุสำคัญสองประการสุดท้าย:

  1. โปรดทราบว่าการทำโปรไฟล์นี้เป็นเพียงการประมาณเท่านั้นเนื่องจากอาจมีสิ่งอื่น ๆ อีกมากมายเกิดขึ้นในเครื่อง เนื่องจากการรวบรวมขยะและปัจจัยอื่น ๆ deltas อาจเป็นศูนย์
  2. ด้วยเหตุผลที่ไม่ทราบสาเหตุการเรียกใช้ฟังก์ชั่นที่สั้นมาก (เช่น 1 หรือ 2 ms) จะปรากฏขึ้นโดยไม่มีการใช้หน่วยความจำ ฉันสงสัยว่านี่เป็นข้อ จำกัด ของฮาร์ดแวร์ / ระบบปฏิบัติการ (ทดสอบบนแล็ปท็อปขั้นพื้นฐานที่มี Linux) ว่าสถิติหน่วยความจำได้รับการปรับปรุงบ่อยเพียงใด
  3. เพื่อให้ตัวอย่างง่ายฉันไม่ได้ใช้อาร์กิวเมนต์ของฟังก์ชั่นใด ๆ แต่พวกเขาควรจะทำงานตามที่คาดหวังเช่น profile(my_function, arg)เพื่อโปรไฟล์my_function(arg)

7

ด้านล่างเป็นมัณฑนากรฟังก์ชั่นเรียบง่ายซึ่งช่วยให้สามารถติดตามจำนวนหน่วยความจำที่กระบวนการใช้ก่อนการเรียกใช้ฟังก์ชันหลังจากการเรียกใช้ฟังก์ชันและความแตกต่างคืออะไร:

import time
import os
import psutil


def elapsed_since(start):
    return time.strftime("%H:%M:%S", time.gmtime(time.time() - start))


def get_process_memory():
    process = psutil.Process(os.getpid())
    return process.get_memory_info().rss


def profile(func):
    def wrapper(*args, **kwargs):
        mem_before = get_process_memory()
        start = time.time()
        result = func(*args, **kwargs)
        elapsed_time = elapsed_since(start)
        mem_after = get_process_memory()
        print("{}: memory before: {:,}, after: {:,}, consumed: {:,}; exec time: {}".format(
            func.__name__,
            mem_before, mem_after, mem_after - mem_before,
            elapsed_time))
        return result
    return wrapper

นี่คือบล็อกของฉันที่อธิบายรายละเอียดทั้งหมด ( ลิงก์ที่เก็บถาวร )


4
มันควรจะเป็นprocess.memory_info().rssไม่ได้process.get_memory_info().rssอย่างน้อยในอูบุนตูและงูหลาม 3.6 ที่เกี่ยวข้องstackoverflow.com/questions/41012058/psutil-error-on-macos
jangorecki

1
คุณถูก 3.x ลูกค้าของฉันใช้ Python 2.7 ไม่ใช่รุ่นใหม่ล่าสุด
Ihor B.

4

อาจช่วยได้:
< ดูเพิ่มเติม >

pip install gprof2dot
sudo apt-get install graphviz

gprof2dot -f pstats profile_for_func1_001 | dot -Tpng -o profile.png

def profileit(name):
    """
    @profileit("profile_for_func1_001")
    """
    def inner(func):
        def wrapper(*args, **kwargs):
            prof = cProfile.Profile()
            retval = prof.runcall(func, *args, **kwargs)
            # Note use of name from outer scope
            prof.dump_stats(name)
            return retval
        return wrapper
    return inner

@profileit("profile_for_func1_001")
def func1(...)

1

ตัวอย่างง่ายๆในการคำนวณการใช้หน่วยความจำของกลุ่มของรหัส / ฟังก์ชั่นโดยใช้ memory_profile ขณะที่ส่งคืนผลลัพธ์ของฟังก์ชัน:

import memory_profiler as mp

def fun(n):
    tmp = []
    for i in range(n):
        tmp.extend(list(range(i*i)))
    return "XXXXX"

คำนวณการใช้หน่วยความจำก่อนเรียกใช้รหัสจากนั้นคำนวณการใช้สูงสุดระหว่างรหัส:

start_mem = mp.memory_usage(max_usage=True)
res = mp.memory_usage(proc=(fun, [100]), max_usage=True, retval=True) 
print('start mem', start_mem)
print('max mem', res[0][0])
print('used mem', res[0][0]-start_mem)
print('fun output', res[1])

คำนวณการใช้งานในจุดสุ่มตัวอย่างขณะใช้งานฟังก์ชัน:

res = mp.memory_usage((fun, [100]), interval=.001, retval=True)
print('min mem', min(res[0]))
print('max mem', max(res[0]))
print('used mem', max(res[0])-min(res[0]))
print('fun output', res[1])

เครดิต: @skeept

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