- Как ускорить код на Python в тысячу раз
- Первопричина такой медленности
- Решение: универсальные функции NumPy
- Подводим итог
- Приложение — код тестов на C, C#, Java и NodeJS
- На правах рекламы
- Приёмы для ускорения кода на Python
- Используйте подходящие структуры данных
- Избегайте циклов for
- Применяйте списковые включения (list comprehension)
- Не пренебрегайте множественным присваиванием
- Не создавайте глобальные переменные
- Применяйте библиотечные функции
- Соединяйте строки методом join
- Используйте генераторы
- Будьте бдительны
- Избегайте точек
- Используйте 1 в бесконечных циклах
- Попробуйте другие подходы
- Используйте ускорители
- Для больших датасетов используйте специальные библиотеки
- Используйте последнюю версию Python
- Заключение
Как ускорить код на Python в тысячу раз
В любых соревнованиях по скорости выполнения программ Python обычно занимает последние места. Кто-то говорит, что это из-за того, что Python является интерпретируемым языком. Все интерпретируемые языки медленные. Но мы знаем, что Java тоже язык такого типа, её байткод интерпретируется JVM. Как показано, в этом бенчмарке, Java намного быстрее, чем Python.
Вот пример, способный показать медленность Python. Используем традиционный цикл for для получения обратных величин:
import numpy as np np.random.seed(0) values = np.random.randint(1, 100, size=1000000) def get_reciprocal(values): output = np.empty(len(values)) for i in range(len(values)): output[i] = 1.0/values[i] %timeit get_reciprocal(values)
Ничего себе, на вычисление всего 1 000 000 обратных величин требуется 3,37 с. Та же логика на C выполняется за считанные мгновения: 9 мс; C# требуется 19 мс; Nodejs требуется 26 мс; Java требуется 5 мс(!), а Python требуется аж целых 3,37 СЕКУНДЫ. (Весь код тестов приведён в конце).
Первопричина такой медленности
Обычно мы называем Python языком программирования с динамической типизацией. В программе на Python всё представляет собой объекты; иными словами, каждый раз, когда код на Python обрабатывает данные, ему нужно распаковывать обёртку объекта. Внутри цикла for каждой итерации требуется распаковывать объекты, проверять тип и вычислять обратную величину. Все эти 3 секунды тратятся на проверку типов.
В отличие от традиционных языков наподобие C, где доступ к данным осуществляется напрямую, в Python множество тактов ЦП используется для проверки типа.
Даже простое присвоение числового значения — это долгий процесс.
Шаг 1. Задаём a->PyObject_HEAD->typecode тип integer
Подробнее о том, почему Python медленный, стоит прочитать в чудесной статье Джейка: Why Python is Slow: Looking Under the Hood
Итак, существует ли способ, позволяющий обойти проверку типов, а значит, и повысить производительность?
Решение: универсальные функции NumPy
В отличие list языка Python, массив NumPy — это объект, созданный на основе массива C. Доступ к элементу в NumPy не требует шагов для проверки типов. Это даёт нам намёк на решение, а именно на Universal Functions (универсальные функции) NumPy, или UFunc.
Если вкратце, благодаря UFunc мы можем проделывать арифметические операции непосредственно с целым массивом. Перепишем первый медленный пример на Python в версию на UFunc, она будет выглядеть так:
import numpy as np np.random.seed(0) values = np.random.randint(1, 100, size=1000000) %timeit result = 1.0/values
Это преобразование не только повышает скорость, но и укорачивает код. Отгадаете, сколько теперь времени занимает его выполнение? 2,7 мс — быстрее, чем все упомянутые выше языки:
2,71 мс ± 50,8 мкс на цикл (среднее значение ± стандартное отклонение после =7 прогонов по 100 циклов каждый)
Вернёмся к коду: самое важное здесь — это 1.0/values . values — это не число, а массив NumPy. Наряду с оператором деления есть множество других.
Здесь можно найти все операторы Ufunc.
Подводим итог
Если вы пользуетесь Python, то высока вероятность того, что вы работаете с данными и числами. Эти данные можно хранить в NumPy или DataFrame библиотеки Pandas, поскольку DataFrame реализован на основе NumPy. То есть с ним тоже работает Ufunc.
UFunc позволяет нам выполнять в Python повторяющиеся операции быстрее на порядки величин. Самый медленный Python может быть даже быстрее языка C. И это здорово.
Приложение — код тестов на C, C#, Java и NodeJS
#include #include #include int main() < struct timeval stop, start; int length = 1000000; int rand_array[length]; float output_array[length]; for(int i = 0; igettimeofday(&start, NULL); for(int i = 0; i gettimeofday(&stop, NULL); printf("took %lu us\n", (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec); printf("done\n"); return 0; >
using System; namespace speed_test < class Program< static void Main(string[] args)< int length = 1000000; double[] rand_array =new double[length]; double[] output = new double[length]; var rand = new Random(); for(int i =0; ilong start = DateTimeOffset.Now.ToUnixTimeMilliseconds(); for(int i =0;i long end = DateTimeOffset.Now.ToUnixTimeMilliseconds(); Console.WriteLine(end - start); > > >
import java.util.Random; public class speed_test < public static void main(String[] args)< int length = 1000000; long[] rand_array = new long[length]; double[] output = new double[length]; Random rand = new Random (); for(int i =0; ilong start = System.currentTimeMillis(); for(int i = 0;i long end = System.currentTimeMillis(); System.out.println(end - start); > >
let length = 1000000; let rand_array = []; let output = []; for(var i=0;i let start = (new Date()).getMilliseconds(); for(var i=0;i let end = (new Date()).getMilliseconds(); console.log(end - start);
На правах рекламы
Воплощайте любые идеи и проекты с помощью наших VDS с мгновенной активацией на Linux или Windows. Создавайте собственный конфиг в течение минуты!
Приёмы для ускорения кода на Python
Для ускорения кода на Python программисты могут использовать много приемов. Мы собрали несколько самых простых и при этом самых эффективных из них.
Python – один из самых популярных языков программирования в мире. Этим он обязан своему простому синтаксису и богатой экосистеме. В последнее время он используется в соревновательном программировании, где большое значение имеет скорость выполнения программ.
Большинство из наших читателей, вероятно, уже начали писать на Python. Сперва всё кажется простым и очевидным. Но при решении задач со сложными алгоритмами начинается головная боль с Time Limit Exceeded . Однако, в этом нет вины Python – это вина программиста. Да, Python медленный, но если программист напишет эффективную программу, она точно выполнится без подобных загвоздок.
Представляем вам несколько приемов и подходов для ускорения кода и повышения его эффективности.
Используйте подходящие структуры данных
Применение правильных структур данных значительно ускоряет выполнение кода.
В Python встроены такие структуры данных, как список ( list ), кортеж ( tuple ), множество ( set ) и словарь ( dictionary ). Несмотря на это, большинство людей хорошо помнят только про списки. Это неправильный подход.
Для ускорения кода используйте те структуры данных, которые максимально соответствуют вашей задаче. Особенно это касается выбора между списком и кортежем, ведь итерирование по последнему занимает куда меньше времени.
Избегайте циклов for
В случаях, когда цикл for обрабатывает диапазон непостоянного размера, его выполнение в Python происходит медленнее, чем выполнение цикла while . Поэтому в таких случаях лучше прибегайте к while .
Применяйте списковые включения (list comprehension)
Не обращайтесь ни к какой другой технике, если можно использовать списковые включения. Например, этот код заносит в список все числа между 1 и 1000, кратные 3:
L = [] for i in range (1, 1000): if i%3 == 0: L.append (i)
Со списковыми включениями код трансформируется в одну строку:
L = [i for i in range (1, 1000) if i%3 == 0]
Этот приём работает быстрее, чем просто метод append() .
Не пренебрегайте множественным присваиванием
Не стоит инициализировать несколько переменных так:
Лучше придерживайтесь следующего синтаксиса:
Не создавайте глобальные переменные
Да, в Python есть ключевое слово global для объявления таких переменных. Но операции с ними требуют больше времени, чем с локальными. Потому не создавайте глобальные переменные без крайней необходимости.
Применяйте библиотечные функции
Не пишите функцию вручную, если она уже реализована в какой-нибудь библиотеке. Библиотечные функции крайне эффективны, и, скорее всего, вам не удастся достичь лучшего результата самостоятельно.
Соединяйте строки методом join
В Python конкатенацию строк можно производить при помощи знака + .
concatenatedString = "Программирование " + "это " + "весело."
Но также для этого есть метод join() .
concatenatedString = " ".join (["Программирование", "это", "весело."])
Всё дело в том, что оператор + каждый раз создаёт новую строку, а затем копирует в неё исходные. join() устроен иначе и обеспечивает выигрыш во времени.
Используйте генераторы
Если у вас в списке хранится много данных, которые требуется использовать все за раз, применяйте generator . Это сэкономит ваше время.
Будьте бдительны
Взгляните на следующий код:
Данный код может показаться эффективным, так как в нём для удаления дубликатов используется set . Но на самом деле он будет выполняться долго. Не забывайте, что приведение списка ко множеству – это время. Так что этот вариант будет лучше:
Избегайте точек
Старайтесь не пользоваться ими. Взгляните на пример:
import math val = math.sqrt(60)
Вместо этого можно применить следующий синтаксис:
from math import sqrt val = sqrt(60)
Всё потому, что когда вы вызываете функцию с помощью точки, она сперва обращается к методу __getattribute()__ или __getattr()__ . Эти методы, в свою очередь, используют операции со словарями, отнимающие время. Поэтому старайтесь писать: from module import function .
Используйте 1 в бесконечных циклах
Пишите while 1 вместо while True . Это выиграет вам немного времени.
Попробуйте другие подходы
Не бойтесь применять новые практики для повышения эффективности кода.
if a_condition: if another_condition: do_something else: raise exception
Вместо этого стоит попробовать:
if (not a_condition) or (not another_condition): raise exception do_something
Используйте ускорители
Медлительность Python послужила вдохновением для различных проектов, сокращающих его время работы. На большинстве соревнований по программированию вы встретитесь с pypy (там, где можно писать на Python).
Эти средства помогут уменьшить время выполнения Python-программ.
Для больших датасетов используйте специальные библиотеки
C/C++ быстрее Python. Поэтому многие пакеты и модули, которые можно использовать в программах на Python, пишутся на C/C++. Среди таких модулей – Numpy, Scipy и Pandas, столь необходимые при обработке больших массивов данных.
Используйте последнюю версию Python
Python регулярно обновляется и совершенствуется и с каждым релизом становится всё быстрее и оптимизированнее. Поэтому для ускорения кода всегда пишите его на новейшей версии языка.
Заключение
Мы рассмотрели приёмы для ускорения кода на Python. Конечно, этот список не исчерпывающий: есть и другие способы, которые могут вам пригодиться. Обязательно ищите их и пишите код эффективно!