Как ускорить код на Python в тысячу раз

Автор оригинала: Andrew Zhu
  • Перевод


Обычно говорят, что 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. Создавайте собственный конфиг в течение минуты!

VDSina.ru
Серверы в Москве и Амстердаме

Комментарии 70

    +17
    Ждем следующей статьи на тему «как сварить кашу из топора».
      +35

      Исходник на си без ключей компилятора смысла никакого не имеет. Утверждение автора о том, что обобщенный код numpy работает быстрее, чем захардкоженный цикл на си выдаёт полное невладение матчастью. Зачем вы этот мусор сюда тащите?

        +3

        При размере массива в миллион накладные расходные на разбор вызова малы.
        А в numpy точно нету ассемблера? Так-то там еще тяжело оптимизированная интеловская библиотека используется.

          0

          numpy опирается на BLAS/LAPACK, насколько помню, а там полный набор, включая интринсики.

        0
        Можно еще написать часть кода, требующую больших вычислений, на том же Cython. Скорость числовых вычислений может вырасти в несколько сотен раз. Это тоже как один из вариантов ускорения там, где не получится использовать numpy.
          0
          А ещё есть numba.njit (не нужны сторонние компиляторы)
          +1
          К сожалению, не указан конфиг, но ради интереса решил проверить PHP 7.4 на 16-ядерном Intel Core i9-9900K.
          $length = 1000000;
          $rand_array = [];
          $output = [];
          for($i = 0; $i < $length; $i++){
              $rand_array[$i] = random_int(1, PHP_INT_MAX);
          }
          $start = microtime(1);
          for($i=0; $i < $length; $i++){
              $output[$i] = 1.0/$rand_array[$i];
          }
          
          echo round(microtime(1) - $start, 4);

          Мои результаты: 0.0347 0.0333 0.0324 0.0332 0.0336

          C: 2071 us 2031 us 2158 us 2788 us 2159 us [получается, где-то в 15 раз быстрее]

          Другие интерпретаторы на сервере не стоят, но может у кого стоит Python с NumPy, можете сравнить с PHP.
            +1
            Что-то мне подсказывает, что наличие 16 ядер на этот код никак не влияет. Тут просто нужны «тупые мегагерцы» одного ядра.
            У меня на Xeon 6140 (2.3GHz) тот PHP код выдал 0.053, а Py код выше выдал 0.0015 (изначально «медленный» код на Py мне выдал 1.62).
            Если увеличить количество итераций на порядок, то будет 0.71 и 0.075 (16.2) соотвественно.
            C-код дал 0.0012 (-O3, gcc 8.3.0).
              0
              Да, упоминая многоядерность я указал на то, что влияние сторонних процессов компенсируется распределением (да и если быть точным, это я забыл, что cat /proc/cpuinfo показывает количество потоков, а не физических ядер, которых восемь), а процессор работает 3.6 и до 5Ghz в турбобусте.
                0
                Кстати, на современных Linux дистрибутивах есть полезная команда «lscpu». Там более очевидно что и сколько есть на процессоре.
                Любопытно включается ли TurboBoost на таком коротком коде.
            +7
            на вычисление всего 1 000 000 обратных величин требуется 3,37 с. Та же логика на C выполняется за считанные мгновения: 9 мс;

            массив NumPy — это объект, созданный на основе массива C.

            Отгадаете, сколько теперь времени занимает его выполнение? 2,7 мс — быстрее, чем все упомянутые выше языки:


            Погодите, а как так получилось? Вот под этим же колпачком шарик!
              +16
              Потому что код на ++ написан плохо.
              output_array[i] = 1.0/(rand_array[i]*1.0);

              следует заменить на
              output_array[i] = 1.0f/float(rand_array[i]);

              и уже получим не 9мс а 6мс
              в оригинале: конвертация из int в double, лишнее умножение (которое не выкинется из-за double), потом вычисление в double а потом конвертация в float. В прочем остальные примеры тоже не сильно запариваются на счёт конвертаций типов, и вычислений в double или float, «этож одно итоже» наверно думает автор.
              А NumPy мало того что написан на С так ещё и паралелит подобные вычисления, вообще отличное сравнение. Смотрите реализация на С быстрее чем на С.

                +3
                Потому что код на ++ написан плохо.

                Даже странно, почему авторы статей, посвященных ускорению Питона, иногда игнорируют очевидные вещи. Это ведь не особенность ++; в большинстве «классических» языков будет так же. Например, три варианта программы на фортране, которые делают одно и то же (засечка времени для простоты опущена):

                      integer*8, parameter :: array_size= 1 000 000 000    
                      real*4 ::     array1(array_size), array2(array_size) 
                с
                      forall (ii= 1:array_size)
                        array1(ii)= real(ii)
                      end forall
                c
                c    1. Выполняется приведение типа
                      do ii= 1,array_size
                        array2(ii)= 1./ii      
                      end do
                c
                c    2. Приведение типа не требуется:
                      do ii= 1,array_size
                        array2(ii)= 1./array1(ii)
                      end do
                c
                c    3. Массивная операция:
                      array2= 1./array1
                

                Работают за:
                1: 2.46 с
                2: 0.28 с
                3: 0.11 с

                При изучении языка, конечно, удобно, что можно вообще не задумываться о подобных вещах. Но когда встает вопрос об оптимизации, это может стать недостатком.
                  0
                  А если описать
                  float rand_array[length];
                  то даже код
                  output_array[i] = 1/rand_array[i];
                  У меня выполняется:
                  took 1362 us

                  Вместо
                  took 3664 us
                    0
                    А если на код на питоне скомпилировать намбой, будет еще быстрее.

                    Сore i3-8100, 3.6 ГГц: 564 µs (буста нет)
                    Intel® Xeon® CPU @ 2.20GHz: 785 µs гугл колаб colab.research.google.com/drive/1t7q1hUFDis5aV4xRszY0sD6jFR5iHeTm#scrollTo=1Ou_paImmQfQ

                    import numpy as np
                    import numba
                    @numba.njit('float64[:](float64[:],float64[:])')
                    def get_r(values,results):
                        for y,x in zip(results,values):
                            y = 1./x
                        return results
                    values = np.array([random.randint(1, 100) for _ in range(1_000_000)], dtype = np.float64)
                    results = np.zeros(vals.shape, dtype = np.float64)
                    %timeit get_r(values,results)
                    

                    Получается, если скомпилировать jit-компилятором, на питоне код все равно быстрее.
                    Скорее все же причина в каких то шероховатостях кода на плюсах.
                      0
                      Извиняюсь, все же моя модификация исходного кода содержала ошибку (не возвращала результат):
                      Вот как должно быть

                      import numpy as np
                      np.random.seed(0)
                      values = np.random.randint(1, 100, size=1_000_000, dtype = np.uint8)
                      
                      @numba.njit('float32[:](uint8[:],float32[:])')
                      def get_reciprocal(values, output):
                          for i, x in enumerate(values):
                              output[i] = 1./x
                          return output
                      output = np.empty(len(values), dtype = np.float32)      
                      %timeit get_reciprocal(values,output)
                      

                      После этого, на i3-8100 код исполняется 1.16 мс
                      На Intel Xeon CPU @ 2.20GHz 1.59
                      Интересно, предел ли это ускорения питона))
                  +3
                  Полагаю UFunc под капотом исппользуюи векторные инструкции, а ваш пример на Си это обычный код, полагаю даже без автовекторизации. В таком случае 3х кратное отставание не так уж и страшо, и если переписать Си код на векторные иструкции мы получим результат в районе 0-1 мс.
                    +4
                    Достаточно сказать gcc -O3 и он сам в этом случае выполнит векторизацию. Переписывать ничего не нужно.
                    0

                    Любопытно. Если тип задаётся как поле в С-подобной структуре — почему проверка типа делается настолько медленно? Наверное, есть ещё что-то, о чем в статье не рассказано.
                    Писать на python как на С это неоптимально, конечно да. Но использовать магию numpy за пределами numpy ведь тоже не получится?

                      +1
                      Если тип задаётся как поле в С-подобной структуре — почему проверка типа делается настолько медленно?

                      Потому что там не только проверка типа, но и полиморфизм, вырождающийся в несколько уровней косвенной адресации.

                        0

                        В плюсах есть rtti, программы от этого в 1000 раз не замедляются же.

                          +3

                          В плюсах у вас нет обязательности проверки через rtti/аналог на каждой операции и поиска, чем же выполнить этот '+' в текущем случае. А в CPython — есть, он обязан на каждой операции проверить, кто и как реализует этот '+' (упрощая, ищется метод __add__ у левого операнда, если нет — то __radd__ у правого, и каждый этот поиск может быть ещё и лукапом в хэш-мапу, а если там объект класса со сложной иерархией, то перебор всех родителей с лукапом у каждого...) В C++, если вы явным кодом не реализовали такой поиск, то операция определяется в момент компиляции. В JIT вариантах Python, типа PyPy, есть частичная оптимизация такого, но там в лучшем случае единицы раз ускорения, а не сотни.

                      +40
                      Как ускорить код на Python?
                      Написать код на C, и вызывать из Python как внешнюю библиотеку.

                      Краткое содержание 90% статей по ускорению Питона.
                        0

                        Как будто это что-то плохое)
                        Весь этот код давно написан, оптимизирован и хорошо задокументирован.
                        Так почему бы не использовать эти сишные библиотеки и не тратить лишнее время?

                          0
                          Так почему бы не использовать эти сишные библиотеки и не тратить лишнее время?

                          Плохо не то, где эти методы ускорения кода на Python работают, а то, где они не работают. А таких мест вагон и маленькая тележка, и слезть с легаси бывает ой тяжело.


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


                          В идеале нужен полный, согласованный со всеми инструментальными средствами бутерброд стиля C# -> C++/CLI -> C (-> asm для особых случаев). Где надо (нечасто) поверх него и скриптовый язык.

                            0

                            Да, по моему для большинства задач это стрельба из пушки по воробьям

                              0

                              Для большинства задач каких? Даже если смотреть в текущие ниши Python, есть две наиболее характерных — 1) математика (как раз в основном решается враппингом numpy/scipy/tensorflow/etc.) и 2) веб (а вот тут уже движкам работы с HTTP/etc. вся эта динамика сильно мешает, замедляя на порядок).

                                0

                                всё равно большую часть времени ждём ввод/вывод, от смены яп там много не выиграешь

                                  +1
                                  всё равно большую часть времени ждём ввод/вывод

                                  У вас больше одного клиента к вебу одновременно не ходит? Ну тогда да, бо́льшую.
                                  Теперь представьте себе хотя бы 1000 одновременных запросов.

                          +1
                          А какие есть еще варианты? Процессор заменить на более быстрый?:)
                          Python много людей использует, которые не являются программистами, и писать код на C для них может быть сложно. А тут просто подключил готовую библиотеку и ускорил код на порядок. А 9мс или 90мс будет выполняться код — не так важно, главное что не 90с.
                            +1
                            А какие есть еще варианты? Процессор заменить на более быстрый?:)

                            Хотя бы скриптовый язык с реально работающим качественным JIT (JavaScript, PHP, вроде бы с недавних пор Ruby).
                            К сожалению, пока что имеем весьма эскобаристый выбор — или откровенно слабая типизация и дурнейшее легаси в JS и PHP, или заметно специфический и нишевый Ruby, или плохой JIT, как в CPython, или языки, которые знают три хипстера. Можно посмотреть на TS+NodeJS или PyPy, но это скорее переходные варианты.
                            Ещё лучше, конечно, когда компилятор берёт максимум контроля и решения о выборе заранее — не буду тут называть конкретные средства, почти в любом уже появляется цена освоения инструментальных средств.


                            Python много людей использует, которые не являются программистами, и писать код на C для них может быть сложно. А тут просто подключил готовую библиотеку и ускорил код на порядок. А 9мс или 90мс будет выполняться код — не так важно, главное что не 90с.

                            Это только для задач, которые можно редуцировать до выполнения чего-то в условном numpy. Да, может, таких 90%. Но не все.

                              0
                              А 9мс или 90мс будет выполняться код — не так важно

                              Зависит от того, сколько раз этот код вызывается для получения конечного результата. Если миллионы раз — очень важно.
                              +2
                              > Написать код на C

                              На фортране.
                                –1
                                И это правильно. Ибо сила питона в намного более быстром проектировании в целом, и последующей замене критичных функций на библиотеки написанные на статически типизированных и быстром С.
                                  +2

                                  Будто бы в других языках нельзя подключить библиотеку на C.

                                    +1

                                    Я этого не говорил.

                                +1
                                Я вот дико прошу прощения. Судя по вашему описанию лучше работать с типами NumPy чем с типами самого питона.
                                Но опять же если мы сделаем
                                s = np.int8(10)
                                Разве это не приведет к задержкам в работе? Тогда как максимально улучшить работу с данными что бы не было оверхеда? Есть какие то примеры?

                                  +2
                                  NodeJS — не язык.
                                  Javascript тоже с динамической типизацией.
                                  Определение времени выполнения в шарпе обычно делается Stopwatch.
                                  Имхо — все данные по времени выполнения недостоверны. Статья чисто для рекламы NumPy.
                                    +1
                                    numpy, мне кажется, не нуждается в рекламе. Отличная либа, кто занимается расчетами на python и так ее использует каждый день, там ей альтернатив почти нет.
                                    Но это не отменяет того фактм что статья очень однобокая и плохо написана. Ведь есть же разные уровни оптимизации. Есть приемы оптимизации которые не зависят от использования тех или иных библиотек, и было бы интересно посмотреть именно на такую оптимизацию.
                                    С другой стороны, ничего не сказано про numba, cython, а ведь на хабре уже была не одна статья на эту тему например habr.com/ru/post/484142, habr.com/ru/news/t/531402.
                                    Я не говорю что та же numba — серебрянная пуля, но обойти ее вниманием в статье «как ускрорить python» — это слишком.
                                      +2
                                      Просто новичок может поверить что все дело в динамической типизации.
                                      И в java и в C# и в javascript движке v8 есть just-in-time компиляция.
                                      Вот и сравнивать надо было с тем же самым в питоне. Я не питонист, но как я понимаю — это и есть numba.
                                      Да, конечно, у языков с динамической типизацией оптимизация сложнее. Но сравнивать надо сравнимое.
                                        0
                                        С намбой проблема, что она не все фичи языка поддерживает, а только его подмножество, что в принципе не странно — проект молодой и развивается.
                                        Есть еще cyton, но он все же отличается от питона, так что это вариант более трудный, нужно многое переписать, numba зачастую дает прирост при минимальной модификации (только декораторы расставить).
                                          0
                                          И в java и в C# и в javascript движке v8 есть just-in-time компиляция.
                                          Вот и сравнивать надо было с тем же самым в питоне.-

                                          Numba по сравнению с JIT в V8 или в том, что в PHP, очень слаба. В её развитие до сходного уровня слабо верится, уже по аналогии — почему UnladenSwallow умер? Что мешало гуглу с его ресурсами довести проект до рабочего состояния (кроме конкуренции со стороны Go)?


                                          PyPy посильнее, хотя подход у него очень специфический (фактически, JIT генерируется на основе трассировки не целевого кода, а состояния промежуточного интерпретатора). Прямой заточки на те же типы задач, что у numpy или numba, у него нет и не предполагается, и он с ними несовместим. Выбор "куда идти с голого CPython — Cython, Numba или PyPy" это классическое "налево пойдёшь — коня потеряешь": общей эффективной реализации для хотя бы двух из них просто нет.


                                          Под мои текущие задачи (манипуляция развесистыми текстовыми данными) Cython и Numba непригодны, PyPy даёт ускорение раза в 3 в лучшем случае… при этом тупой неоптимизированный код на Java с ходу бьёт PyPy в 2 раза, а если чуть подумать — то в 10.


                                          Да, конечно, у языков с динамической типизацией оптимизация сложнее. Но сравнивать надо сравнимое.

                                          Сравнивать надо от целевых задач. В задаче типа "распарсить сообщение и выцепить данные" заказчику плевать на вид типизации, ему нужен результат в виде затраченных ресурсов.


                                          Просто новичок может поверить что все дело в динамической типизации.

                                          На 90% таки в ней (особенно в том виде, в котором она в Питоне — что на ходу можно менять всё, включая тип объекта и его методы). У тех, кто имеет дело с этим, просто опускаются руки.
                                          В случае V8 — авторам было просто некуда деваться — альтернативы JavaScript в браузере нет. В случае Python — альтернативы есть, и поэтому быстрее сбегают на них, чем реализуют эффективную работу его кода.

                                      +1
                                      gcc -std=c99 -o test test.c
                                      took 11975 us
                                      done

                                      gcc -std=c99 -O3 -o test test.c
                                      took 3664 us
                                      done

                                      gcc --version
                                      gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39.0.3)
                                        +1

                                        Ваш тест требует уточнения, потому что вообще без оптимизации машинный код будет переполнен левыми копированиями, постоянными чтениями локальных переменных из стека и записями обратно в стек, и т.п. Добавление хотя бы -Og их устраняет:


                                        $ gcc -std=c99 -o h552378 h552378.c
                                        $ ./h552378 
                                        took 2480 us
                                        done
                                        
                                        $ gcc -std=c99 -o h552378 h552378.c -Og
                                        $ ./h552378 
                                        took 1327 us
                                        done
                                        
                                        $ gcc -std=c99 -o h552378 h552378.c -O2
                                        $ ./h552378 
                                        took 1269 us
                                        done
                                        
                                        $ gcc -std=c99 -o h552378 h552378.c -O3
                                        $ ./h552378 
                                        took 733 us
                                        done
                                        $ ./h552378 
                                        took 786 us
                                        done
                                        
                                        $ gcc -v
                                        [...]
                                        gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04) 

                                        тут чётко видно 2 прыжка, второй уже осмысленный (на векторизации), а вот первый — на как раз экономии на откровенно ненужных действиях.

                                        +2
                                        import random
                                        values = [random.randint(1, 100) for _ in range(1_000_000)]
                                        
                                        def get_r(values):
                                             return [1/val for val in values]
                                        
                                        %timeit get_r(values)
                                        

                                        46.9 ms ± 669 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
                                        intel core i7 8gen
                                          +2
                                          К сожалению, далеко не всё возможно ускорить, просто переписав код эффективнее. А иногда проще написать либу на C++, чем догадаться, как перетасовать функции numpy в нужном тебе порядке.
                                          Например, задача поиска пересечений двух массивов, когда там есть равные числа.
                                            0
                                            Почему нет примера на ассемблере? Какой смысл сравнивать компилируемые языки и интерпретируемые?
                                              +2
                                              const length = 1000000;
                                              const rand_array = [];
                                              const output = new Float32Array(length-1);
                                              for (let i = 0; i < length; i++) {
                                                rand_array[i] = Math.floor(Math.random() * 10000000);
                                              }
                                              console.time("calc");
                                              for (let i = 0; i < length; i++) {
                                                output[i]= 1 / rand_array[i];
                                              }
                                              console.timeEnd("calc");
                                              

                                              код для nodeJS можно ускорить в 4 раза, чем тот что в статье, использовав подходящий массив (в моем случае с ~20мс до ~5мс), и это без вставок на Си всяких
                                                +2

                                                Мои варианты кода


                                                Node.js:


                                                let length = 1000000;
                                                let rand_array = new Float32Array(length);
                                                let output = new Float32Array(length);
                                                for (let i = 0; i < length; i++) {
                                                    rand_array[i] = Math.floor(Math.random() * 10000000);
                                                }
                                                
                                                let start = process.hrtime();
                                                while (length--) {
                                                    output[length] = 1.0 / rand_array[length];
                                                }
                                                let end = process.hrtime(start);
                                                console.info('Execution time (hr): %dms', end[1] / 1000000)

                                                PHP


                                                <?php
                                                
                                                $length = 1000000;
                                                $rand_array = new SplFixedArray($length);
                                                $output = new SplFixedArray($length);
                                                for ($i = 0; $i < $length; $i++) {
                                                    $rand_array[$i] = random_int(1, PHP_INT_MAX);
                                                }
                                                $start = microtime(true);
                                                for ($i = 0; $i < $length; $i++) {
                                                    $output[$i] = 1.0 / $rand_array[$i];
                                                }
                                                echo sprintf('Execution time (hr): %sms', round((microtime(true) - $start) * 100, 6));

                                                Вариант на PHP работает быстрее, чем такой же на node.js
                                                  0
                                                  Не правда! У вас в варианте с ПХП опечатка, умножать на 1000 все-таки, не сочтите за code-nazi. В итоге на пыхе 7.4.1 выходит 38мс, против 20мс на 14 ноде

                                                  while (length--) {

                                                  Если что этот хак уже лет 5 как не работает с развитием компиляторов JS :)

                                                    0

                                                    Мой косяк :( Исправляюсь.


                                                    Nodejs


                                                    let length = 1000000;
                                                    let rand_array = new Float32Array(length);
                                                    let output = new Float32Array(0);
                                                    for (let i = 0; i < length; i++) {
                                                        rand_array[i] = Math.floor(Math.random() * 10000000);
                                                    }
                                                    
                                                    let start = process.hrtime();
                                                    while (length--) {
                                                        output[length] = 1.0 / rand_array[length];
                                                    }
                                                    let end = process.hrtime(start);
                                                    console.info('Execution time (hr): %dms', end[1] / 1000000)

                                                    Execution time (hr): 3.584035ms

                                                    PHP


                                                    <?php
                                                    
                                                    $length = 1000000;
                                                    $rand_array = new SplFixedArray($length);
                                                    $output = new SplFixedArray($length);
                                                    for ($i = 0; $i < $length; $i++) {
                                                        $rand_array[$i] = random_int(1, PHP_INT_MAX);
                                                    }
                                                    $start = microtime(true);
                                                    for ($i = 0; $i < $length; $i++) {
                                                        $output[$i] = 1.0 / $rand_array[$i];
                                                    }
                                                    echo sprintf('Execution time (hr): %sms', round((microtime(true) - $start) * 1000, 6));

                                                    Execution time (hr): 25.705099ms
                                                  +4
                                                  Как ускорить код на Python в тысячу раз

                                                  Использовать C-шные либы.
                                                  (вся статья одной строкой)

                                                    +4
                                                    как зарабатывать будучи SMM-щиком гуманитарием:
                                                    1. нужно не уметь программировать
                                                    2. нужно взять любую статью на английском где куча обсуждений (потому, что она сомнительна) с линкедына, medium, towards datascience, или первого попавшегося блога из поисковика
                                                    3. перевести статью гугл транслейтом, поправить слегка машиноперевод
                                                    4. вставить рекламную плашку, «на правах рекламы» и запостить на хабр
                                                    5. откинуться в кресле, захватить попкорн, читать каменты и получать комиссию за успешный копирайтинг и показывать спонсору сколько просмотров и каментов
                                                      0

                                                      Глупый вопрос. Почему в python нельзя сделать помимо list() например list_int() (где все элементы будут одного типа int, и тогда интерпретатор не проверял бы тип на каждой итерации цикла?

                                                        +1
                                                        Потому что это будет отказом от главной исходной парадигмы Python — динамической типизации.
                                                          +1
                                                          Вообще, предоставления опциональной статической типизации — совсем не противоречит исходной парадигме.
                                                          Вот например реализация docs.python.org/3/library/array.html
                                                            0
                                                            Если я переменной x присвою строку, ничто не помешает мне потом присвоить ей же array unsigned char в другом месте, array double в третьем и просо float в четвертом.

                                                            Вы не путаете строгую типизацию и динамическую типизацию?
                                                              0
                                                              Это я понимаю, но в контексте заданного вопроса речь была про элементы массивов.

                                                              Глупый вопрос. Почему в python нельзя сделать помимо list() например list_int() (где все элементы будут одного типа int, и тогда интерпретатор не проверял бы тип на каждой итерации цикла?


                                                              Если после создания переменной с типизированным массивом я попробую присвоить его элементу значения какого-то другого типа — получу ошибку.
                                                              Например:
                                                              from array import array
                                                              a = array('B', [1,2,3])
                                                              a[1] = -1
                                                              

                                                              Вызовет
                                                              OverflowError: unsigned byte integer is less than minimum
                                                              , потому что тип при создании был задан как беззнаковое целое.

                                                              Может я неудачно выразился, назвав это статической типизацией, тут скорее массив с однородным типом, или типизированным массив, но суть тут в другом, что в python такие массивы есть. Впрочем, я всегда использую numpy-массивы, как то даже родными питоновскими особо дела не приходилось иметь, просто знаю что такая возможность встроена в стандартной реализации языка.
                                                                0
                                                                Прикол в том, что эту ошибку генерирует вовсе не интерпретатор Python, а функция на C из ипортированного Вами модуля array во время выполнения кода.
                                                                С точки зрения синтаксиса языка Python тут никаких проблем нет. Ограничения накладывает код на C.
                                                                  +1
                                                                  А что тут неожиданного — сама реализация CPython написана на С, и все ошибки генерирует код на С. Тоже мне открытия Америки.
                                                                  Вроде есть реализации Python на Java, ошибку будет генерировать код на Java.
                                                                  Ну а синтаксис тут вообще ни при чем, как будто все ошибки обязаны быть синтаксическими.
                                                                  С точки зрения синтаксиса языка Python тут никаких проблем нет.

                                                                  Конечно нет, синтаксис Python и возможность реализации типизированного массива в рамках этого языка — вещи взаимоортогональные.
                                                                    0
                                                                    Суть не в том, на каком языке Python написан, а в том, что ошибка возникает на этапе выполнения. То есть, если перед этим Ваша программа два часа тащила данные из разных источников и трансформировала их, то через два часа и обломитесь такой ошибкой.

                                                                    синтаксис Python и возможность реализации типизированного массива в рамках этого языка — вещи взаимоортогональные

                                                                    Именно об этом и речь.
                                                                      +1
                                                                      То есть, если перед этим Ваша программа два часа тащила данные из разных источников и трансформировала их, то через два часа и обломитесь такой ошибкой.

                                                                      Ну, именно потому в data science где Python используют очень активно — на этапе разработки используют интерактивные блокноты jupyter. Даже если возникает ошибка на каком то этапе — результаты предыдущих операций и значения переменных сохраняются. Ну а уже для готового приложения нужно все же проверки все равно писать для валидации входных данных, думаю, это на любом языке хорошая практика. Если верите что статическая типизация защитит вас от всех проблем в жизни — ваше право верить.
                                                                      Я имел опыт работы с такими классическими статически типизированными языками как фортран например, причем писал на них многие годы и могу сравнивать и гибкость, легкость прототипирования, возможность легкого переиспользования кода. Также имею уже определенный опыт на python, и почему то динамическая типизация какие то частые суперпроблемы вызывала — трудно даже припомнить.
                                                                      Впрочем, каждый себе подбирает инструмент исходя из задач, которые он решает и личных склонностей. Так что развивать спор на тему «динамическая типизация безусловное зло или благословение небес» наверное не стоит.
                                                                        +1
                                                                        на этапе разработки используют

                                                                        При чем тут этап разработки? Обсуждаемая проблема возникает из-за входных данных. Поэтому намного важнее возможности решения ее в продуктивной среде.

                                                                        Если верите что статическая типизация защитит вас от всех проблем в жизни

                                                                        Я уверен в обратном. В том что идеал не достижим. Но так же я уверен в том, что статическая типизация существенно снижает вероятность возникновения подобных проблем. Поэтому, если на этапе прототипирования я положительно отношусь к языкам с динамической типизацией, то на этапе создания продуктивной системы — уже отрицательно.

                                                                        как фортран например

                                                                        Прошу прощения, но сравнивать языки, возраст которых различается на 34 года, это примерно то же, что сравнивать оригинальную IBM/PC с современными компьютерами.
                                                                        Корректней сравнивать хотя бы с Java.
                                                                          +1
                                                                          Прошу прощения, но сравнивать языки, возраст которых различается на 34 года, это примерно то же, что сравнивать оригинальную IBM/PC с современными компьютерами.

                                                                          Не буду разводить спор про типизацию, лично у меня в отлаженном коде подобных проблем пока не возникало. По крайней мере, вспомнить не могу.
                                                                          Про фортран не согласен. В стандарте 90/95 это вполне жизнеспособная опция для расчетов, которыми я собственно и занимаюсь. Подозреваю, что вы имеете весьма отдаленное представление об этом языке.
                                                                            0
                                                                            для численных расчетов

                                                                            OpenCL не поддерживает Fortran, хоть и поддерживает вызов функций на нем. CUDA — поддерживает Fortran но хуже, чем С/C++, да еще и не переносима. Получается, для численных расчетов Fortran уже как-то не очень.
                                                            0

                                                            Ничуть нет. А к примеру, в JS есть вещи например подобные Uint8ClampedArray. Местами очень удобно.

                                                            +1
                                                            Вообще говоря, такие структуры данных есть docs.python.org/3/library/array.html
                                                            Но если вы могли заметить, что в исходном коде для демонстрации медлительности питона, итерация производится именно по типизированому массиву numpy, и все же она очень медленная.
                                                            И если скомпилировать этот код с помощю numba, все ускоряется даже больше чем при переходе на numpy
                                                            import numpy as np
                                                            import numba
                                                            np.random.seed(0)
                                                            values = np.random.randint(1, 100, size=1000000)
                                                            @numba.njit
                                                            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)
                                                            

                                                            Ничего не менял, только добавил import numba и декоратор @numba.njit, результат — код исполняется за 1.44 ms.
                                                            0

                                                            А ничего, что в статье сравниваются по сути разные реализации rand, и все остальное уже просто не имеет смысла?)
                                                            Ну и тест, который выполняется за миллисекунды — ну это вообще не тест))

                                                              +3
                                                              Как ускорить ходьбу в 20 раз?

                                                              Человек, как известно, ходит, попеременно перемещая опору на каждую из ног. Особенности строения организма человека таковы, что его средняя скорость ходьбы — в пределах 3..6 км/ч, что заметно ниже скорости множества диких животных. При этом в течение дня человеку зачастую надо преодолеть расстояние в десятки километров.

                                                              Решение: универсальные места ожидания автотранспорта (остановки)
                                                              Подождав немного на остановке, вы можете сесть в автобус или электропоезд, который довезёт вас довольно близко к точке назначения.

                                                              К чему это я:
                                                              Ускоряется не любой код на питоне, ускоряется конкретно обработка больших унифицированных массивов.
                                                              Ускорить, например, сложную стейт-машину таким образом вряд ли получится.
                                                              Название, как минимум, некорректно, сравнение тоже некорректно, пакетная обработка на других языках с использованием такого же синтаксиса будет не менее быстрым.

                                                              И да, интересно: каким образом у такой статьи появились 46 плюсов?
                                                                0
                                                                del

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

                                                                Самое читаемое