首页 > 解决方案 > 为什么我的 Python 代码比 PHP 中的相同代码慢 100 倍?

问题描述

我有两个点(x1 和 x2),并希望在给定的步数中生成正态分布。x1 和 x2 之间的 x 值的 y 值之和为 1。对于实际问题:

我对 Python 还很陌生,想知道为什么下面的代码会产生预期的结果,但比 PHP 中的相同程序慢大约 100 倍。大约有 2000 个 x1-x2 对,每对大约有 5 个步长值。

我尝试使用 Cython 进行编译,使用了多处理,但它只是改进了 2 倍,这仍然比 PHP 慢 50 倍。任何建议如何提高速度以至少匹配 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 秒,这要快得多,但仍比 PHP 慢 10 倍左右。另外我认为重新定义通用函数违背了 Python 简单的想法?!

自定义函数(来源是其他一些 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

标签: phppythonperformancecythonpython-multiprocessing

解决方案


答案是,您没有为 python 中的任务使用正确的工具/数据结构。

在 python 中调用 numpy 功能有相当大的开销(scipy.stats.norm.pdf在引擎盖下使用 numpy),因此人们永远不会为一个元素调用此函数,而是为整个数组(所谓的向量化计算)调用此函数,这意味着而不是

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)

计算 x 中所有元素的 pdf 并只支付一次而不是len(x)多次的开销。

例如,计算 10^4 个元素的 pdf 所花费的时间比一个元素多不到 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针对长向量进行了优化(如果对 100 万个元素非常快,那么没有人真正关心 10 个元素的快/慢),但是您的测试偏向于 numpy,因为它只使用/创建短数组,因此norm.pdf不能发光。

因此,如果它真的是关于小型阵列并且您认真考虑加速它,您将不得不推出您自​​己的norm.pdf 使用 cython 版本来创建这个快速和专业的功能可能值得一试。


推荐阅读