Почему мой код Python в 100 раз медленнее, чем тот же код в PHP? - PullRequest
0 голосов
/ 12 октября 2018

У меня есть две точки (x1 и x2), и я хочу сгенерировать нормальное распределение с заданным количеством шагов.Сумма значений y для значений x между x1 и x2 равна 1. К актуальной проблеме:

Я довольно плохо знаком с Python и удивляюсь, почему следующий код дает желаемый результат, но примерно в 100 раз медленнее, чемта же программа на PHP.Существует около 2000 пар x1-x2 и около 5 значений шага на пару.

Я пытался скомпилировать с Cython, использовал многопроцессорность, но он просто улучшил вещи в 2 раза, которые все еще в 50 раз медленнее, чем PHP.Любые предложения, как улучшить скорость, чтобы соответствовать по крайней мере производительности PHP?

from scipy.stats import norm
import numpy as np
import time

# Calculates normal distribution
def calculate_dist(x1, x2, steps, slope):
    points = []
    range = np.linspace(x1, x2, steps+2)

    for x in range:
        y = norm.pdf(x, x1+((x2-x1)/2), slope)
        points.append([x, y])

    sum = np.array(points).sum(axis=0)[1]

    norm_points = []
    for point in points:
        norm_points.append([point[0], point[1]/sum])

    return norm_points

start = time.time()
for i in range(0, 2000):
    for j in range(10, 15):
        calculate_dist(0, 1, j, 0.15)

print(time.time() - start) # Around 15 seconds or so

Редактировать, код PHP:

$start = microtime(true);

for ($i = 0; $i<2000; $i++) {
    for ($j = 10; $j<15; $j++) {
        $x1 = 0; $x2 = 1; $steps = $j; $slope = 0.15;
        $step = abs($x2-$x1) / ($steps + 1);

        $points = [];
        for ($x = $x1; $x <= $x2 + 0.000001; $x += $step) {
            $y = stats_dens_normal($x, $x1 + (($x2 - $x1) / 2), $slope);
            $points[] = [$x, $y];
        }

        $sum = 0;
        foreach ($points as $point) {
            $sum += $point[1];
        }

        $norm_points = [];
        foreach ($points as &$point) {
            array_push($norm_points, [$point[0], $point[1] / $sum]);
        }
    }
}

return microtime(true) - $start; # Around 0.1 seconds or so

Редактировать 2, профилировать каждую строку и обнаружил, что norm.pdf ()Это занимало 98% времени, поэтому нашел пользовательскую функцию normpdf и определил ее, теперь время составляет около 0,67 с, что значительно быстрее, но все же примерно в 10 раз медленнее, чем PHP.Также я думаю, что переопределение общих функций противоречит идее простоты Питона?!

Пользовательская функция (источником является другой ответ Stackoverflow):

from math import sqrt, pi, exp
def normpdf(x, mu, sigma):
    u = (x-mu)/abs(sigma)
    y = (1/(sqrt(2*pi)*abs(sigma)))*exp(-u*u/2)
    return y

1 Ответ

0 голосов
/ 12 октября 2018

Ответ таков: вы не используете правильные инструменты / структуры данных для задач в python.

Вызов функциональности numpy сопряжен с большими затратами (scipy.stats.norm.pdf использует numpy под капотом) в python итаким образом, никогда не вызывать эти функции для одного элемента, но для всего массива (так называемые векторизованные вычисления), это означает, что вместо

for x in range:
        y = norm.pdf(x, x1+((x2-x1)/2), slope)
        ys.append(y)

лучше использовать:

ys = norm.pdf(x,x1+((x2-x1)/2), slope)

вычисленияpdf для всех элементов в x и оплата накладных расходов только один раз, а не len(x) раз.

Например, для вычисления pdf для 10 ^ 4 элементов требуется менее чем в 10 раз больше времени, чем для одного элемента:

%timeit norm.pdf(0)   # 68.4 µs ± 1.62 µs
%timeit norm.pdf(np.zeros(10**4))   # 415 µs ± 12.4 µs

Использование векторизованных вычислений не только сделает вашу программу более быстрой, но зачастую также более короткой / легкой для понимания, например:

def calculate_dist_vec(x1, x2, steps, slope):
    x = np.linspace(x1, x2, steps+2)
    y = norm.pdf(x, x1+((x2-x1)/2), slope)
    ys = y/np.sum(y)
    return x,ys

Использование этой векторизованной версии ускоряет работу примерно на 10.

Проблема: norm.pdf оптимизирована для длинных векторов (на самом деле никого не волнует, насколько быстро / медленно оно для 10 элементов, если оно очень быстро для миллионаэлементы), но ваш тест смещен против numpy, потому что он использует / создает только короткие массивы и, следовательно, norm.pdf не может светиться.

Так что, если речь идет о небольших массивах, и вы серьезно относитесь к его ускорению, выпридется развернуть собственную версию norm.pdf Использование Cython для создания этой быстрой и специализированной функции может стоить попробовать.

...