Вы можете генерировать результаты, подобные учебникам, если посчитать, что они считают, предпринятые шаги:
def pow(n):
global calls
calls+=1
"""Return 2**n, where n is a nonnegative integer."""
if n == 0:
return 1
x = pow(n//2)
if n%2 == 0:
return x*x
return 2*x*x
def steppow(n):
global calls
calls=0
pow(n)
return calls
sx = [math.pow(10,n) for n in range(1,11)]
sy = [steppow(n)/math.log(n) for n in sx]
print(sy)
Тогда получится что-то вроде этого:
[2.1714724095162588, 1.737177927613007, 1.5924131003119235, 1.6286043071371943, 1.5634601348517065, 1.5200306866613815, 1.5510517210830421, 1.5200306866613813, 1.4959032154445342, 1.5200306866613813]
Там, где 1,52 ... кажется каким-то фаворитом.
Но фактическое время выполнения также включает в себя, казалось бы, невинные математические операции, которые также усложняются по мере того, как число физически увеличивается в памяти.CPython использует множество реализаций умножения, разветвляющихся в различных точках:
long_mul
- это запись:
if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
return PyLong_FromLongLong((long long)v);
}
z = k_mul(a, b);
, если числа соответствуютв слове процессора они умножаются на месте (но результат может быть больше, следовательно, LongLong
(*)), в противном случае они будут использовать k_mul()
, что означает умножение Карацубы, которое также проверяетпара вещей, основанных на размере и значении:
i = a == b ? KARATSUBA_SQUARE_CUTOFF : KARATSUBA_CUTOFF;
if (asize <= i) {
if (asize == 0)
return (PyLongObject *)PyLong_FromLong(0);
else
return x_mul(a, b);
}
для более коротких чисел, используется классический алгоритм, x_mul()
, и проверка на короткое замыкание также зависитна продукте, являющемся квадратом, beause x_mul()
имеет оптимизированный путь кода для вычисления x*x
-подобных выражений.Тем не менее, выше определенного размера в памяти, алгоритм остается локально, но затем он делает еще одну проверку того, насколько различны величины двух значений:
if (2 * asize <= bsize)
return k_lopsided_mul(a, b);
, возможно, переход на еще один алгоритм,k_lopsided_mul()
, который по-прежнему является Карацубой, но оптимизирован для умножения чисел со значительной разницей в величине.
Короче говоря, даже значение 2*x*x
имеет значение, если заменить его на x*x*2
, timeit
результаты будут отличаться:
2*x*x: [0.00020009249478223623, 0.0002965123323532072, 0.00034258906889154733, 0.0024181753953639975, 0.03395215528201522, 0.4794894526936972, 4.802882867816082]
x*x*2: [0.00014974939375012042, 0.00020265231347948998, 0.00034002925019471775, 0.0024501731290706985, 0.03400164511014836, 0.462764023966729, 4.841786565730171]
(измеряется как
sx = [math.pow(10,n) for n in range(1,8)]
sy = [timeit.timeit('pow(%d)' % i, number=100, globals=globals()) for i in sx]
)
(*), кстати,поскольку размер результата часто переоценивают (как в самом начале, long*long
может соответствовать или не соответствовать long
впоследствии), есть также функция long_normalize
, которая наend действительно тратит время на освобождение дополнительной памяти (см. комментарий выше), но все же устанавливает правильный размер для внутреннего объекта, что включает цикл, подсчитывающий нули перед фактическим числом.