85 votes

Suivi de l'utilisation *maximale* de la mémoire par une fonction Python

Je veux savoir quelle est la quantité maximale de RAM allouée pendant l'appel à une fonction (en Python). Il y a d'autres questions sur SO liées au suivi de l'utilisation de la RAM :

Quel profileur de mémoire Python est recommandé ?

Comment puis-je profiler l'utilisation de la mémoire en Python ?

mais ceux-ci semblent vous permettre de mieux suivre l'utilisation de la mémoire au moment où la heap() la méthode (dans le cas de guppy) est appelée. Cependant, ce que je veux suivre, c'est une fonction dans une bibliothèque externe que je ne peux pas modifier, et qui utilise beaucoup de RAM mais la libère une fois l'exécution de la fonction terminée. Existe-t-il un moyen de savoir quelle est la quantité totale de RAM utilisée pendant l'appel de la fonction ?

64voto

Fabian Pedregosa Points 1611

Il est possible de le faire avec profileur de mémoire . La fonction memory_usage renvoie une liste de valeurs, qui représentent l'utilisation de la mémoire dans le temps (par défaut, par tranches de 0,1 seconde). Si vous avez besoin du maximum, prenez simplement le maximum de cette liste. Petit exemple :

from memory_profiler import memory_usage
from time import sleep

def f():
    # a function that with growing
    # memory consumption
    a = [0] * 1000
    sleep(.1)
    b = a * 100
    sleep(.1)
    c = b * 100
    return a

mem_usage = memory_usage(f)
print('Memory usage (in chunks of .1 seconds): %s' % mem_usage)
print('Maximum memory usage: %s' % max(mem_usage))

Dans mon cas (memory_profiler 0.25), si on imprime la sortie suivante :

Memory usage (in chunks of .1 seconds): [45.65625, 45.734375, 46.41015625, 53.734375]
Maximum memory usage: 53.734375

44voto

Adam Lewis Points 2058

Cette question m'a semblé plutôt intéressante et m'a donné une raison de me pencher sur Guppy / Heapy, pour cela je vous remercie.

J'ai essayé pendant environ 2 heures de faire en sorte que Heapy surveille un appel de fonction / processus sans modifier son source avec zéro la chance.

J'ai trouvé un moyen d'accomplir votre tâche en utilisant la bibliothèque Python intégrée. resource . Notez que la documentation n'indique pas ce que le RU_MAXRSS les retours de valeur. Un autre utilisateur de SO noté que c'était en kB. Sous Mac OSX 7.3 et en regardant les ressources de mon système grimper pendant le code de test ci-dessous, je pense que les valeurs retournées sont en Octets et non pas des kBytes.

Une vue à 10000 pieds sur la façon dont j'ai utilisé la resource pour surveiller l'appel de la bibliothèque consistait à lancer la fonction dans un thread séparé (pouvant être surveillé) et à suivre les ressources du système pour ce processus dans le thread principal. Vous trouverez ci-dessous les deux fichiers que vous devez exécuter pour tester cette méthode.

Contrôleur des ressources de la bibliothèque - whatever_you_want.py

import resource
import time

from stoppable_thread import StoppableThread

class MyLibrarySniffingClass(StoppableThread):
    def __init__(self, target_lib_call, arg1, arg2):
        super(MyLibrarySniffingClass, self).__init__()
        self.target_function = target_lib_call
        self.arg1 = arg1
        self.arg2 = arg2
        self.results = None

    def startup(self):
        # Overload the startup function
        print "Calling the Target Library Function..."

    def cleanup(self):
        # Overload the cleanup function
        print "Library Call Complete"

    def mainloop(self):
        # Start the library Call
        self.results = self.target_function(self.arg1, self.arg2)

        # Kill the thread when complete
        self.stop()

def SomeLongRunningLibraryCall(arg1, arg2):
    max_dict_entries = 2500
    delay_per_entry = .005

    some_large_dictionary = {}
    dict_entry_count = 0

    while(1):
        time.sleep(delay_per_entry)
        dict_entry_count += 1
        some_large_dictionary[dict_entry_count]=range(10000)

        if len(some_large_dictionary) > max_dict_entries:
            break

    print arg1 + " " +  arg2
    return "Good Bye World"

if __name__ == "__main__":
    # Lib Testing Code
    mythread = MyLibrarySniffingClass(SomeLongRunningLibraryCall, "Hello", "World")
    mythread.start()

    start_mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    delta_mem = 0
    max_memory = 0
    memory_usage_refresh = .005 # Seconds

    while(1):
        time.sleep(memory_usage_refresh)
        delta_mem = (resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) - start_mem
        if delta_mem > max_memory:
            max_memory = delta_mem

        # Uncomment this line to see the memory usuage during run-time 
        # print "Memory Usage During Call: %d MB" % (delta_mem / 1000000.0)

        # Check to see if the library call is complete
        if mythread.isShutdown():
            print mythread.results
            break;

    print "\nMAX Memory Usage in MB: " + str(round(max_memory / 1000.0, 3))

Fil à arrêter - stoppable_thread.py

import threading
import time

class StoppableThread(threading.Thread):
    def __init__(self):
        super(StoppableThread, self).__init__()
        self.daemon = True
        self.__monitor = threading.Event()
        self.__monitor.set()
        self.__has_shutdown = False

    def run(self):
        '''Overloads the threading.Thread.run'''
        # Call the User's Startup functions
        self.startup()

        # Loop until the thread is stopped
        while self.isRunning():
            self.mainloop()

        # Clean up
        self.cleanup()

        # Flag to the outside world that the thread has exited
        # AND that the cleanup is complete
        self.__has_shutdown = True

    def stop(self):
        self.__monitor.clear()

    def isRunning(self):
        return self.__monitor.isSet()

    def isShutdown(self):
        return self.__has_shutdown

    ###############################
    ### User Defined Functions ####
    ###############################

    def mainloop(self):
        '''
        Expected to be overwritten in a subclass!!
        Note that Stoppable while(1) is handled in the built in "run".
        '''
        pass

    def startup(self):
        '''Expected to be overwritten in a subclass!!'''
        pass

    def cleanup(self):
        '''Expected to be overwritten in a subclass!!'''
        pass

12voto

JenyaKh Points 121

Amélioration de la réponse de @Vader B (car elle n'a pas fonctionné pour moi hors de la boîte) :

$ /usr/bin/time --verbose  ./myscript.py
        Command being timed: "./myscript.py"
        User time (seconds): 16.78
        System time (seconds): 2.74
        Percent of CPU this job got: 117%
        Elapsed (wall clock) time (h:mm:ss or m:ss): 0:16.58
        Average shared text size (kbytes): 0
        Average unshared data size (kbytes): 0
        Average stack size (kbytes): 0
        Average total size (kbytes): 0
        Maximum resident set size (kbytes): 616092   # WE NEED THIS!!!
        Average resident set size (kbytes): 0
        Major (requiring I/O) page faults: 0
        Minor (reclaiming a frame) page faults: 432750
        Voluntary context switches: 1075
        Involuntary context switches: 118503
        Swaps: 0
        File system inputs: 0
        File system outputs: 800
        Socket messages sent: 0
        Socket messages received: 0
        Signals delivered: 0
        Page size (bytes): 4096
        Exit status: 0

9voto

VPS Points 55

Vous pouvez utiliser les ressources de la bibliothèque python pour obtenir l'utilisation de la mémoire.

import resource
resource.getrusage(resource.RUSAGE_SELF).ru_maxrss

Il donnera l'utilisation de la mémoire en kilo-octets, pour convertir en MB divisez par 1000.

8voto

sethp Points 94

Cela semble fonctionner sous Windows. Je ne sais pas pour les autres systèmes d'exploitation.

In [50]: import os

In [51]: import psutil

In [52]: process = psutil.Process(os.getpid())

In [53]: process.get_ext_memory_info().peak_wset
Out[53]: 41934848

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X