为何打印整数列表的最快方式如此违背直觉?
批量打印整数列表的性能分析与疑问
我有一个整数列表ints,希望以最快速度将每个整数逐行打印。
初始实现为:
list(map(print,ints))
在处理10⁷个整数(从标准输入读取)时,30次重复测试的平均耗时为1708ms。
之前看到一篇认可度较高的Stack Overflow回答提到,用list()展开map结果会因创建大量无意义的None列表而低效,这符合直觉。于是我尝试了多种实现方式:
# n次print调用 list(map(print,ints)) # map for n in ints: print(n) # loop for i in range(0,len(ints)): print(ints[i]) # range [print(n) for n in ints] # compr deque(map(print,ints),0) # deque # 单次print调用 print('\n'.join(map(str,ints))) # cat print(*ints, sep='\n') # sep
结合ints = list(map(int,sys.stdin.readlines()))测试,各方法耗时如下:
map 1707 ms loop 1926 ms range 2027 ms compr 1828 ms deque 1699 ms -------------- cat 1084 ms sep 1462 ms
这些结果并非偶然,但实际情况完全违背直觉:创建千万级None列表的map变体,反而比其他n次print调用的变体快得多,Python是否优化了该列表的创建?而最令人惊讶的是,创建超大字符串的cat变体速度最快——本以为内存分配会严重影响性能,即便默认flush=False。
核心问题
为何map变体比其他n次print调用的变体快得多?
为何cat变体的速度还要更快?
示例代码(map.py)
#!/usr/bin/python3 import sys if __name__ == '__main__': ints = list(map(int,sys.stdin.readlines())) list(map(print,ints))
运行方式
/usr/bin/time -f '%e' -o map.run-time -a ./map.py < input.log | wc -l > /dev/null
问题解答
1. 为什么map变体比其他n次print调用更快?
- 底层实现差异:
map是Python内置的C语言实现迭代器,它调用print的过程在C层面完成,相比Python层面的for循环(包括普通loop、列表推导),减少了大量Python字节码的执行开销——普通for循环每次迭代都要处理变量绑定、循环指令等Python级逻辑,额外开销远高于C级迭代。 - 列表创建的高效性:虽然
list(map(...))会生成全为None的大列表,但这个列表的创建是在C层面批量完成的,内存分配和元素填充的效率远高于Python层面逐个添加元素的操作(比如列表推导[print(n) for n in ints])。而deque(map(...), 0)只是消费迭代器不保存结果,耗时和map接近,进一步说明map本身的迭代效率是关键。 - 额外操作的开销:
range版本需要额外做索引查找ints[i],比直接遍历元素的loop多了一层开销,所以速度最慢;列表推导本质还是Python级循环,只是语法更紧凑,开销略低于普通loop,但仍高于map。
2. 为什么cat变体的速度最快?
- IO次数的大幅减少:系统IO是性能瓶颈的核心。n次print调用即使开启缓冲,最终也会触发多次IO刷写(缓冲有大小限制);而cat变体只触发单次系统IO操作,把所有内容一次性写入,IO的耗时占比远高于内存操作,减少IO次数能直接大幅提升性能。
- 字符串拼接的高效性:
'\n'.join(map(str,ints))的字符串拼接是Python优化过的操作——join会预先计算所有字符串的总长度,一次性分配内存完成拼接,避免了多次字符串拷贝的开销。相比多次调用str()和print()的累计成本,单次大内存分配的代价可以忽略。 - sep版本的额外开销:
print(*ints, sep='\n')虽然也是单次print,但它需要在Python层面逐个将整数转换为字符串,并处理分隔符拼接,这个过程的效率远低于join的C级字符串拼接,所以耗时比cat高。
内容的提问来源于stack exchange,提问作者bitmask
相关产品推荐
相关产品推荐

