web-dev-qa-db-ja.com

python multiprocessingで合計CPU使用率を制限する

Multiprocessing.Pool.imapを使用して、Windows 7でPython 2.7を使用して多くの独立したジョブを並行して実行します。デフォルト設定では、Windowsで測定した合計CPU使用率が100%タスクマネージャー:これにより、コードがバックグラウンドで実行されている間、他の作業を実行できなくなります。

Python使用する)プロセッサの数を制限する方法 で説明されているように、プロセスの数をCPUの数から1を引いた数に制限しようとしました。

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

これにより、実行中のプロセスの総数が減ります。ただし、各プロセスはそれを補うためにより多くのサイクルを必要とします。したがって、合計CPU使用率は100%のままです。

プロセスの数だけでなく、合計CPU使用率を直接制限する方法はありますか?

9
Dave Kielpinski

解決策は、何をしたいかによって異なります。以下にいくつかのオプションを示します。

プロセスの優先度を下げる

Nice サブプロセスを使用できます。この方法では、CPUを100%消費しますが、他のアプリケーションを起動すると、OSは他のアプリケーションを優先します。ラップトップのバックグラウンドでの作業集中型の計算実行を残し、CPUファンが常に実行されていることを気にしない場合は、psutilsでNice値を設定するのが解決策です。このスクリプトは、すべてのコアで十分な時間実行されるテストスクリプトなので、動作を確認できます。

_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 __name__ == '__main__':
    # start "number of cores" processes
    pool = Pool(None, limit_cpu)
    for p in pool.imap(f, range(10**8)):
        pass
_

トリックは、_limit_cpu_がすべてのプロセスの開始時に実行されることです(ドキュメントの initializer引数 を参照)。 Unixのレベルは-19(最高のprio)から19(最低のprio)ですが、Windowsの優先順位は 少数の異なるレベル です。 _BELOW_NORMAL_PRIORITY_CLASS_はおそらく要件に最適です。また、_IDLE_PRIORITY_CLASS_もあります。これは、システムがアイドル状態のときにのみプロセスを実行するようにWindowsに指示します。

タスクマネージャで詳細モードに切り替えて、プロセスを右クリックすると、優先度を表示できます。

enter image description here

少ないプロセス数

このオプションを拒否しましたが、それでも良いオプションかもしれません:pool = Pool(max(cpu_count()//2, 1))を使用してサブプロセスの数をCPUコアの半分に制限すると、OSは最初にCPUコアの半分でそれらのプロセスを実行し、アイドル状態のままにするか、現在実行中の他のアプリケーションを実行します。しばらくすると、OSはプロセスを再スケジュールし、それらを他のCPUコアなどに移動する場合があります。UNIXベースのシステムとしてのWindowsは両方ともこのように動作します。

Windows:4つのコアで2つのプロセスを実行:

OSX:8つのコアで4つのプロセスを実行

enter image description here

両方のOSがコア間でプロセスのバランスをとっていることがわかりますが、均等ではないため、他のコアよりも高いパーセンテージのコアがいくつか表示されます。

Sleep

絶対に行きたい場合、プロセスが特定のコアの100%を決して消費しないようにする(たとえば、CPUファンの起動を防ぎたい場合)場合、処理機能でスリープを実行できます:

_from time import sleep

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

これにより、OSは各計算で_0.01_秒プロセスを「スケジュール」し、他のアプリケーションのためのスペースを確保します。他のアプリケーションがない場合、CPUコアはアイドル状態であるため、100%になることはありません。異なるスリープ時間で遊ぶ必要があります。また、実行するコンピューターによっても異なります。非常に洗練されたものにしたい場合は、cpu_times()が報告する内容に応じてスリープを調整できます。

13
hansaplast

OSレベルで

Niceを使用して、単一のコマンドに優先順位を設定できます。 Niceを使用してpythonスクリプトを開始することもできます。以下から: http://blog.scoutapp.com/articles/2014/11/04/restricting-process-cpu -usage-using-Nice-cpulimit-and-cgroups

ニース

Niceコマンドは、プロセスの優先度レベルを調整して、実行頻度を減らします。これは、CPUを集中的に使用するタスクをバックグラウンドジョブまたはバッチジョブとして実行する必要がある場合に便利です。 nicenessレベルの範囲は、-20(最も有利なスケジューリング)から19(最も有利ではない)です。 Linux上のプロセスは、デフォルトで0のnicenessで開始されます。 Niceコマンド(追加パラメーターなし)は、プロセスをナイスネス10で開始します。このレベルでは、スケジューラーは優先度の低いタスクとしてそれを認識し、CPUリソースを減らします。なしの場合:

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

今トップを実行します。

enter image description here

Pythonの関数として

別のアプローチは、psutilsを使用して過去1分間のCPU負荷平均をチェックし、指定されたCPU負荷ターゲットを下回っている場合はスレッドにCPU負荷平均をチェックし、別のスレッドをスプールしてもらい、 CPU負荷ターゲットの上。これは、コンピューターを使用しているときは邪魔になりませんが、一定のCPU負荷を維持します。

# 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
jonnyjandles