Обычно говорят, что 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/
Добавить комментарий