Как я могу профилировать многопоточную программу на Python?

Я разрабатываю по сути многопоточный модуль на Python, и я хотел бы узнать, где он проводит свое время. cProfile, кажется, только профилирует основной поток. Есть ли способ профилирования всех потоков, участвующих в расчете?

5 ответов

Решение

Пожалуйста, смотрите yappi (еще один Python Profiler).

Вместо запуска одного cProfileВы могли бы бежать отдельно cProfile экземпляр в каждой теме, а затем объединить статистику. Stats.add() делает это автоматически.

Если вы в порядке с небольшой дополнительной работой, вы можете написать свой собственный класс профилирования, который реализует profile(self, frame, event, arg), Это вызывается всякий раз, когда вызывается функция, и вы можете довольно легко настроить структуру для сбора статистики по ней.

Вы можете использовать threading.setprofile зарегистрировать эту функцию в каждом потоке. Когда функция вызывается, вы можете использовать threading.currentThread() чтобы увидеть, на чем он работает. Более подробная информация (и готовый рецепт) здесь:

http://code.activestate.com/recipes/465831/

http://docs.python.org/library/threading.html

Учитывая, что основные функции ваших разных потоков отличаются, вы можете использовать очень полезные profile_func() декоратор отсюда.

Проверять, выписываться mtprof из проекта Dask:

https://github.com/dask/mtprof

Это прямая замена для cProfileто есть, если ваши потоки запускаются обычным способом и завершаются до вашего основного потока, их статистика сводится к той же статистике отчетов. Сработало для меня как шарм.

С 2019 года: мне понравилось предложение vartec, но мне бы очень понравился пример кода. Поэтому я построил его - его несложно реализовать, но вам нужно принять во внимание несколько вещей. Вот рабочий пример (Python 3.6):

Вы можете видеть, что результаты учитывают время, затраченное на вызовы Thread1 и thread2 к thread_func().

Единственные изменения, которые вам нужны в вашем коде, - это создать подкласс threading.Thread, переопределив его метод run(). Минимальные изменения для легкого профилирования резьбы.

import threading
import cProfile
from time import sleep
from pstats import Stats
import pstats
from time import time
import threading
import sys

# using different times to ensure the results reflect all threads
SHORT = 0.5
MED = 0.715874
T1_SLEEP = 1.37897
T2_SLEEP = 2.05746
ITER = 1
ITER_T = 4

class MyThreading(threading.Thread):
    """ Subclass to arrange for the profiler to run in the thread """
    def run(self):
        """ Here we simply wrap the call to self._target (the callable passed as arg to MyThreading(target=....) so that cProfile runs it for us, and thus is able to profile it. 
            Since we're in the current instance of each threading object at this point, we can run arbitrary number of threads & profile all of them 
        """
        try:
            if self._target:
                # using the name attr. of our thread to ensure unique profile filenames
                cProfile.runctx('self._target(*self._args, **self._kwargs)', globals=globals(), locals=locals(), filename= f'full_server_thread_{self.name}')
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self._target, self._args, self._kwargs

def main(args):
    """ Main func. """
    thread1_done =threading.Event()
    thread1_done.clear()
    thread2_done =threading.Event()
    thread2_done.clear()

    print("Main thread start.... ")
    t1 = MyThreading(target=thread_1, args=(thread1_done,), name="T1" )
    t2 = MyThreading(target=thread_2, args=(thread2_done,), name="T2" )
    print("Subthreads instances.... launching.")

    t1.start()          # start will call our overrident threading.run() method
    t2.start()

    for i in range(0,ITER):
        print(f"MAIN iteration: {i}")
        main_func_SHORT()
        main_func_MED()

    if thread1_done.wait() and thread2_done.wait():
        print("Threads are done now... ")
        return True

def main_func_SHORT():
    """ Func. called by the main T """
    sleep(SHORT)
    return True

def main_func_MED():
    sleep(MED)
    return True


def thread_1(done_flag):
    print("subthread target func 1 ")
    for i in range(0,ITER_T):
        thread_func(T1_SLEEP)
    done_flag.set()

def thread_func(SLEEP):
    print(f"Thread func")
    sleep(SLEEP)

def thread_2(done_flag):
    print("subthread target func 2 ")
    for i in range(0,ITER_T):
        thread_func(T2_SLEEP)
    done_flag.set()

if __name__ == '__main__':

    import sys
    args = sys.argv[1:]
    cProfile.run('main(args)', f'full_server_profile')
    stats = Stats('full_server_profile')
    stats.add('full_server_thread_T1')
    stats.add('full_server_thread_T2')
    stats.sort_stats('filename').print_stats()

Я не знаю ни одного приложения для профилирования, которое бы поддерживало такую ​​вещь для python, но вы могли бы написать класс Trace, который записывает лог-файлы, в которые вы вводите информацию о том, когда операция начинается, когда она заканчивается, и сколько времени она выполняет. потребляются.

Это простое и быстрое решение вашей проблемы.

Другие вопросы по тегам