Какой самый эффективный способ сделать квадратный корень из суммы квадратов двух чисел? - PullRequest
0 голосов
/ 28 июня 2018

Я ищу более эффективный и кратчайший способ выполнения квадратного корня из суммы квадратов из двух или более чисел. Я на самом деле использую numpy и этот код:

np.sqrt(i**2+j**2)

Это кажется в пять раз быстрее, чем:

np.sqrt(sum(np.square([i,j])))

(i и j - числа!)

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

Ответы [ 4 ]

0 голосов
/ 29 июня 2018

Я провел несколько сравнений, основанных на ответах, и кажется, что более быстрый способ - использовать модуль math, а затем math.hypot(i + j), но, вероятно, лучшим компромиссом является использование (i*i + j*j)**0.5 без импорта какого-либо модуля, хотя это и не так явно.

enter image description here

код

from timeit import timeit
import matplotlib.pyplot as plt


tests = [
"np.sqrt(i**2+j**2)",
"np.sqrt(sum(np.square([i,j])))",
"(i*i + j*j)**0.5",
"math.sqrt(i*i + j*j)",
"math.hypot(i,j)",
"np.linalg.norm([i,j])",
"ne.evaluate('sqrt(i**2+j**2)')",
"np.hypot(i,j)"]

results = []
lengths = []
for test in tests:
    results.append(timeit(test,setup='i = 7; j = 4;\
                          import numpy  as np; \
                          import math; \
                          import numexpr as ne', number=1000000))
    lengths.append(len(test))

indx = range(len(results))
plt.bar(indx,results)
plt.xticks(indx,tests,rotation=90)
plt.yscale('log')
plt.ylabel('Time (us)')
0 голосов
/ 28 июня 2018

Вместо того, чтобы оптимизировать этот довольно простой вызов функции, вы можете попробовать переписать вашу программу так, чтобы i и j были массивами вместо отдельных чисел (при условии, что вам нужно вызывать функцию на множестве разных входов) , Посмотрите на этот небольшой тест:

import numpy as np
i = np.arange(10000)
j = np.arange(10000)

%%timeit 
np.sqrt(i**2+j**2)
# 74.1 µs ± 2.74 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit
for idx in range(len(i)):
    np.sqrt(i[idx]**2+j[idx]**2)
# 25.2 ms ± 1.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Как видите, первый вариант (с использованием массивов чисел в качестве входных данных) примерно в 300 раз быстрее, чем второй, использующий цикл для Python. Причина этого заключается в том, что в первом примере все вычисления выполняются с помощью numpy (который реализован внутри c и, следовательно, действительно быстро), тогда как во втором примере чередуются код numpy и обычный код Python (цикл for), что делает выполнение намного медленнее.

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

0 голосов
/ 28 июня 2018

В этом случае numexpr модуль может быть быстрее. Этот модуль избегает промежуточной буферизации и, следовательно, быстрее для определенных операций:

i = np.random.rand(100000)
j = np.random.rand(100000)
%timeit np.sqrt(i**2 + j**2)
# 1.34 ms

import numexpr as ne
%timeit ne.evaluate('sqrt(i**2+j**2)')
#370 us
0 голосов
/ 28 июня 2018

Для случая i != j это невозможно сделать с помощью np.linalg.norm, поэтому я рекомендую следующее:

(i*i + j*j)**0.5

Если i и j - одиночные поплавки, это примерно в 5 раз быстрее, чем np.sqrt(i**2+j**2). Если i и j являются массивами-пустышками, это примерно на 20% быстрее (из-за замены квадрата на i*i и j*j. Если вы не замените квадраты, производительность будет равна np.sqrt(i**2+j**2).
Некоторые тайминги с использованием одинарных поплавков

i = 23.7
j = 7.5e7
%timeit np.sqrt(i**2 + j**2)
# 1.63 µs ± 15.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit (i*i + j*j)**0.5
# 336 ns ± 7.38 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit math.sqrt(i*i + j*j)
# 321 ns ± 8.21 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

math.sqrt немного быстрее, чем (i*i + j*j)**0.5, , но это происходит за счет потери гибкости: (i*i + j*j)**0.5 будет работать с одиночными числами с плавающей запятой и массивами, тогда как math.sqrt будет работать только со скалярами .

И некоторые тайминги для массивов среднего размера:

i = np.random.rand(100000)
j = np.random.rand(100000)
%timeit np.sqrt(i**2 + j**2)
# 1.45 ms ± 314 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit (i*i + j*j)**0.5
# 1.21 ms ± 78.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...