Я хочу генерировать случайные числа с определенной точностью, по одному (поэтому я не ищу векторизованное решение).
Я нашел метод в этом QnA на stackoverflow, и он дал мне эти тесты, как и было обещано. Метод определенно почти в два раза быстрее. Теперь вот что меня озадачивает.
%timeit int(0.5192853551955484*(10**5)+0.5)/(10.**5) #> 149 ns ± 5.76 ns per loop
%timeit round(0.5192853551955484, 5) #> 432 ns ± 11.7 ns per loop
## Faster as expected
fl = random.random()
pr = 5
%timeit int(fl*(10**pr)+0.5)/(10.**pr) #> 613 ns ± 27.9 ns per loop
%timeit round(fl, pr) #> 444 ns ± 9.25 ns per loop
## Slower?!
%timeit int(random.random()*(10**5)+0.5)/(10.**5) #> 280 ns ± 29.3 ns per loop
%timeit round(random.random(), 5) #> 538 ns ± 17.5 ns per loop
## Faster than using a variable even though it has the overhead
## of creating a random number for each call?
Почему описанный выше метод работает медленнее, когда я использую переменные? Он восстанавливает потерянную скорость, когда я напрямую передаю случайно сгенерированное число. Что мне не хватает?
В моем коде, поскольку мне требуется округление в нескольких местах, я бы хотел, чтобы он был заключен в функцию следующим образом. Я понимаю, что вызов функции связан с небольшими затратами, но я не думаю, что это стоит большей части увеличенного времени. Этот ответ говорит, что он все равно должен быть быстрее, чем стандартная round()
функция Python.
def rounder(fl, pr):
p = float(10**pr)
return int(fl * p + 0.5)/p
%timeit rounder(random.random(), 5) #> 707 ns ± 14.2 ns per loop
%timeit round(random.random(), 5) #> 525 ns ± 22.1 ns per loop
## Having a global variable does make it faster by not having to do the 10**5 everytime
p = float(10**5)
def my_round_5(fl):
return int(fl* p + 0.5)/p
%timeit my_round_5(random.random()) #> 369 ns ± 18.9 ns per loop
Я бы предпочел не иметь в своем коде глобальной переменной, но, допустим, я уступаю этому требованию, но выигрыш в производительности меньше, чем при использовании формулы без переменных.
Итак, последний вопрос будет заключаться в том, какой метод будет наиболее полезным для меня? При переключении на функцию, требующую глобальной переменной, выигрыш составляет всего 100-150 нс. Или есть что-то, что может быть быстрее.
@DaniMesejo Привет, Дэни, извини, я упустил одну тонкость. Я сделал то же самое сейчас, но проблема все еще сохраняется.
можно ли использовать генератор? то есть что-то, что yield
является единственным значением каждый раз, это было бы хорошим местом для предварительного расчета p
@SamMason На самом деле это звучит как хороший способ решить проблему. Я попробую. Можете ли вы добавить код для него и сравнить его с вышеуказанным методом и добавить его в свое решение. Тогда ваш ответ будет полным ответом на мой вопрос, так что я смогу отметить его и, надеюсь, двигаться дальше. :D
Байт-компилятор Python «знает», как работают числа, и использует это знание для оптимизации вещей там, где это возможно. Вы можете использовать модуль dis
, чтобы увидеть, что происходит.
Например, ваш первый «быстрый» пример:
from dis import dis
def fn():
return int(0.5192853551955484*(10**5)+0.5)/(10.**5)
dis(fn)
на самом деле делает:
2 0 LOAD_GLOBAL 0 (int)
2 LOAD_CONST 1 (51929.03551955484)
4 CALL_FUNCTION 1
6 LOAD_CONST 2 (100000.0)
8 BINARY_TRUE_DIVIDE
10 RETURN_VALUE
то есть он знает, что оценивает 0.5192853551955484*(10**5)+0.5
, и делает это при компиляции байт-кода. Если у вас есть pr
в качестве параметра, он не может этого сделать, поэтому при запуске кода приходится выполнять больше работы.
Чтобы ответить на вопрос «что было бы лучше», может быть что-то вроде:
def fn(pr):
# cache to prevent global lookup
rng = random.random
# evaluate precision once
p = 10. ** pr
# generate infinite stream of random numbers
while True:
yield int(rng() * p + 0.5) / p
можно сравнить с:
x = fn(5)
%timeit next(x)
давая ~ 160 нс за цикл, в то время как:
%timeit int(fl*(10**pr)+0.5)/(10**pr)
%timeit int(fl*(10.**pr)+0.5)/(10.**pr)
работать в ~ 500 и ~ 250 нс. Не понимал, что возведение в степень int и float настолько отличается!
Я понимаю. Так это кеширование значения после первого вызова?
до первого звонка! интерпретатор Python оценивает только «байт-код», оптимизация выполняется в процессе преобразования исходного кода в байт-код.
Хорошо. Я не знал ни об этом, ни о модуле dis
. Это будет удобно в дальнейшем. Спасибо!
Ух ты. Возведение в степень с плавающей запятой, похоже, помогает сократить время, необходимое для оставшихся вычислений. Учиться новым маленьким вещам каждый день. :D
Еще один вопрос по rng = random.random
. # cache to prevent global lookup
, значит ли это, что этот генератор случайных чисел является локальным и имеет начальное число, отличное от других вызовов random.random()
, которые могут быть в моем коде? Если я заменю rng()
на random.random()
, какие последствия это будет иметь?
нет, просто кеширование поиска функции. просто доступ к локальным переменным (немного) быстрее, чем к глобальным переменным. когда вы используете random.random()
, вы делаете три вещи: 1. ищете модуль random
, 2. ищете функцию random
, 3. вызываете функцию (см. вывод dis
). мы просто кэшируем первые два шага в (более быструю) локальную переменную. это быстрее, потому что локальные переменные - это просто целочисленный индекс, а не поиск по хэшу, используемый для глобальных переменных
Ну, функция
rounder
, которую вы указали, не совсем такая же, как описанная в ссылке, которую вы предоставили...