小能豆

如果我重复使用范围结果,是否应该缓存它们?

py

我对 Python 还比较陌生,我正在尝试优化一些 HackerRank 问题的代码。我发现使用range(即生成列表?)比仅使用while带有单个变量的循环进行迭代更快,这很奇怪。

我想知道,如果我稍后在代码中迭代相同的序列,缓存函数的结果是否会更快range。例如:


这更快吗:

ten = 10
zeroToTen = range(ten)

sum = 0
for x in zeroToTen:
    sum += x

product = 1
for y in zeroToTen:
    product *= y

或者我应该range每次都回忆一下:

ten = 10

sum = 0
for x in range(10):
    sum += x

product = 1
for y in range(10):
    product *= y

阅读 22

收藏
2024-11-17

共1个答案

小能豆

这是一个有趣的 Python 性能问题,特别是当你试图在竞赛编程或优化脚本中寻找最佳实践时!我们来分析这两种方法的性能和适用场景。


理论上的区别

  1. 第一次调用 range
  2. range 是一个高效的、惰性生成器。它不会实际分配一个完整的列表,而是生成一个迭代器来逐步生成所需的值。
  3. range(n) 在内存上非常高效,不会存储整个序列。

  4. 缓存 range

  5. 当你将 range 赋值给变量(如 zeroToTen),它不会重新生成。变量仅是对 range 对象的引用,后续循环会复用这个对象。

  6. 重新调用 range

  7. 直接调用 range(如 range(10)) 每次都会创建一个新的迭代器,但性能开销是非常小的,因为 Python 内部已经对 range 的实现进行了优化。

性能比较

示例代码

让我们对两种情况进行简单的性能测试,使用 Python 的 timeit 模块。

import timeit

def cached_range():
    ten = 10
    zeroToTen = range(ten)

    sum_ = 0
    for x in zeroToTen:
        sum_ += x

    product = 1
    for y in zeroToTen:
        product *= y

def uncached_range():
    ten = 10

    sum_ = 0
    for x in range(ten):
        sum_ += x

    product = 1
    for y in range(ten):
        product *= y

print("Cached range:", timeit.timeit(cached_range, number=100000))
print("Uncached range:", timeit.timeit(uncached_range, number=100000))

结果

  • Cached range 的性能通常略快,因为 zeroToTen 是一个已经构造的对象,只需多次迭代。
  • Uncached range 会有极小的性能开销,因为每次 range 都创建一个新的迭代器,但这个开销通常微乎其微。

何时缓存 range

适合缓存 range 的情况

  1. 重复迭代相同的序列:
    如果你需要多次遍历相同的 range 对象,那么缓存是一个好习惯。例如:
    py r = range(1000) for i in r: pass for j in r: pass # 重用已经生成的 range 对象

  2. 大范围的 range
    如果范围非常大(如 range(10**6)),缓存可以避免多次创建迭代器的开销。

  3. 在性能敏感的代码中:
    如果代码执行频率很高(如内层循环),缓存 range 可以略微减少开销。


不需要缓存 range 的情况

  1. 单次使用:
    如果只需要使用一次 range,直接调用即可,缓存不会带来任何实际性能提升。

  2. Python 3 的优化:
    在现代 Python(3.x)中,range 本质上是惰性生成的对象,性能非常高效,因此多次调用 range 的开销很小。


哪个更快?

在你的例子中,差异会非常小,大多取决于场景。如果你的问题偏向性能敏感并多次使用 range,使用缓存版本会稍微快一些。但如果你只使用一次,不必缓存它。


推荐实践

对于 Pythonic 的写法和清晰性,建议如下:
- 单次使用时直接调用 range
py for x in range(10): ...
- 多次使用时缓存 range
py zero_to_ten = range(10) for x in zero_to_ten: ... for y in zero_to_ten: ...

2024-11-17