Пользователь
0,0
рейтинг
1 июня 2013 в 21:38

Разработка → Тестирование производительности Python 2.7 при обработке списков различными способами из песочницы

image

В ходе одного из моих питоновских проектов, с большой примесью ООП и обработкой большого числа данных — у меня возник вопрос, а насколько эффективно обрабатывать списки в классе с использованием вызовов его методов, или может использовать вызов внешней функции? Для этого были написаны 24 теста которые показали очень интересные результаты, Кому интересна данная тема, прошу в подробности.


Тестовый образец:
создается список из 500 тыс. элементов и заполняется случайными значениями
lst = []
for i in range(1, 500000):
    val = random() + i
    lst.append(val)


Обработка в 2 этапа:
1) сначала получение нового списка путем возведение в квадрат каждого элемента списка (несколькими способами)
2) потом добавление к полученному списку некоторой константы, которая может быть: локальной, глобально, аттрибутом (для класса)

Например так
@howlong
def process_list_global_func_plus_local_value():
    """Функция. Обработка в цикле с вызовом глобальной функции и добавлением локальной переменной"""
    local_plus_value = GLOBAL_VALUE
    new_lst = []
    for i in lst:
        new_lst.append(global_func(i))
    for v in new_lst:
        v + local_plus_value


Производительность
Под производительностью понималось длительность выполнения каждого теста, помереная с помощью декоратора

import time
def howlong(f):
    def tmp(*args, **kwargs):
        t = time.time()
        res = f(*args, **kwargs)
        need_time = time.time()-t
        tmp.__name__ = f.__name__
        tmp.__doc__ = f.__doc__
        #print u"%s time: %f" % ((f.__doc__), need_time)
        print ".",
        return need_time
    return tmp


Были получены следующие результаты по двум группам тестов:

Функция. Обработка Генератором без вызова внешних функций и добавлением локальной переменной — 0.192 — 100%
Функция. Обработка Генератором без вызова внешних функций и добавлением глобальной переменной — 0.2 — 104%
Функция. Обработка в цикле без вызова внешних функций и добавлением локальной переменной — 0.238 — 123%
Функция. Обработка в цикле без вызова внешних функций и добавлением глобальной переменной — 0.245 — 127%
Функция. Обработка с использованием map и добавлением локальной переменной — 0.25 — 130%
Функция. Обработка с использованием map и добавлением глобальной переменной — 0.255 — 132%
Функция. Обработка Генератором с вызовом локальной функции и добавлением локальной переменной — 0.258 — 134%
Функция. Обработка Генератором с вызовом глобальной функции и добавлением глобальной перем. — 0.274 — 142%
Функция. Обработка в цикле с вызовом локальной функции и добавлением локальной переменной — 0.312 — 162%
Функция. Обработка в цикле с вызовом локальной функции и добавлением глобальной переменной — 0.32 — 166%
Функция. Обработка в цикле с вызовом глобальной функции и добавлением глобальной переменной — 0.327 — 170%
Функция. Обработка в цикле с вызовом глобальной функции и добавлением локальной переменной — 0.332 — 172%

Класс. Обработка Генератором без вызова внешних функций и добавлением локальной переменной — 0.191 — 100%
Класс. Обработка Генератором без вызова внешних функций и добавлением значения глобальной пер. — 0.20 — 104%
Класс. Обработка Генератором без вызова внешних функций и добавлением значения аттрибута — 0.213 — 111%
Класс. Обработка вызовом локальной функции и добавлением локальной переменной — 0.312 — 163%
Класс. Обработка вызовом глобальной функции и добавлением локальной переменной — 0.318 — 166%
Класс. Обработка вызовом локальной функции и добавлением глобальной переменной — 0.318 — 166%
Класс. Обработка вызовом глобальной функции и добавлением глобальной переменной — 0.328 — 171%
Класс. Обработка вызовом локальной функции и добавлением значения аттрибута — 0.333 — 174%
Класс. Обработка вызовом глобальной функции и добавлением значения аттрибута — 0.34 — 178%
Класс. Обработка вызовом метода класса и добавлением локальной переменной — 0.39 — 204%
Класс. Обработка вызовом метода класса и добавлением глобальной переменной — 0.398 — 208%
Класс. Обработка вызовом метода класса и добавление значения аттрибута — 0.411 — 215%

Выводы:

Наибольший интерес представляю процентные разницы.

По обработке списка функцией
1) Самая быстрая обработка списка — в генераторе без вызова внешних функций и с использованием локальных переменных, с использованием глобальных переменных будет на ~5% дольше
2) при обработке списка перебором в цикле for время работы будет на 23% с использование локальных переменных и на 27% дольше с использованием глобальных переменных, чем при обработке через генератор
3) при использовании в цикле for для обработки списка внешней локальной или глобальной функции скорость будет ниже более чем на 60%
4) при использовании map в качестве функции обработки элементов списка практически такой же как ри использовании вызова внешней функции в генераторе
5) самым долгим способом обработки списка является обработка в цикле с использование вызова внешней глобальной функции

По обработке списка в классе
1) в классах доступ к аттрибутам дольше чем к локальным переменным, примерно на 10%, и на 5% дольше чем к глобальным переменным
2) в классах доступ к методам дольше, чем к локальным функциям примерно на 22% и на 20% дольше чем к глобальным функциям
3) при обработке списка в классе с использованием генератора получается также быстро, как и с использованием функции с генератором
4) самым долгим способом (более чем в 2 раза дольше) оказалось использование вызова метода в цикле и добавлением значения аттрибута, что явилось для меня большим удивлением

Код доступен в одном файле по адресу http://pastebin.com/rgdXNdXb

Дальше планирую исследовать скорость доступа к элементам списков, туплов, различных видом словарей, а потом вопросы целесообразности кеширования функций и методов классов
@pymen
карма
7,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (15)

  • +2
    Стоит упомянуть, что для численных массивов лучше использовать numpy.
  • +7
    Добавьте, пожалуйста, в howlong вызов функции хотя бы пару десятков раз, а то разница в несколько десятых секунды вполне может быть погрешностью измерения.
    • 0
      Проверил, все осталось на прежних позициях, только время выросло в 10 раз :)
  • +7
    Уважаемый автор, для профилирования в Python есть замечательные библиотеки cProfile и profile. Измерение времени выполнения чего-либо светрую начинать именно с них. docs.python.org/2/library/profile.html
    • +1
      Здесь больше уместен timeit.
  • +1
    Рекомендую в книжке «Идеальный код», Глава 18. «Реализация словарей Python: стремление быть всем во всем полезным».
    Со словарями в Python не все просто. Они имеют различные реализации, могут переиспользоваться и могут налету переключать функцию сравнения ключей.
    • 0
      Спасибо большое что упомянули книгу, просмотрел оглавление и сразу заказал :)
  • 0
    удивительно что цикл медленее аж на 20%. там под капотом настолько большая разница?
  • +2
    Может не совсем в тему, но это:
    tmp.__name__ = f.__name__
    tmp.__doc__ = f.__doc__

    вполне можно заменить декоратором functools.wraps:
    def howlong(f):
        @functools.wraps(f)
        def tmp(*args, **kwargs):
    
  • +1
    Боюсь огорчить, но тесты не показательны: пространства имен в питоне — это те же словари; области видимости определяются вложенностью. Таким образом, в вашем случае, скорость доступа определяется вложенностью и размером словаря модуля. Как только размер словаря модуля изменится, вполне можете ожидать других результатов.

    На PyCon2013, если правильно помню, было занятие, посвященное пространствам имён. Посмотрите видео, возможно, убдет полезно.
    • 0
      спасибо за полезный комментарий
  • 0
    Ребят, понимаю что оффтопик, начал учить Питон 3.3 для научных целей (анализ соц. сетей и обработка естественного языка).
    Подскажите основные ресурсы по Питону на русском, именно форумы чтобы спрашивать, набрал в гугле «python русское сообщество» и ВСЕ первые ссылки это заброшенные форумы, последнее сообщение в которых хорошо если несколько месяцев назад. Самый актуальный это пока vingrad — но и на нем не так чтоб много народу.
    Английский знаю и пользуюсь преимущественно pyvideo.org www.codecademy.com/ru/tracks/python и stackoverflow.
  • 0
    Еще один тест, будет занимать 3 место (110-120%)

    @howlong
    def process_list_without_dots():
        """Суперфункция с заменой вызова метода объекта на локальную переменную"""
        local_plus_value = GLOBAL_VALUE
        new_lst = []
        append = new_lst.append
        for i in lst:
            append(i**2)
        for v in new_lst:
            v + local_plus_value
    
    • 0
      спасибо за идею, совсем забыл про такой способ

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.