我很好奇这样一个事实,当将函数应用于for循环中的pd.Series的每个元素时,执行时间看起来比O(N)快得多。
考虑下面的一个函数,该函数将数字按位旋转,但是代码本身在这里并不重要。
def rotate(x: np.uint32) -> np.uint32:
return np.uint32(x >> 1) | np.uint32((x & 1) << 31)
在for循环中执行此代码1000次时,只需按照预期执行1000次。
x = np.random.randint(2 ** 32 - 1, dtype=np.uint32)
%timeit rotate(x)
# 13 µs ± 807 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
for i in range(1000):
rotate(x)
# 9.61 ms ± 255 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
但是,当我在大小为1000的Series上的for循环中应用此代码时,它的运行速度明显加快。
s = pd.Series(np.random.randint(2 ** 32 - 1, size=1000, dtype=np.uint32))
%%timeit
for x in s:
rotate(x)
# 2.08 ms ± 113 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
我对使这种情况发生的机制感到好奇吗?
答案 0 :(得分:1)
请注意,在第一个循环中,您实际上并没有使用迭代器的下一个值。以下是更好的比较:
...: %%timeit
...: for i in range(1000):
...: rotate(i)
...:
1.46 ms ± 71.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
...: %%timeit
...: for x in s:
...: rotate(x)
...:
1.6 ms ± 66.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
毫不奇怪,它们的表现大致相同。
在您的原始示例中,通过使用在外部声明的变量x
,解释器需要使用LOAD_GLOBAL 2 (x)
加载该变量,而如果您仅使用值i
,则解释器可以只需致电LOAD_FAST 0 (i)
,顾名思义,这会更快。