You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

列表扩展:slice与islice的性能差异及困惑

为什么slice比islice在列表扩展场景中性能更优?

测试发现,理论上更具优势的islice,在列表扩展场景中实际性能反而不如slice。以下是性能对比的Python代码:

from time import perf_counter
from itertools import islice
from random import choices
from string import ascii_letters
import sys

test_str = "".join(choices(ascii_letters, k=10 ** 7))

arr1 = []
t0 = perf_counter()
for _ in range(10):
    arr1.extend(test_str[slice(1, sys.maxsize)])
t1 = perf_counter()
print('%5.1f ms ' % ((t1 - t0) * 1e3))

arr2 = []
t0 = perf_counter()
for _ in range(10):
    arr2.extend(islice(test_str, 1, sys.maxsize))
t1 = perf_counter()
print('%5.1f ms ' % ((t1 - t0) * 1e3))

运行结果:

552.6 ms
786.0 ms

我对两者的执行逻辑理解如下:

严格切片(slice)

  • 分配临时字符串tmp,即原字符串从索引1到末尾的不可变字符串
  • 获取tmp的迭代器
  • 为每个字符创建字符串对象
  • 将这些对象添加到列表中
  • 回收tmp

延迟切片(islice)

  • 获取test_str的迭代器,丢弃第一个元素
  • 为每个字符创建字符串对象
  • 将这些对象添加到列表中

两者均由C语言实现,但我无法理解为何一次性分配临时字符串后回收的slice反而性能更优,特此寻求解答。


解答

核心原因在于内存访问效率与批量操作的底层优化:

  • 字符串切片的零拷贝特性:Python字符串是不可变对象,test_str[slice(1, sys.maxsize)]并不会复制原字符串的字符数据,只是创建一个新的字符串对象,共享原字符串的内存缓冲区,仅修改了起始偏移和长度参数。所谓的"临时字符串"只是一个轻量的包装,几乎没有额外内存开销。

  • 迭代器的开销差异:

    • 基于切片的字符串迭代是针对连续内存块的专用实现,C层可以批量处理字符,CPU缓存命中率极高,迭代效率远高于通用迭代器。
    • islice作为通用迭代器适配器,每次迭代都要通过通用迭代协议执行next()调用,还要持续判断是否到达终止条件(sys.maxsize),这带来了额外的调用成本和分支判断开销,即使是C实现,这种通用逻辑也比字符串专用迭代慢。
  • extend的预分配优化:列表的extend方法在处理序列类型(如字符串)时,能提前获取元素总数,一次性预分配足够的内存,减少内存扩容的次数。而islice是迭代器,extend无法提前知晓元素数量,只能逐步扩容,进一步增加了性能损耗。

内容的提问来源于stack exchange,提问作者Mathias Sven

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:22:34