web-dev-qa-db-fra.com

Limiter l'utilisation totale de la CPU en multitraitement python

J'utilise multiprocessing.Pool.imap pour exécuter de nombreux travaux indépendants en parallèle à l'aide de Python 2.7 sous Windows 7. Avec les paramètres par défaut, l'utilisation totale de mon processeur est fixée à 100%, mesurée par le Gestionnaire des tâches Windows. Cela rend impossible tout autre travail pendant que mon code est exécuté en arrière-plan.

J'ai essayé de limiter le nombre de processus au nombre de processeurs moins 1, comme décrit dans Comment limiter le nombre de processeurs utilisés par Python :

pool = Pool(processes=max(multiprocessing.cpu_count()-1, 1)
for p in pool.imap(func, iterable):
     ...

Cela réduit le nombre total de processus en cours d'exécution. Cependant, chaque processus prend juste plus de cycles pour le rattraper. Donc, mon utilisation totale du processeur est toujours fixée à 100%.

Existe-t-il un moyen de limiter directement l'utilisation totale du processeur - PAS seulement le nombre de processus - ou à défaut, existe-t-il une solution de contournement?

6
Dave Kielpinski

La solution dépend de ce que vous voulez faire. Voici quelques options:

Priorités inférieures des processus

Vous pouvez Nice les sous-processus. De cette façon, bien qu'ils consomment toujours 100% du processeur, lorsque vous démarrez d'autres applications, le système d'exploitation donne la préférence aux autres applications. Si vous souhaitez laisser un calcul exigeant beaucoup de travail sur l'arrière-plan de votre ordinateur portable et que le ventilateur du processeur ne fonctionne pas tout le temps, régler la valeur de Nice avec psutils est votre solution. Ce script est un script de test qui s'exécute sur tous les cœurs suffisamment longtemps pour que vous puissiez voir comment il se comporte. 

from multiprocessing import Pool, cpu_count
import math
import psutil
import os

def f(i):
    return math.sqrt(i)

def limit_cpu():
    "is called at every process start"
    p = psutil.Process(os.getpid())
    # set to lowest priority, this is windows only, on Unix use ps.Nice(19)
    p.Nice(psutil.BELOW_NORMAL_PRIORITY_CLASS)

if __== '__main__':
    # start "number of cores" processes
    pool = Pool(None, limit_cpu)
    for p in pool.imap(f, range(10**8)):
        pass

L'astuce est que limit_cpu est exécuté au début de chaque processus (voir initializer argment dans le doc ). Alors qu'Unix a des niveaux de -19 (prix le plus élevé) à 19 (prix le plus bas), Windows a quelques niveaux distincts pour donner la priorité. BELOW_NORMAL_PRIORITY_CLASS correspond probablement mieux à vos besoins, il existe également IDLE_PRIORITY_CLASS qui indique que Windows n'exécute votre processus que lorsque le système est inactif. 

Vous pouvez afficher la priorité si vous passez en mode détail dans le Gestionnaire des tâches et cliquez avec le bouton droit sur le processus:

 enter image description here

Nombre réduit de processus

Même si vous avez rejeté cette option, cela pourrait être une bonne option: disons que vous limitez le nombre de sous-processus à la moitié des cœurs de processeur à l'aide de pool = Pool(max(cpu_count()//2, 1)). autres applications en cours d'exécution. Après un court laps de temps, le système d’exploitation replanifie les processus et peut les déplacer vers d’autres cœurs de processeurs, etc. Les systèmes Windows comme ceux basés sur Unix se comportent de la sorte.

Windows: Exécution de 2 processus sur 4 cœurs:

OSX: exécution de 4 processus sur 8 cœurs :

 enter image description here

Vous voyez que les deux systèmes d'exploitation équilibrent le processus entre les cœurs, bien que de manière inégale, vous voyez toujours quelques cœurs avec des pourcentages plus élevés que d'autres.

Sleep

Si vous voulez absolument vous assurer que vos processus ne consomment jamais 100% d'un noyau donné (par exemple, si vous souhaitez empêcher la montée en puissance du ventilateur du processeur), vous pouvez alors mettre en veille votre fonction de traitement:

from time import sleep

def f(i):
    sleep(0.01)
    return math.sqrt(i)

Ainsi, le système d’exploitation «planifie» votre processus pour 0.01 secondes pour chaque calcul et laisse de la place pour d’autres applications. S'il n'y a pas d'autres applications, le cpu core est inactif et ne passera jamais à 100%. Vous aurez besoin de jouer avec différentes durées de sommeil, cela variera également d'un ordinateur à l'autre sur lequel vous l'exécutez. Si vous voulez le rendre très sophistiqué, vous pouvez adapter le sommeil en fonction de ce que cpu_times() rapporte.

11
hansaplast

 

Au niveau de l'OS

vous pouvez utiliser Nice pour définir une priorité pour une commande unique. Vous pouvez également démarrer un script python avec Nice. (Ci-dessous, à partir de: http://blog.scoutapp.com/articles/2014/11/04/restricting-process-cpu-usage-using-Nice-cpulimit-and-cgroups )

Nice  

La commande Nice ajuste le niveau de priorité d'un processus afin qu'il s'exécute moins souvent. Ceci est utile lorsque vous devez exécuter une tâche Intensive en ressources CPU en tâche de fond ou en tâche de fond. Le niveau de gentillesse Varie de -20 (planification la plus favorable) à 19. (moins favorable). Les processus sous Linux démarrent avec une gentillesse de 0 par défaut. La commande Nice (sans aucun paramètre supplémentaire) démarrera un processus Avec une gentillesse de 10. À ce niveau, le planificateur la verra comme une tâche de priorité inférieure Et lui donnera moins de ressources de processeur. Commencez par deux tâches matho-prime, une avec Nice et une sans:

Nice matho-primes 0 9999999999 > /dev/null &matho-primes 0 9999999999 > /dev/null &
matho-primes 0 9999999999 > /dev/null &

Maintenant, cours en haut.

 enter image description here

En fonction en Python

Une autre approche consiste à utiliser psutils pour vérifier la charge moyenne de votre processeur au cours de la dernière minute, puis demandez à vos threads de vérifier la charge moyenne du processeur et d'en créer un autre si vous êtes en dessous de la cible de charge du processeur spécifiée. au-dessus de la cible de charge du processeur. Cela vous échappera lorsque vous utiliserez votre ordinateur, mais maintiendra une charge de processeur constante.

# Import Python modules
import time
import os
import multiprocessing
import psutil
import math
from random import randint

# Main task function
def main_process(item_queue, args_array):
    # Go through each link in the array passed in.
    while not item_queue.empty():
        # Get the next item in the queue
        item = item_queue.get()
        # Create a random number to simulate threads that
        # are not all going to be the same
        randomizer = randint(100, 100000)
        for i in range(randomizer):
            algo_seed = math.sqrt(math.sqrt(i * randomizer) % randomizer)
        # Check if the thread should continue based on current load balance
        if spool_down_load_balance():
            print "Process " + str(os.getpid()) + " saying goodnight..."
            break

# This function will build a queue and
def start_thread_process(queue_pile, args_array):
    # Create a Queue to hold link pile and share between threads
    item_queue = multiprocessing.Queue()
    # Put all the initial items into the queue
    for item in queue_pile:
        item_queue.put(item)
    # Append the load balancer thread to the loop
    load_balance_process = multiprocessing.Process(target=spool_up_load_balance, args=(item_queue, args_array))
    # Loop through and start all processes
    load_balance_process.start()
    # This .join() function prevents the script from progressing further.
    load_balance_process.join()

# Spool down the thread balance when load is too high
def spool_down_load_balance():
    # Get the count of CPU cores
    core_count = psutil.cpu_count()
    # Calulate the short term load average of past minute
    one_minute_load_average = os.getloadavg()[0] / core_count
    # If load balance above the max return True to kill the process
    if one_minute_load_average > args_array['cpu_target']:
        print "-Unacceptable load balance detected. Killing process " + str(os.getpid()) + "..."
        return True

# Load balancer thread function
def spool_up_load_balance(item_queue, args_array):

    print "[Starting load balancer...]"
    # Get the count of CPU cores
    core_count = psutil.cpu_count()
    # While there is still links in queue
    while not item_queue.empty():
        print "[Calculating load balance...]"
        # Check the 1 minute average CPU load balance
        # returns 1,5,15 minute load averages
        one_minute_load_average = os.getloadavg()[0] / core_count
        # If the load average much less than target, start a group of new threads
        if one_minute_load_average < args_array['cpu_target'] / 2:
            # Print message and log that load balancer is starting another thread
            print "Starting another thread group due to low CPU load balance of: " + str(one_minute_load_average * 100) + "%"
            time.sleep(5)
            # Start another group of threads
            for i in range(3):
                start_new_thread = multiprocessing.Process(target=main_process,args=(item_queue, args_array))
                start_new_thread.start()
            # Allow the added threads to have an impact on the CPU balance
            # before checking the one minute average again
            time.sleep(20)

        # If load average less than target start single thread
        Elif one_minute_load_average < args_array['cpu_target']:
            # Print message and log that load balancer is starting another thread
            print "Starting another single thread due to low CPU load balance of: " + str(one_minute_load_average * 100) + "%"
            # Start another thread
            start_new_thread = multiprocessing.Process(target=main_process,args=(item_queue, args_array))
            start_new_thread.start()
            # Allow the added threads to have an impact on the CPU balance
            # before checking the one minute average again
            time.sleep(20)

        else:
            # Print CPU load balance
            print "Reporting stable CPU load balance: " + str(one_minute_load_average * 100) + "%"
            # Sleep for another minute while
            time.sleep(20)

if __name__=="__main__":

    # Set the queue size
    queue_size = 10000
    # Define an arguments array to pass around all the values
    args_array = {
        # Set some initial CPU load values as a CPU usage goal
        "cpu_target" : 0.60,
        # When CPU load is significantly low, start this number
        # of threads
        "thread_group_size" : 3
    }

    # Create an array of fixed length to act as queue
    queue_pile = list(range(queue_size))
    # Set main process start time
    start_time = time.time()
    # Start the main process
    start_thread_process(queue_pile, args_array)
    print '[Finished processing the entire queue! Time consuming:{0} Time Finished: {1}]'.format(time.time() - start_time, time.strftime("%c"))
0
rippledj