Как ускорить код на 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)

Результат:

3,37 с ± 582 мс на цикл (среднее значение ± стандартное отклонение после 7 прогонов по 1 циклу)

Ничего себе, на вычисление всего 1 000 000 обратных величин требуется 3,37 с. Та же логика на C выполняется за считанные мгновения: 9 мс; C# требуется 19 мс; Nodejs требуется 26 мс; Java требуется 5 мс(!), а Python требуется аж целых 3,37 СЕКУНДЫ. (Весь код тестов приведён в конце).

Первопричина такой медленности

Обычно мы называем Python языком программирования с динамической типизацией. В программе на Python всё представляет собой объекты; иными словами, каждый раз, когда код на Python обрабатывает данные, ему нужно распаковывать обёртку объекта. Внутри цикла for каждой итерации требуется распаковывать объекты, проверять тип и вычислять обратную величину. Все эти 3 секунды тратятся на проверку типов.

В отличие от традиционных языков наподобие C, где доступ к данным осуществляется напрямую, в Python множество тактов ЦП используется для проверки типа.

Даже простое присвоение числового значения — это долгий процесс.

a = 1

Шаг 1. Задаём a->PyObject_HEAD->typecode тип integer

Шаг 2. Присваиваем a->val =1

Подробнее о том, почему 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

Язык C:

#include <stdio.h> #include <stdlib.h> #include <sys/time.h>  int main(){     struct timeval stop, start;     int length = 1000000;     int rand_array[length];     float output_array[length];     for(int i = 0; i<length; i++){         rand_array[i] = rand();     }     gettimeofday(&start, NULL);     for(int i = 0; i<length; i++){         output_array[i] = 1.0/(rand_array[i]*1.0);     }     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; }

C#(dotnet 5.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; i<length;i++){                 rand_array[i] = rand.Next();                 //Console.WriteLine(rand_array[i]);             }             long start = DateTimeOffset.Now.ToUnixTimeMilliseconds();             for(int i =0;i<length;i++){                 output[i] = 1.0/rand_array[i];             }             long end = DateTimeOffset.Now.ToUnixTimeMilliseconds();             Console.WriteLine(end - start);         }     } }

Java:

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; i<length; i++){             rand_array[i] = rand.nextLong();         }         long start = System.currentTimeMillis();         for(int i = 0;i<length; i++){             output[i] = 1.0/rand_array[i];         }         long end = System.currentTimeMillis();         System.out.println(end - start);     } }

NodeJS:

let length = 1000000; let rand_array = []; let output = []; for(var i=0;i<length;i++){     rand_array[i] = Math.floor(Math.random()*10000000); } let start = (new Date()).getMilliseconds(); for(var i=0;i<length;i++){     output[i] = 1.0/rand_array[i]; } let end = (new Date()).getMilliseconds(); console.log(end - start);

На правах рекламы

Воплощайте любые идеи и проекты с помощью наших VDS с мгновенной активацией на Linux или Windows. Создавайте собственный конфиг в течение минуты!

ссылка на оригинал статьи https://habr.com/ru/company/vdsina/blog/552378/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *