每位程序员都应了解的 Python 数字

是否存在某些因素会导致其速度低于你的预期?若你需要实现性能敏感的算法,该选用哪种数据结构?

有些数字是每位 Python 程序员都应了解的。例如,在 Python 中向列表添加元素的速度有多快或多慢?打开文件呢?是否少于一毫秒?是否存在某些因素会导致其速度低于你的预期?若你需要实现性能敏感的算法,该选用哪种数据结构?浮点数占用多少内存?单个字符或空字符串呢?FastAPI相较于Django的速度优势如何?

本文特意为Python开发者整理了性能数据,下方表格按类别归纳了详尽数值,并在表格下方通过图表呈现关键分析。

鸣谢:本文灵感源自每位程序员都应了解的延迟数据等资源。

元素周期表

基准测试源代码§

本文未附代码。欢迎深入研究基准测试代码,代码已发布于GitHub:

https://github.com/mikeckennedy/python-numbers-everyone-should-know

📊 系统信息§

基准测试在表中描述的系统上运行。您的系统可能更快或更慢,但最重要的是进行相对比较。

Property Value
Python Version CPython 3.14.2
Hardware Mac Mini M4 Pro
Platform macOS Tahoe (26.2)
Processor ARM
CPU Cores 14 physical / 14 logical
RAM 24 GB
Timestamp 2025-12-30

快速概览:Python 数字§

此初版为常见 Python 操作的增长时间/大小“金字塔”速览。下方包含更详尽的细节。

Python操作延迟数据(金字塔图)

Attribute read (obj.x)                              14   ns
Dict key lookup                                     22   ns              1.5x attr
Function call (empty)                               22   ns
List append                                         29   ns              2x attr
f-string formatting                                 65   ns              3x function
Exception raised + caught                          140   ns             10x attr
orjson.dumps() complex object                      310   ns        0.3 μs
json.loads() simple object                         714   ns        0.7 μs   2x orjson
sum() 1,000 integers                             1,900   ns        1.9 μs   3x json
SQLite SELECT by primary key                     3,600   ns        3.6 μs   5x json
Iterate 1,000-item list                          7,900   ns        7.9 μs   2x SQLite read
Open and close file                              9,100   ns        9.1 μs   2x SQLite read
asyncio run_until_complete (empty)              28,000   ns         28 μs   3x file open
Write 1KB file                                  35,000   ns         35 μs   4x file open
MongoDB find_one() by _id                      121,000   ns        121 μs   3x write 1KB
SQLite INSERT (with commit)                    192,000   ns        192 μs   5x write 1KB
Write 1MB file                                 207,000   ns        207 μs   6x write 1KB
import json                                  2,900,000   ns      2,900 μs   3 ms   15x write 1MB
import asyncio                              17,700,000   ns     17,700 μs  18 ms    6x import json
import fastapi                             104,000,000   ns    104,000 μs 104 ms    6x import asyncio

Python内存数据(金字塔图)

Float                                               24   bytes
Small int (cached -5 to 256)                        28   bytes
Empty string                                        41   bytes
Empty list                                          56   bytes          2x int
Empty dict                                          64   bytes          2x int
Empty set                                          216   bytes          8x int
__slots__ class (5 attrs)                          212   bytes          8x int
Regular class (5 attrs)                            694   bytes         25x int
List of 1,000 ints                              36,856   bytes         36 KB
Dict of 1,000 items                             92,924   bytes         91 KB         
List of 1,000 __slots__ instances              220,856   bytes        216 KB
List of 1,000 regular instances                309,066   bytes        302 KB         1.4x slots list
Empty Python process                        16,000,000   bytes         16 MB

程序员必知的 Python 数据统计(详解版)§

此处提供更深入的对比表格,涵盖更多细节。

Category Operation Time Memory
💾 Memory Empty Python process 15.77 MB
Empty string 41 bytes
100-char string 141 bytes
Small int (-5 to 256) 28 bytes
Large int 28 bytes
Float 24 bytes
Empty list 56 bytes
List with 1,000 ints 36.0 KB
List with 1,000 floats 32.1 KB
Empty dict 64 bytes
Dict with 1,000 items 90.7 KB
Empty set 216 bytes
Set with 1,000 items 59.6 KB
Regular class instance (5 attrs) 694 bytes
__slots__ class instance (5 attrs) 212 bytes
List of 1,000 regular class instances 301.8 KB
List of 1,000 __slots__ class instances 215.7 KB
dataclass instance 694 bytes
namedtuple instance 228 bytes
⚙️ Basic Ops Add two integers 19.0 ns (52.7M ops/sec)
Add two floats 18.4 ns (54.4M ops/sec)
String concatenation (small) 39.1 ns (25.6M ops/sec)
f-string formatting 64.9 ns (15.4M ops/sec)
.format() 103 ns (9.7M ops/sec)
% formatting 89.8 ns (11.1M ops/sec)
List append 28.7 ns (34.8M ops/sec)
List comprehension (1,000 items) 9.45 μs (105.8k ops/sec)
Equivalent for-loop (1,000 items) 11.9 μs (83.9k ops/sec)
📦 Collections Dict lookup by key 21.9 ns (45.7M ops/sec)
Set membership check 19.0 ns (52.7M ops/sec)
List index access 17.6 ns (56.8M ops/sec)
List membership check (1,000 items) 3.85 μs (259.6k ops/sec)
len() on list 18.8 ns (53.3M ops/sec)
Iterate 1,000-item list 7.87 μs (127.0k ops/sec)
Iterate 1,000-item dict 8.74 μs (114.5k ops/sec)
sum() of 1,000 ints 1.87 μs (534.8k ops/sec)
🏷️ Attributes Read from regular class 14.1 ns (70.9M ops/sec)
Write to regular class 15.7 ns (63.6M ops/sec)
Read from __slots__ class 14.1 ns (70.7M ops/sec)
Write to __slots__ class 16.4 ns (60.8M ops/sec)
Read from @property 19.0 ns (52.8M ops/sec)
getattr() 13.8 ns (72.7M ops/sec)
hasattr() 23.8 ns (41.9M ops/sec)
📄 JSON json.dumps() (simple) 708 ns (1.4M ops/sec)
json.loads() (simple) 714 ns (1.4M ops/sec)
json.dumps() (complex) 2.65 μs (376.8k ops/sec)
json.loads() (complex) 2.22 μs (449.9k ops/sec)
orjson.dumps() (complex) 310 ns (3.2M ops/sec)
orjson.loads() (complex) 839 ns (1.2M ops/sec)
ujson.dumps() (complex) 1.64 μs (611.2k ops/sec)
msgspec encode (complex) 445 ns (2.2M ops/sec)
Pydantic model_dump_json() 1.54 μs (647.8k ops/sec)
Pydantic model_validate_json() 2.99 μs (334.7k ops/sec)
🌐 Web Frameworks Flask (return JSON) 16.5 μs (60.7k req/sec)
Django (return JSON) 18.1 μs (55.4k req/sec)
FastAPI (return JSON) 8.63 μs (115.9k req/sec)
Starlette (return JSON) 8.01 μs (124.8k req/sec)
Litestar (return JSON) 8.19 μs (122.1k req/sec)
📁 File I/O Open and close file 9.05 μs (110.5k ops/sec)
Read 1KB file 10.0 μs (99.5k ops/sec)
Write 1KB file 35.1 μs (28.5k ops/sec)
Write 1MB file 207 μs (4.8k ops/sec)
pickle.dumps() 1.30 μs (769.6k ops/sec)
pickle.loads() 1.44 μs (695.2k ops/sec)
🗄️ Database SQLite insert (JSON blob) 192 μs (5.2k ops/sec)
SQLite select by PK 3.57 μs (280.3k ops/sec)
SQLite update one field 5.22 μs (191.7k ops/sec)
diskcache set 23.9 μs (41.8k ops/sec)
diskcache get 4.25 μs (235.5k ops/sec)
MongoDB insert_one 119 μs (8.4k ops/sec)
MongoDB find_one by _id 121 μs (8.2k ops/sec)
MongoDB find_one by nested field 124 μs (8.1k ops/sec)
📞 Functions Empty function call 22.4 ns (44.6M ops/sec)
Function with 5 args 24.0 ns (41.7M ops/sec)
Method call 23.3 ns (42.9M ops/sec)
Lambda call 19.7 ns (50.9M ops/sec)
try/except (no exception) 21.5 ns (46.5M ops/sec)
try/except (exception raised) 139 ns (7.2M ops/sec)
isinstance() check 18.3 ns (54.7M ops/sec)
⏱️ Async Create coroutine object 47.0 ns (21.3M ops/sec)
run_until_complete(empty) 27.6 μs (36.2k ops/sec)
asyncio.sleep(0) 39.4 μs (25.4k ops/sec)
gather() 10 coroutines 55.0 μs (18.2k ops/sec)
create_task() + await 52.8 μs (18.9k ops/sec)
async with (context manager) 29.5 μs (33.9k ops/sec)

内存消耗§

了解不同 Python 对象的内存占用情况。

空Python进程占用15.77 MB内存§


字符串§

ASCII字符串的经验法则是:核心字符串对象占用41字节,每增加一个字符额外占用1字节。注意:Python根据内容采用不同的内部表示——包含Latin-1字符的字符串占用1字节/字符,包含多数Unicode字符的占用2字节/字符,包含表情符号或罕见字符的占用4字节/字符。

String Size
Empty string "" 41 bytes
1-char string "a" 42 bytes
100-char string 141 bytes

图0:每位程序员都应了解的 Python 数字


数字§

Python中的数字结构出人意料地庞大。它们必须继承自CPython的PyObject,并受垃圾回收的引用计数机制约束,这超越了我们常见的认知模型:

  • 2字节 = 短整型
  • 4字节 = 长整型
  • 等等
Type Size
Small int (-5 to 256, cached) 28 bytes
Large int (1000) 28 bytes
Very large int (10**100) 72 bytes
Float 24 bytes

图1:每位程序员都应了解的 Python 数字


集合§

Python的集合特性令人惊叹。动态增长的列表。超高性能的字典与集合。以下是它们在空状态与“满载”状态下的开销对比。

Collection Empty 1,000 items
List (ints) 56 bytes 36.0 KB
List (floats) 56 bytes 32.1 KB
Dict 64 bytes 90.7 KB
Set 216 bytes 59.6 KB

图2:每位程序员都应了解的 Python 数字


类与实例§

槽机制为Python类带来了创新变革。它彻底消除了通过__dict__追踪字段及其他值的概念。即使仅处理单个实例,槽类也具有显著更小的体积(5个属性时仅需212字节,而普通类需694字节)。若需在内存中存储大量列表或缓存,槽位类的内存节省效果尤为显著——约可减少30%的内存占用。值得庆幸的是,在多数应用场景中,仅需添加槽位条目即可轻松实现内存优化。

Type Empty 5 attributes
Regular class 344 bytes 694 bytes
__slots__ class 32 bytes 212 bytes
dataclass 694 bytes
@dataclass(slots=True) 212 bytes
namedtuple 228 bytes

聚合内存占用(1,000个实例):

Type Total Memory
List of 1,000 regular class instances 301.8 KB
List of 1,000 __slots__ class instances 215.7 KB

图3:每位程序员都应了解的 Python 数字


基础操作§

基础 Python 操作的开销:远慢于 C/C++/C# 但仍相当快速。我已在源代码仓库中添加与 C# 的简要对比

算术运算§

Operation Time
Add two integers 19.0 ns (52.7M ops/sec)
Add two floats 18.4 ns (54.4M ops/sec)
Multiply two integers 19.4 ns (51.6M ops/sec)

图4:每位程序员都应了解的 Python 数字


字符串操作§

Python中的字符串操作同样高效。在基于模板的格式化样式中,f字符串速度最快。简单连接(+)在组合少量字符串时更快捷,但f字符串具有更好的可扩展性且更易于阅读。即使是最慢的格式化样式,其执行时间也仅以纳秒计。

Operation Time
Concatenation (+) 39.1 ns (25.6M ops/sec)
f-string 64.9 ns (15.4M ops/sec)
.format() 103 ns (9.7M ops/sec)
% formatting 89.8 ns (11.1M ops/sec)

图5:每位程序员都应了解的 Python 数字


列表操作§

Python中的列表操作极其高效。添加单个元素通常仅需28纳秒。换言之,每秒可执行3500万次追加操作。除非列表需采用倍增算法等方式扩展,否则性能保持稳定。这可从1000个元素的每秒操作次数中得到验证。

令人惊讶的是,列表推导式比使用追加语句的等效for循环快26%

Operation Time
list.append() 28.7 ns (34.8M ops/sec)
List comprehension (1,000 items) 9.45 μs (105.8k ops/sec)
Equivalent for-loop (1,000 items) 11.9 μs (83.9k ops/sec)

图6:每位程序员都应了解的 Python 数字


集合访问与迭代§

从Python内置集合中提取数据的速度能有多快?以下案例生动展示了正确数据结构的效率优势:仅处理1000项数据时,item in setitem in dict的执行速度竟比item in list200倍!这种差异源于算法复杂度:集合与字典采用O(1)哈希查找,而列表需进行O(n)线性扫描——且随集合规模扩大差距持续扩大。

下图的x轴呈非线性分布。

键值/索引访问§

Operation Time
Dict lookup by key 21.9 ns (45.7M ops/sec)
Set membership (in) 19.0 ns (52.7M ops/sec)
List index access 17.6 ns (56.8M ops/sec)
List membership (in, 1,000 items) 3.85 μs (259.6k ops/sec)

图7:每位程序员都应了解的 Python 数字


长度计算§

len() 函数执行极快。或许在循环执行100次时,我们根本无需将其从测试条件中优化掉。

Collection len() time
List (1,000 items) 18.8 ns (53.3M ops/sec)
Dict (1,000 items) 17.6 ns (56.9M ops/sec)
Set (1,000 items) 18.0 ns (55.5M ops/sec)

迭代§

Operation Time
Iterate 1,000-item list 7.87 μs (127.0k ops/sec)
Iterate 1,000-item dict (keys) 8.74 μs (114.5k ops/sec)
sum() of 1,000 integers 1.87 μs (534.8k ops/sec)

类与对象属性§

读写属性的开销,以及__slots__如何改变情况。在大型集合中,slots可节省约30%的内存,且属性访问速度几乎完全相同

属性访问§

Operation Regular Class __slots__ Class
Read attribute 14.1 ns (70.9M ops/sec) 14.1 ns (70.7M ops/sec)
Write attribute 15.7 ns (63.6M ops/sec) 16.4 ns (60.8M ops/sec)

图8:每位程序员都应了解的 Python 数字


其他属性操作§

Operation Time
Read @property 19.0 ns (52.8M ops/sec)
getattr(obj, 'attr') 13.8 ns (72.7M ops/sec)
hasattr(obj, 'attr') 23.8 ns (41.9M ops/sec)

JSON与序列化§

对比标准库JSON与优化替代方案。orjson 支持更多数据类型,处理复杂对象时速度超过标准库json的8倍。令人印象深刻!

序列化(转储)§

Library Simple Object Complex Object
json (stdlib) 708 ns (1.4M ops/sec) 2.65 μs (376.8k ops/sec)
orjson 60.9 ns (16.4M ops/sec) 310 ns (3.2M ops/sec)
ujson 264 ns (3.8M ops/sec) 1.64 μs (611.2k ops/sec)
msgspec 92.3 ns (10.8M ops/sec) 445 ns (2.2M ops/sec)

图9:每位程序员都应了解的 Python 数字


反序列化(加载)§

Library Simple Object Complex Object
json (stdlib) 714 ns (1.4M ops/sec) 2.22 μs (449.9k ops/sec)
orjson 106 ns (9.4M ops/sec) 839 ns (1.2M ops/sec)
ujson 268 ns (3.7M ops/sec) 1.46 μs (682.8k ops/sec)
msgspec 101 ns (9.9M ops/sec) 850 ns (1.2M ops/sec)

Pydantic§

Operation Time
model_dump_json() 1.54 μs (647.8k ops/sec)
model_validate_json() 2.99 μs (334.7k ops/sec)
model_dump() (to dict) 1.71 μs (585.2k ops/sec)
model_validate() (from dict) 2.30 μs (435.5k ops/sec)

Web框架§

返回简单JSON响应。使用wrk对运行4个工作进程的Granian本地服务器进行基准测试。每个框架均从最小化接口返回相同JSON有效负载。不涉及数据库访问等操作,仅评估各框架本身的开销与性能表现。在视图方法中执行的代码基本相同。

结果§

Framework Requests/sec Latency (p99)
Flask 16.5 μs (60.7k req/sec) 20.85 ms (48.0 ops/sec)
Django 18.1 μs (55.4k req/sec) 170.3 ms (5.9 ops/sec)
FastAPI 8.63 μs (115.9k req/sec) 1.530 ms (653.6 ops/sec)
Starlette 8.01 μs (124.8k req/sec) 930 μs (1.1k ops/sec)
Litestar 8.19 μs (122.1k req/sec) 1.010 ms (990.1 ops/sec)

图10:每位程序员都应了解的 Python 数字


文件I/O§

读写不同大小的文件。注意图表y轴为非线性刻度。

基础操作§

Operation Time
Open and close (no read) 9.05 μs (110.5k ops/sec)
Read 1KB file 10.0 μs (99.5k ops/sec)
Read 1MB file 33.6 μs (29.8k ops/sec)
Write 1KB file 35.1 μs (28.5k ops/sec)
Write 1MB file 207 μs (4.8k ops/sec)

图11:每位程序员都应了解的 Python 数字


Pickle 与 JSON 对比(序列化)§

有关更多序列化选项(包括 orjsonmsgspecpydantic),请参阅上文JSON与序列化

Operation Time
pickle.dumps() (complex obj) 1.30 μs (769.6k ops/sec)
pickle.loads() (complex obj) 1.44 μs (695.2k ops/sec)
json.dumps() (complex obj) 2.72 μs (367.1k ops/sec)
json.loads() (complex obj) 2.35 μs (425.9k ops/sec)

数据库与持久化§

使用相同复杂对象比较 SQLite、diskcache 和 MongoDB。

测试对象§

user_data = {
    "id": 12345,
    "username": "alice_dev",
    "email": "alice@example.com",
    "profile": {
        "bio": "Software engineer who loves Python",
        "location": "Portland, OR",
        "website": "https://alice.dev",
        "joined": "2020-03-15T08:30:00Z"
    },
    "posts": [
        {"id": 1, "title": "First Post", "tags": ["python", "tutorial"], "views": 1520},
        {"id": 2, "title": "Second Post", "tags": ["rust", "wasm"], "views": 843},
        {"id": 3, "title": "Third Post", "tags": ["python", "async"], "views": 2341},
    ],
    "settings": {
        "theme": "dark",
        "notifications": True,
        "email_frequency": "weekly"
    }
}

SQLite(JSON二进制大对象方案)§

Operation Time
Insert one object 192 μs (5.2k ops/sec)
Select by primary key 3.57 μs (280.3k ops/sec)
Update one field 5.22 μs (191.7k ops/sec)
Delete 191 μs (5.2k ops/sec)
Select with json_extract() 4.27 μs (234.2k ops/sec)

diskcache§

Operation Time
cache.set(key, obj) 23.9 μs (41.8k ops/sec)
cache.get(key) 4.25 μs (235.5k ops/sec)
cache.delete(key) 51.9 μs (19.3k ops/sec)
Check key exists 1.91 μs (523.2k ops/sec)

MongoDB§

Operation Time
insert_one() 119 μs (8.4k ops/sec)
find_one() by _id 121 μs (8.2k ops/sec)
find_one() by nested field 124 μs (8.1k ops/sec)
update_one() 115 μs (8.7k ops/sec)
delete_one() 30.4 ns (32.9M ops/sec)

对比表§

Operation SQLite diskcache MongoDB
Write one object 192 μs (5.2k ops/sec) 23.9 μs (41.8k ops/sec) 119 μs (8.4k ops/sec)
Read by key/id 3.57 μs (280.3k ops/sec) 4.25 μs (235.5k ops/sec) 121 μs (8.2k ops/sec)
Read by nested field 4.27 μs (234.2k ops/sec) N/A 124 μs (8.1k ops/sec)
Update one field 5.22 μs (191.7k ops/sec) 23.9 μs (41.8k ops/sec) 115 μs (8.7k ops/sec)
Delete 191 μs (5.2k ops/sec) 51.9 μs (19.3k ops/sec) 30.4 ns (32.9M ops/sec)

注:MongoDB 因网络访问版本问题导致进程内访问受限。

图12:每位程序员都应了解的 Python 数字


函数调用开销§

函数调用、异常处理与异步操作的隐性成本。

函数调用§

Operation Time
Empty function call 22.4 ns (44.6M ops/sec)
Function with 5 arguments 24.0 ns (41.7M ops/sec)
Method call on object 23.3 ns (42.9M ops/sec)
Lambda call 19.7 ns (50.9M ops/sec)
Built-in function (len()) 17.1 ns (58.4M ops/sec)

异常处理§

Operation Time
try/except (no exception raised) 21.5 ns (46.5M ops/sec)
try/except (exception raised) 139 ns (7.2M ops/sec)

类型检查§

Operation Time
isinstance() 18.3 ns (54.7M ops/sec)
type() == type 21.8 ns (46.0M ops/sec)

异步开销§

异步机制的成本。

协程创建§

Operation Time
Create coroutine object (no await) 47.0 ns (21.3M ops/sec)
Create coroutine (with return value) 45.3 ns (22.1M ops/sec)

协程运行§

Operation Time
run_until_complete(empty) 27.6 μs (36.2k ops/sec)
run_until_complete(return value) 26.6 μs (37.5k ops/sec)
Run nested await 28.9 μs (34.6k ops/sec)
Run 3 sequential awaits 27.9 μs (35.8k ops/sec)

asyncio.sleep()§

注:asyncio.sleep(0)Python 事件循环中的特殊情况——它会让出控制权,但会立即调度回调函数,因此比常规睡眠更快,但不能代表常规事件循环的开销。

Operation Time
asyncio.sleep(0) 39.4 μs (25.4k ops/sec)
Coroutine with sleep(0) 41.8 μs (23.9k ops/sec)

asyncio.gather()§

Operation Time
gather() 5 coroutines 49.7 μs (20.1k ops/sec)
gather() 10 coroutines 55.0 μs (18.2k ops/sec)
gather() 100 coroutines 155 μs (6.5k ops/sec)

任务创建§

Operation Time
create_task() + await 52.8 μs (18.9k ops/sec)
Create 10 tasks + gather 85.5 μs (11.7k ops/sec)

异步上下文管理器与迭代§

Operation Time
async with (context manager) 29.5 μs (33.9k ops/sec)
async for (5 items) 30.0 μs (33.3k ops/sec)
async for (100 items) 36.4 μs (27.5k ops/sec)

同步与异步对比§

Operation Time
Sync function call 20.3 ns (49.2M ops/sec)
Async equivalent (run_until_complete) 28.2 μs (35.5k ops/sec)

方法论§

基准测试方法§

  • 所有基准测试均多次运行,且不计入预热时间
  • 计时采用timeitperf_counter_ns(视情况而定)
  • 内存使用sys.getsizeof()tracemalloc测量
  • 结果取N次运行中位数

环境§

  • 操作系统: macOS 26.2
  • Python: 3.14.2 (CPython)
  • CPU: ARM – 14核(14逻辑核)
  • 内存: 24.0 GB

代码仓库§

所有基准测试代码详见:https://github.com/mkennedy/python-numbers-everyone-should-know


核心要点§

  1. 内存开销:Python对象存在显著内存开销——即使空列表也占用56字节
  2. 字典/集合速度:字典和集合的查找速度极快(平均情况为 O(1)),远优于列表成员检查(O(n))
  3. JSON 性能:替代 JSON 库如 orjsonmsgspec 比标准库 json 快 3-8 倍
  4. 异步开销:创建和等待协程存在可测量的开销——仅在需要并发时使用异步
  5. __slots__权衡__slots__可节省内存(实例集合约30%),且几乎不影响性能
元素周期表抱枕

本文由 TecHug 分享,英文原文及文中图片来自 Python Numbers Every Programmer Should Know

共有{184}精彩评论

  1. 这里很多人评论说,如果你在Python中必须关注具体的延迟数值,那就该换用其他语言。

    我不同意这种观点。许多重要的大型代码库都是用Python构建和维护的(Instagram、Dropbox、OpenAI),当你不可避免地遇到Python性能问题时,懂得如何通过逻辑推理解决问题——而无需转向其他更复杂的语言——这非常有用。

    Python是极具价值的工具,掌握这些数值能让你更精通使用。作者身为*Python软件基金会院士*,正是运用此工具的典范。

    常见情况下,Python性能问题并非源于语言极限,而是源于粗糙低效的代码——例如在热点循环中无谓调用函数十万次。

    我在此处[https://thundergolfer.com/computers-are-fast](https://thundergolfer.com/computers-are-fast)撰写了更聚焦的“你应该了解的Python延迟数值”测验。

    1. 我负责优化Python系统的性能。这些数据对我基本无用——除非实际遇到问题,否则完全无关紧要,届时我会自行测量。若你写代码时纠结于节省方法调用次数,说明你并未真正发挥语言优势,或许该考虑换种语言。

      1. 这永远是权衡取舍。

        优秀的设计不会凭空产生,至少需要对环境轮廓有所认知。

        早餐时构思创意——让粘稠的牛奶洒在餐桌上又何妨?若日后成问题,我再清理也不迟。

        另一种情况是:避免泼洒牛奶其实并非苛刻要求——或许事后清理不费事,但此刻保持整洁也无伤大雅,何不稍加注意卫生?

        在制造混乱、清理混乱与避免混乱之间存在平衡点。另一极端则是过度防患于未然,反而阻碍了进展。

        最佳状态介于两极之间,心中有个大致的数值范围会有所帮助。这能反映环境状况。

    2. 不。

      Python的问题在于,它在令普通开发者意外的场景中极其缓慢。就连调用函数或访问字典这类基础操作都异常迟缓。

      若非拥有海量流行的C/C++库,Python早已销声匿迹。正是Numpy等库拯救了它。

      1. 我不理解为何说Python被numpy等库“拯救”——数值计算生态几乎与语言同步诞生,二者本就共同演化。为何Perl(搭配PDL)、R或Ruby(甚至PHP)未能复制这种成功?

      2. 函数调用加字典键查找仅耗时22纳秒,这速度实在令人惊讶。

        1. 同样时间内,Java应用完成了50次字符串解析(构建对象层次结构,使用未缓存的正则表达式),从请求对象提取值至处理对象,处理错误并记录结果。

          整整三次。

          这还是代码的*初级版本*,因为“并行化留待后续”,当时只专注于实现核心逻辑。

          事实证明,当使用真正适合任务的编程语言时,无需纠结每个函数调用——计算机本就足够快。

          人们严重*低估*了Python的运行效率。

          我们正在用Java重构内部服务,从Python迁移而来。即便粗糙的初版实现也快了十倍以上,无需额外工程投入——正是因为Python调用函数耗时漫长。旧版Python版本已彻底过时,除非彻底重构并进行大规模改动,否则永远无法提升性能。

          Python执行两个整数相加需耗时19纳秒,而CPU在2004年就能以0.3纳秒完成。

          每个整数占用28字节内存,这或许正是新版Java服务内存占用仅为原版十分之一的原因。

          1. Python确实慢,但根据我的经验(主要涉及Web服务和数据处理),I/O才是最大的瓶颈。等待数据库响应、其他HTTP服务或本地存储时,耗时往往超过1毫秒。

            1. 没错。

              除非问题不在I/O环节。

          2. 22纳秒大约相当于100条处理器指令。我怀疑任何编程语言都无法用100条指令解析50个字符串,更别提用原始代码实现了。

      3. 我讨厌Python,但若瓶颈在于SQLite查询,优化几笔加法运算根本无济于事。正因如此,你至少需要对这些表结构有概念。

    3. 赞同,而且更重要的是:

      我认为这类数据无处不在,并非Python独有。

      在Zig语言中,我偶尔会快速查看各类操作的CPU周期消耗,以避免缓存未命中。同时需要关注数据对齐和类型大小来优化数据结构。如果这些逻辑都适用,那太糟糕了——我该放弃编程了,毕竟所有语言在特定操作上都有其固有延迟,我们本就该有所认知。

      不选Python自有其道理,但这个理由并不成立。

    4. 我认为两点都合理。Python确实较慢——速度至关重要时应避免使用,但有时难以规避。

      这份列表本身冗长且信息量有限。许多操作耗时相差无几。整数相加比浮点数相加慢一点重要吗?(即便你相信这个说法,而我不信。)不重要。更合理的总结应是:“这些操作耗时相当:简单运算、函数调用等;而这些操作明显更慢:I/O操作。”这样概括就一目了然了。

      1. *我认为这份清单本身冗长乏味且信息价值不高。*

        我同意。必须赞赏作者付出的努力,但这份清单偏离了原始文章《每位程序员都应了解的延迟数据》的核心要义——即建立对操作延迟的直观预估能力,例如理解A比B耗费高两个数量级。

    5. 部分操作存在替代模块,了解这一点很重要。但若真有必要,你应该早就知道了吧?

      对我而言,这份清单有助于选择最适合任务的语言。不过它不会改变我对Python作为原型开发语言的卓越评价。

    6. 我们的构建系统用Python编写,我希望它既能保持Python特性又不至于太糟糕,所以这些数字至关重要。

    7. > … 在热点循环中执行 O(10_000) 次的函数

      O(10_000) 这种表示法实在奇怪。

      1. 宽容点说,他们大概是想表达~10_000次而非 O(10_000)

        1. 我本意是作为数量级表示法,即10,000到90,000之间。例如调用3,000次没问题,但30,000次就过量了。确实是奇怪的记法,不过我也是在实践中逐渐掌握的。

          1. 我认为这源于自然语言表达。人们常自然地说“十万量级”,大O表示法也常被口语化为“量级”。但严格来说,这种书写方式存在概念冲突。

    8. > 许多重要的大型代码库都是用Python构建并维护的

      这究竟如何实现?难道仅仅是惯性使然,让人们用本质上无类型检查的解释型脚本语言编写大型系统?

      1. 初创公司往往选择能快速实现功能的语言编写代码,因为面对庞大代码库和高负载才是真正的奢侈难题。

        若告知你我们将构建覆盖初创企业至亚马逊的巨型支付系统,你绝不会选择Ruby编写代码,将数据存入MongoDB,再用其操作日志充当队列…但Stripe早期正是如此。他们甚至组建编译器团队为语言添加类型检查功能,因为这远比将庞大单仓库迁移至其他平台更合理。

      2. 道理很简单:大型系统始于小型系统。

        1. 大型系统往往也是小型系统的集合体。

      3. 这语言既优雅又高效,为何难以理解?

      4. 它非常自然。Python最适合从零到一的开发,因为它简单且容错性强。因此许多项目都用它起步,尤其是机器学习相关领域。项目启动后更换工具难度更大。

        1. 这绝对正确,但还有更深层的含义:Python确实出色,确实简单且容错,但其他语言也有类似特性。…但实际上并没有。除了Ruby和可能的Go语言外,其他主流语言都牺牲了易用性,去追求对绝大多数程序根本无关紧要的东西。Python的流行很大程度上并非源于其易用性,而是因为其他语言都不够好。在常规编程中,除非别无选择,我们何必去折腾Python以外的语言?

          趁着发牢骚的兴头,我要特别提一下Java:几年前我还会说Java很简单,尽管它繁琐又恼人。但最近因高中项目重新接触它(Python不适合他们的需求,而学校计算机课本已采用Java教学)。

          今年我们改用C++了。

          1. 天啊,让编程初学者直接用C++入门…这简直是“如何让大多数学生对编程彻底死心”的教科书案例。C++对新手来说真的没比这更糟糕的了。

            1. 俄勒冈州立大学在编程入门课里把C++当作“带类的C语言”来教,完全忽略了C++的其他特性。这让已有C++经验的人很沮丧,但除此之外效果还不错。

              1. 我们应区分“第一语言”课程(面向计算机科学专业学生,他们未来会学习多种语言,毕业时应具备通过自学在合理时间掌握新语言的能力)与“唯一语言”课程(面向需要编写软件的学科领域)。这两类课程目标不同——例如将OCaml作为唯一语言教学毫无意义,但作为首选语言则完全合理。

              2. 多年前我们在高中入门课就是这样学的,当时效果也相当不错。

            2. 上世纪90年代,C++曾是高中生和大学一年级生的必修课程。

              1. 我刚查了下母校——2025年他们仍在给大一新生教C++

      5. Python有类型系统,甚至支持渐进式静态类型。只要能解决问题,语言是否解释型脚本根本无关紧要。

      6. 有人提议“用Python写个原型吧”,另有人质疑“确定不用更优语言吗?同等效率却不会让我们陷入未来性能崩溃的困境”,但众人齐声回应“别担心性能问题,反正只是原型——正式版本到时再写”…

        十年后“不行太慢了;选择有:a)再砸1000万买服务器,b)花500万开发更快的Python运行时(最终可能因无人使用而放弃),c)耗时两年重写代码(大概率失败),期间无法开发新功能。那就选a吧。”

        1. 多数初创企业要成功,关键在于快速转向/开发/迭代,找到能盈利的产品+市场组合。若未能找到(而多数都未能找到),你所说的数百万收益永远不会实现。这些企业通常开发人员不足,因此短期开发效率对迭代速度至关重要。若该初创企业最终成为Dropbox或Instagram,你提到的数百万收入在数十亿规模中不过是四舍五入误差。商业决策本就如此简单,而初创企业首先是商业实体。

          有些初创企业最终处于上述两种极端之间。我曾供职于一家基于Python的此类企业。当年收入达3000万美元时,Python仅用15台廉价的2010年代服务器就支撑着1亿月独立访问量。当年收入突破10亿美元时,我们已部署Spark处理重型批处理与流式计算任务,并用Java承担高负载在线计算工作(如在线机器学习推理)。系统中还零星分布着Scala、Clojure、Haskell、C++和Rust代码(随着开发者规模突破千人,这些语言随时间推移自然渗透进来)。公司90%的代码仍基于Python运行良好。当然存在痛点,但这本是常态。当年收入达10亿美元时,我们有了预算进行优化投资(清理滞后的架构选择、为核心组件添加静态类型、扩展包管理和CI工具等)。

          但这一切的关键在于…那个最终实现3000万美元(并最终突破10亿美元)的产品,与最初向投资者展示的方案截然不同。若没有早期卓越的开发者生产力,几乎不可能通过反复尝试找到真正有效的方案。工程决策不仅关乎技术考量,更关乎业务本身。

        2. 哪种语言能“保持同等生产力,却不会在后期陷入性能深渊”?

          为何这种语言不能被视为绝对优于Python?

          1. TypeScript、C#、Go、Rust。

            我认为它们*几乎*完全优于Python,但仍有细微因素可能让你选择Python而非它们。例如任意精度整数或REPL环境。Go稍显繁琐,Rust学习难度更高(但掌握后效率显著提升)。

            总体而言它们都比Python更优。即便是需要快速迭代的初创公司也是如此。

            1. 我这么说纯粹出于惊讶,而非评判——我*绝对*想不到原生TypeScript竟比原生Python快这么多。说句公道话,我也不确定“快这么多”这个说法是否恰当。我明白TypeScript是强类型语言(嘿嘿),而Python不是,但相比我日常使用的语言(同样非强类型),Python的速度优势如此显著,以至于我潜意识里认为类型系统并非性能提升的关键。不过看来当其他方面都优化到位后,类型系统确实是性能瓶颈的(近乎)最终挑战者。

          2. 松散类型让编码速度飞快——前提是你能把所有细节都记在脑子里。Python很适合处理小规模任务。但一旦超过某个临界点,缺乏安全机制的支撑就会拖慢你的脚步。

            1. 当然,我惯用的语言比这灵活得多:我可以输入

              “`
              将“test abc999 this”赋值给x
              将x的第二个单词第4至6位加1
              输出x — 输出“test abc1000 this”
              “`

              但我依然好奇——究竟哪种语言更优?

        3. 若我用Python开发应用,十年后它成功到需要1000万美元的垂直扩展或500万美元重写,我甚至不会抱怨。

        4. 我不知道比Python更优秀的开源语言。Java和C#作为平台更出色,但它们明显带有企业枷锁。

          1. 你依然可以使用Scala、Kotlin、Clojure、F#,它们都比Python性能更优,且具备相似的原型开发能力。

            1. 这些都是非主流语言,我认为原论点依然成立。坦白说你需要证明Scala或Kotlin的价值——Scala复杂得令人发指,而Kotlin和Python一样存在功能臃肿的问题,不断堆砌冗余或难以记忆的特性与语法。Clojure和F#虽不错但过于小众。

              1. Python的复杂性对初学者显然不成问题。

                Kotlin以80%的Android市场份额主导移动开发领域。

                Scala在Python之前就凭借Hadoop、Spark等工具成为AI领域的标杆。

                Lisp类语言虽小众,但自1958年起就具备Python的灵活性,并拥有机器码编译器。

  2. 反直觉建议:若能不了解这些数字就使用Python编程,请务必坚持。

    当这些数字开始影响性能时,Python便不再是合适的工具。

    1. 或保留Python框架,将关键性能模块下沉至C/Rust扩展层——正如numpy、pandas、PyTorch等库的做法。

      但我认同你文章的核心观点——这些数字虽有趣却不值得死记硬背。更应在生产环境中对代码进行监控,用真实用户数据找出实际运行中的瓶颈(过早优化是万恶之源),并通过性能分析工具 (使用pyspy是查找CPU占用代码的最佳工具),若你开始担忧Python中向列表添加元素的耗时,那你根本就不该用Python执行这类操作。

    2. 我赞同。我靠Python谋生二十年,从未需要过这些数字,现在工作也无需它们——标题所言恰恰相反。我常通过性能分析优化代码,并根据需要选用Cython、SWIG、JIT库等工具。这些数字从未影响过我的决策。

      1. 你认为了解这些数字毫无价值?

        1. 这正是我的观点。这些数字对我毫无价值。我认为Python内置方法的速度基本相当,因此专注于解决IO延迟问题并尽量减少这类操作。对于处理海量数据的CPU密集型循环,我会优先采用numpy、DuckDB等库进行计算。若系统更复杂,则通过性能分析定位方法,并基于分析结果优化关键循环。我根本不在乎文章里的数字,因为我会进行性能分析,针对最慢的流程进行优化——比如使用Cython。我说的哪部分不合逻辑?

          1. 完全合理。尤其考虑到这些数字会随Python版本更新而变化。

        2. 正如其他人指出的,Python更适合应用于那些不关注性能数据的场景。

          若性能数据开始变得重要,通常意味着你正在将这种鸭子类型字节码脚本粘合语言应用于它并不擅长的领域。

    3. 完全正确。若你在需要关注这些数字的应用场景中工作,Python作为高度抽象的语言根本无法实现真正的优化。

    4. 为什么?我用Python结合turbodbc和pandas构建过一些庞大的分析数据流,其运行速度基本可媲美C++。虽然内存消耗更大(这确实印证了你的观点),但反过来说,每年额外成本不过5-10美元。坦白说,即使每年花费2万美元,也比雇佣更多像我这样的人来维护这些系统更划算——毕竟只需少数人维护,就能让商业智能团队使用我们提供的工具。同样地,在嵌入式开发中,微型Python对我们的工程师而言也更易于处理。

      C与Python的互操作性堪称完美,而你必须掌握Python的这些数据才能判断何时真正需要用C语言开发。随着Zig实现卓越的互操作性,前景比以往任何时候都更光明。

      并非说你完全错误。我当然不会用Python操控飞机,但实在不明白为何仅因使用解释型或GC语言就该忽视资源消耗问题。

      1. > 你需要了解Python的这些性能指标,才能判断何时真正需要用C语言实现

        人们通常采取相反做法:若pandas或numpy能解决问题,就直接使用它们。切勿在Python中编写矩阵乘法或连接操作。

        若无现成库能解决问题,这恰恰说明你该避开Python——除非你愿意耗费五年人力开发兼容Python的C/C++库。

        1. > 多数人做法恰恰相反:若pandas或numpy能解决问题,他们从一开始就直接使用。

          但这恰恰是我们采取的策略。我们并非从turbodbc+pandas起步,最初是基于SQL Alchemy和pandas的服务。当性能不足时,我介入项目,定位并解决了瓶颈。若不了解Python各组件的效率差异,我实在难以想象如何发现并修复这类问题。另外你会注意到,我们并未编写自有组件,而是直接采用了更高效的Python库。

        2. 生产环境中通常不会自行实现矩阵乘法或连接操作。针对全新、复杂或未解决的问题,已有大量工具可供选择,如Numba、Jax、Triton等,它们能编写出极高效的代码。所谓“需要高速代码就别用Python”的论调,早在十多年前就已彻底过时。

          1. 没错,我说的正是这个意思。

            若你需要编写性能敏感的代码,而现有主流Python库又无法满足需求——除非你是能组建团队开发维护库的巨头企业,否则别这么做。

            1. 这并非你的本意。若你执意要用Numba手写矩阵乘法实现,其速度大致可媲美同类C语言代码。当然你绝不该这么做——正如手写C语言矩阵乘法同样愚蠢。

              许多问题在纯Python中就能高效解决,尤其借助我提到的JIT库等日益丰富的工具集。待自由线程Python等技术落地后,可解决的问题将更多。真正无法解决的问题将属于少数——如果现在还不是的话。

      2. 从完全相反的角度看,我也编写过些微不足道的小代码片段,其中Python完全不可接受——例如在shell启动时/bash的PROMPT_COMMAND中。即便代码简单到接近Hello World级别,最终仍会导致启动时间明显拖沓。

        “`
        time python -I -c ‘print(“Hello World”)’
        real 0m0.014s
        time bash –noprofile -c ‘echo “Hello World”’
        real 0m0.001s
        “`

        1. 在 shell 启动时,你究竟需要 1ms 而不是 14ms 的启动时间?这种差异几乎无法察觉。

          启动时间的大部分消耗在于搜索文件系统中的数千个软件包。

          1. > 究竟为何需要将 shell 启动时间从 14ms 缩短至 1ms?

            正如他们所言:当动态构建 shell 输入提示符时,若存在 3 个以上此类操作且频繁使用终端,延迟差异便会变得相当明显。

            1. 啊,我只注意到“shell启动”这部分。

              没错,超过2-3个后,如果你操作够快确实会察觉差异。我猜届时我会让Gemini用Rust重写提示符构建命令(它在这方面很擅长),或者将所有提示符构建命令合并为单一命令(以摊销启动成本)。

                1. 所有信息触手可及的感觉确实很棒,但默认配置通常过于冗余。

    5. 这些数字基本上像是最后的手段。当你已经分析过所有常见问题(大容量磁盘读取、网络延迟、多项式或指数时间算法、冗余的数据结构等)并排除后,才需要在单个操作层面进行优化。

    6. 完全不是。

      其中某些数据至关重要:

      – 集合成员检查耗时19.0纳秒,列表操作耗时3.85微秒。选择合适的数据结构是关键。

      – 写入1KB文件耗时35.1微秒,而1MB文件仅需207微秒。理解I/O权衡的意义至关重要。

      – 计算1000个整数的sum()仅需1900纳秒:善用标准库与手动循环的性能差距天壤之别。

      等等。

      几年前我曾重写客户的大型代码库,将Python化。他们原有复杂计算流程需6台服务器耗时2小时。

      我们将其优化至1台服务器10分钟完成——这甚至不是项目目标,只是正确使用Python的附带效果。

      归根结底,二次行为就是二次行为。

      1. 列表成员检查远慢于集合成员检查,这是计算机科学入门课的基础知识。

  3. 我怀疑知道空字符串占用多少内存没什么意义。这篇文章或列出的数字对内存使用量和具体时间测量有着奇怪的执念。对“每个程序员”来说,更重要的是时间和空间复杂度,这样才能避免设计出不必要地慢或占用大量内存的程序。假设使用Python,知道你的整数占用28字节有什么用?归根结底,你需要判断程序是否满足性能要求。若未达标,就该采用更聪明的算法或数据处理方式。知道1000×1000的布尔值二维数组占用多少内存毫无意义,真正有价值的是判断其是否过大——或许该改用大整数配合位板方案。或者直接换语言。

    1. 我不同意。性能是种渗透性抽象,它永远都至关重要。

      你对它的认知要么隐性存在,要么显性呈现。

      即便你不知道列表追加是线性而非二次增长且相当快速——

      即便你根本不在乎简单程序因某些原因慢上万倍——只要达到基本可用标准,或你无需承受低效带来的问题。

      底层库作者依然清楚这一点,你交互的API、看到的Pythonic代码、LLMS生成的代码都会受到这种泄漏抽象的影响。

      顺便说一句,若你认为n²的朴素列表追加是糟糕示例——Python字符串追加同样是n²复杂度,这确实影响了人们的编程方式,比如f字符串的懒惰实现。

      同样地,字典在Python中高速运行的直接后果就是它们被无处不在地使用。Raymond在2017年PyCon上的旧演讲就探讨过这个问题。

      归根结底,该博文作者提供的正是对隐性性能认知的一种量化论证。

    2. > 在使用Python的前提下,知道整型占用28字节有何意义?

      当问题需要实例化大量对象时才相关。这让我想起埃里克·雷蒙德讨论使用Reposurgeon迁移GCC时遭遇困境的博文,详见[http://esr.ibiblio.org/?p=8161](http://esr.ibiblio.org/?p=8161)

  4. 关于标题的元说明(因其似乎混淆了许多评论者):标题是对杰夫·迪恩2012年著名文章《每位程序员都该知道的延迟数值》的戏仿,并非字面意义。计算机科学论文和写作中存在一个常见主题:标题常借鉴过往论文的主题进行戏仿。另一个典型例子是“_____ considered harmful”这类标题。

    1. 我打算写篇震撼性论文,标题定为《延迟数值有害论:你需要的全部》,看我的学术声誉如何飙升。

      1. “…及其在决策问题中的应用”

    2. 对论文引用的指正很到位,但作者在首段的行文完全表明他极其严肃。我认为评论者们并未产生困惑。

    3. 该文档的发布时间远早于2012年。

      据我所知,该系统基本是在杰夫加入谷歌的头几年创建的,用于原始搜索引擎的索引和数据服务。例如缓存、内存和磁盘的比较机制:它决定数据是存储在内存(索引,用于检索)还是磁盘(文档,通常不用于检索但用于评分)。同样地,加州与荷兰时差的考量——据我所知谷歌首个国际数据中心设在荷兰,他们需要权衡是批量复制整个索引,还是让美国后端处理查询而荷兰前端直接响应。

      这些数据始终处于动态更新中;例如闪存驱动器的出现就显著改变了磁盘延迟。记得杰夫曾找我谈论他发明的基因组数据压缩算法,声称“这样就能用闪存存储”(他认为用宝贵的闪存空间存储未压缩基因组数据太浪费)。

    4. 这个标题只有在数字真正有用时才成立。但这些数字毫无意义,且数量过多导致完全无法理解。

      1. 标题本意是字面理解——要求读者记住所有数字。这是对原始文档的内部梗,暗示本文将包含各类操作的时序值。

        但我完全理解单独看会令人困惑或沮丧。

  5. 每位Python程序员都该关注比底层性能细节更重要的事情。这虽是绝佳参考资料,但在极少数需要优化的场景外几乎毫无实际意义。若你的工作负载增长到需要关注这些细节的程度,那再好不过!在此之前,这不过是种干扰。

    1. 掌握常用工具的通用知识绝非分心,无论如何都是智力提升,在特定场景下更是宝贵资产。

      1. 知道空字符串占41字节或算术运算耗时多少纳秒,这不属于通用知识范畴。

        1. 这怎么不算常识?否则你凭什么判断程序运行时间是否合理?若超时,又该如何排查修复?

          1. 根据我在A轮及早期数据密集型SaaS领域的经验,只需运行程序并运用常识就能判断耗时是否合理。

            FastAPI服务的P50延迟超过30秒。你的数据采集管道运行耗时超过一个工作日,团队中甚至有数据运维人员专门等待其完成。

            你的程序显然不可接受。而且,你的问题很可能与这些经验法则毫无关联——要么是算法效率低下,更可能是工具选错(如用OLTP做OLAP),或是正确工具用错了方式(糟糕的关系模型设计或过时的LLM模型)。

            若你执着于在此场景下削减毫秒级延迟,那你就是在浪费时间做无谓之事。

            话虽如此,我确信在某些特定领域、组织或企业规模/发展阶段,掌握这些知识仍有重要价值。但对绝大多数阅读本文的人而言,这些指标恐怕毫无意义。

            无论如何,对于我们这些求知者,我仍认为这篇内容极具价值——尽管绝非常识。

            1. 我不确定这算常识,但属于基础知识范畴。并非所有HN用户都在编写网页应用,许多人可能在开发真正受计算能力限制的应用程序。

              根据我编写计算机视觉软件的经验,人们往往难以理解计算机实际运行速度的常识性认知。诸如加载广告需要多少纳秒这类知识,能极大帮助判断算法运行时长是否合理。这可能促使他们意识到算法存在根本性缺陷。我常看到开发者未能为预期设定合理边界,而这类具体数值恰恰能帮助建立边界。

          2. 你需要通过指标和性能剖析进行评估,必要时采取针对性措施。不必事先逐行审查代码是否“合理”,而应专注于真正能推动进展的工作。

            1. 这些指标是核心基础。性能分析能指出相对较慢的模块,并测试具体实现的耗时。求和百万个整数本该耗时多久?

              1. 除非影响用户,否则根本无关紧要。我不明白为何要浪费时间在虚无的问题上。

                1. 没人提议“浪费时间在虚无的问题上”。你在做无谓的抵抗。

          3. 但若缺乏某种标准对比基准,这些性能数据便毫无意义。例如当你测得某字符串操作耗时100纳秒时,如何与本文给出的数值进行比较?任何差异都可能源于个人电脑、Python版本或你的具体实现方式。因此无论如何都必须进行规范的基准测试。

            1. 若你的程序执行100万次加法操作,耗时却远超19毫秒,便可推测存在其他问题。

    2. 没错,遇到性能瓶颈时直接寻找C语言实现的模块(或自行编写)即可。这向来是Python开发者的惯用做法。

      1. 我正在(此刻正在编写代码哈哈)用业余GIS项目实践这个思路:Python帮我完成了原型和概念验证,但当数据处理规模扩展到全球范围时,其速度明显不足,因此我正借助LLM辅助将其重写为C语言版本。Python的巨大优势在于我拥有已知的可工作(但缓慢)的“参考实现”作为测试基准。当C版本输出完全一致时,我便确信其有效性。若我能保留过往C/C++/Rust等项目中经过验证的Python版本,在测试验证阶段将极具价值。

      2. 除非经过极致优化仍无解,否则我更倾向采用JIT技术避免“重写C代码”。

      3. 有时只需用分析器找出热点,对算法或数据结构做简单调整即可——这在任何语言中都适用。人们对用Python构建系统过度忧虑的做法实在可笑。

    3. 我同意——不过多年来这更多是我的主观感受。实际运行速度足够快,体验良好。

      这页数据提供了有力的佐证。至少在短期内,我能确信而非仅凭感觉:那些底层性能细节可以忽略不计。

  6. “`
    > 字符串
    > 字符串的基本规则是:核心字符串对象占用41字节,每个额外字符占用1字节。
    “`

    此说法存在误导。Python实际存在三种字符串类型(每字符1、2或4字节)。

    [https://rushter.com/blog/python-strings-and-memory/](https://rushter.com/blog/python-strings-and-memory/)

  7. 标题表述存在问题。例如:

    “`
    集合访问与迭代
    从Python内置集合中提取数据的速度有多快?以下案例生动展示了正确数据结构的效率优势:仅处理1000个元素时,set或dict的item操作比list的item操作快200倍!
    “`

    这似乎暗示着`for x in mylist`的迭代速度比`for x in myset`慢200倍。实际上是成员资格测试慢得多,而非迭代本身。(此外`for x in mydict`是遍历键而非值,因此不属于我们通常理解的字典数据迭代。)

    此外,标题为“每位程序员都应了解的Python数字”,开篇列出的20个数字仅具趣味性。

    尽管如此,文章排版精美且引人入胜。

  8. 我从“现代Python是否存在明显缺陷”的角度阅读本文颇有收获,但强烈反对要求任何人“必须掌握”这些数字。其中大概有5-10个基本类型需要掌握大致时间复杂度;其余数值应通过大O算法和数据结构知识推导得出。

  9. 这里遗漏了类实例化的时间开销。

    我记得有次重构代码提升可读性后,发现原本几微秒的操作竟耗时数十秒。

    原始代码创建了大量嵌套列表,每个子列表包含4个字段,字段类型各异——部分为整数,部分为字符串。

    我创建了新类,将字段名封装为字段属性,并添加辅助方法处理数据。新代码生成的是该类的实例列表,下游消费者可通过类结构直接获取数据类型。现代Python开发者通常会使用数据类实现此功能。

    新代码运行极其缓慢。真希望原作者能测量下类实例化的耗时。

    1. 在Python中,类实例化通常不会成为性能瓶颈。你的问题核心似乎在于滥用面向对象机制——将实例列表传递给每个方法及下游调用(而非仅引用当前实例self)。请避免这种做法,它本不该存在。你似乎在拙劣地模仿类方法,却未识别并重构那些“方法可能需要从其他实例访问的内容”。

      请将代码片段发布到StackOverflow([python]标签)或CodeReview.SE,以便他人协助修正。

      > *创建了一个新类,包含每个字段的名称和用于处理数据的辅助方法。新代码生成了一组该类的实例。下游消费者可以查看该类来了解他们获取的数据内容。*

    2. 我去看医生说:“这样做会疼”
      医生说:“别那样做”

      编辑:确实是个刻薄的回答。抱歉。但值得思考的是:为何我们总想在所有地方使用类和对象?艾伦·凯曾提出著名观点——面向对象的核心在于消息传递(尤其被Erlang社区广泛引用)。

      列表嵌套列表(每个列表重复四种不同类型)本就是优秀的数据结构,既可供外部函数操作,又便于序列化。将其转化为类和对象未必是有效的重构,我肯定要深入了解后才会批准。

      1. 核心原因在于掌控复杂性。

        当项目拥有数百万行代码和十年历史时,混乱在所难免。

        数据在传到你这里前,可能已被无数函数处理过。若使用原始列表,代码将变得极其混乱。在某个数据结构中客户名称可能是[4],而在另一个结构中可能位于[9]。更糟的是,若有人在[5]处新增字段,当两个列表合并后,下游使用合并列表的代码中姓名位置可能变为[10]。

      2. 我认为将数据封装为对象的做法合情合理。

        customers[3][4]

        的可读性远不如

        customers[3].balance

        1. 完全同意

          但这背后隐藏着所有SQL桥接器的通病——程序员确实更容易读懂customers(3).balance,但代价是必须为所有操作提供基于类的语义,这往往导致(你知道的)阻抗不匹配问题。

          我更倾向于“尽可能以原始形式存储记录”,再添加操作函数(想想pandas本质上只存储整数浮点数和字符串,因为底层是numpy)

          (当然你可以存储pyobjects,但性能会断崖式下跌。)

          总之——尽可能保持存储和数据结构的原始简洁,再编写操作函数。然后尽快迁移到pandas或SQLite吧 🙂

          1. customers[3][‘balance’]似乎是个合理的折中方案?

            1. 这要看具体情况——很可能是以语言特有的数据结构存储(比如Python的字典序列化到磁盘)。此时我们正走向更难逆转的决策,不如直接做对。但说到底还是“要看具体情况”……

  10. > 小整型缓存(0-256)

    实际是-5到256,这些值对混淆身份与相等的程序员而言行为极其诡谲。

    “`
    >>> a = -5
    >>> b = -5
    >>> a is b
    True
    >>> a = -6
    >>> b = -6
    >>> a is b
    False
    “`

    1. Java也有类似机制。初学者初次遇到时绝对会感到困惑。

  11. 这长串数字看起来奇怪地具体。除了知道f字符串比其他方式快得多,以及某些特定比较操作外,我不确定日常编程中会用到这些。

    粗略浏览后发现,多数“简单”操作耗时约20纳秒。我会把这个经验法则记在心里。

    1. 若感兴趣,f字符串之所以更快,是因为它们在编译时直接生成字节码,而非运行时调用函数

      1. 感谢分享这个信息!性能差异着实让我意外。我一直以为基础字符串格式化的各种变体编译后会生成相同的字节码。

        通常我更倾向于使用经典的%格式化(参数较长时)和f字符串(参数较短时),以提升可读性。但考虑到大规模场景下存在显著性能差异,某些情况下可能会更倾向于使用f字符串。

    2. 这个数字其实也没多大参考价值,性能主要取决于硬件。绝大多数虚拟化服务器CPU(比如最终运行Django的环境)都远不及作者的M4 Pro。

      上次我测试VPS性能时,其表现仅相当于Ivy Bridge时代的笔记本电脑。

      1. > 上次我测试VPS性能时,其表现仅相当于Ivy Bridge时代的笔记本电脑。

        我家里有多台Intel N95系统用于不同用途。发现它们能相当准确地模拟小型实例VPS的性能。N95采用的是Intel E系列核心,本质上是Sandy Bridge/Ivy Bridge架构。

        某些任务在我的MacBook上运行流畅,但在小型VPS实例上却会卡顿。通过N95系统进行验证(我已完成)很有帮助。具体情况具体分析。

  12. Python程序员无需记住85个晦涩难懂的性能指标,真正理解约7个通用系统性能指标更为重要。

  13. 本文作者在此。

    感谢大家的反馈。特别感谢@woodenchair和@aurornis指出文章的本意。

    本文绝非建议通过选择截然不同的算法来节省0.5纳秒,也非鼓吹必须对所有代码进行极致优化。

    事实上,这些数据更揭示了过度优化往往得不偿失(例如将coll的长度缓存到变量中而非反复调用,其实际效益远低于概念层面的预期)。

    只需编写干净的Python代码。多数情况下,其运行速度远超你的预期。

    我的目标仅是建立各种操作成本的参考基准,以便形成认知模型。

    1. 既然如此,你本该明确说明。但你却为那些热衷过早优化的群体提供了更多论据。

      1. 我从未要求任何人优化代码。我只是公布数据。某些人天生爱优化,这不怪我。每当我提出建议时,都是在强调“不要优化”。

        例如在帖子中写道:“或许我们根本不必在测试条件中优化那个循环100次的while循环。”

        1. 标题直译为“每位程序员都该了解的Python数字”,暗示文章细节(包括具体数值)至关重要。事实并非如此。

          真正有价值的是掌握这些操作的相对成本。其余部分均可通过性能分析针对特定架构的工作流需求进行优化。

          打个比方:涡轮设计师无需再死记“蒸汽表”数值,但设计任何朗肯循环系统时,他们必须精通高效几何结构及其权衡关系,以满足功率、扭矩和雷诺数要求。

  14. 数据展示得不错,了解数量级总是值得的。但这些图表离“每个程序员都该知道”还差得远。

    1. 我认为我们可以安全地将主张强化为“每位Python程序员都应知晓”,甚至进一步限定为那些出于“重要”目的从事专业Python编程的“严肃”程序员——而非仅限于为脚本任务学习Python的初学者。显然C#程序员没有必要去死记硬背这些数字。

      不过依我之见,只需明白“Python比C慢40-50倍且不擅长多核处理”并非反Python者的恶意宣传,而是相当合理的工程评估就足够了。若你理解这一点,其实无需那张图表。若任务能承受这种性能,则无妨;若不能,请尽早规划解决方案——无论是通过多种方式绑定更快代码、使用PyPy,还是直接弃用Python,关键在于匹配具体场景需求。

  15. 这种担忧在Python中实属蹊跷。但这种担忧也具有误导性:Python整数是任意精度类型,其存储空间和运算时间消耗会随数值大小大幅波动。

  16. 我最明显的发现是import openai和import numpy这两个操作。

    在我的旧笔记本上,它们各自耗时近一秒。

    最终我编写了自己的简单LLM库,只为在交互式脚本中不再需要导入OpenAI。

    (其实就是围绕curl请求封装的几个函数,说实话我用OpenAI库基本就只做这些事。)

    1. 我也注意到numpy导入耗时过长,导致脚本重跑明显卡顿。不确定OpenAI的拖累原因,但numpy的延迟可能源于加载原生dll?

      1. OpenAI我多年未用,但记得它最初会导入许多重量级库(比如scikit-learn),而据我所知,这些库仅用于实现嵌入函数的余弦相似度公式(笑)。

  17. 你完全*不需要*知道具体数值——只需了解不同操作的相对成本即可。

    此外,无论代码如何,你都可以对系统进行性能分析,找出“热点”所在,并在必要时重构代码或调用更高效的运行时(如Rust、Go、C)来处理这些工作流。

  18. 这篇文章的目的可不是让你死记硬背具体数字,这还用说!

    关键在于关注数量级差异,结合硬件速度[https://gist.github.com/jboner/2841832](https://gist.github.com/jboner/2841832),你就能清晰把握语言开销占比,从而选择能提升速度的构造方式。

    仅通过阅读代码,你就能感受到其运行速度及主要耗时环节。结合常规计时指标,还能评估第三方库(比如pydantic)带来的开销。

    因此我认为这份清单在代码设计阶段相当实用,能有效减少生产环境中排查慢代码的时间。

  19. 很棒的目录。关于msgspec,既然包含了pydantic,或许值得加入对msgspec结构体进行序列化和反序列化的基准测试。

  20. 我怀疑列表与字符串拼接操作并非常数时间,否则会影响其他基准测试。例如:两个列表的拼接耗时相同(无论大小),但会导致第二个列表(或两者)的访问速度变慢。

    更具争议的是:不必过度纠结Python的性能。它本就是慢语言(除部分外部库外,但这并非原帖讨论重点)。

    1. 该页面两次提及字符串连接,给出相同耗时。首次标注括号注释“(小字符串)”,第二次则未标注。我猜你写这段时看到的是后者,因为我也认同不能简单标记为常量时间。但他们似乎确实指的是拼接“小”字符串的情况——此时Python对象构造的开销会远大于组合字符串的构造成本。

  21. 信息很有意思,但这些并非精确数据。

    100字符字符串占用141字节显然不准确,该数值仅适用于ASCII编码的100字符字符串。

    更具参考价值的是Unicode字符串(假设采用UTF-8编码)的开销。我推测100个表情符号字符串约需441字节(仅为假设),而100个变音符字符串约需241字节。

  22. 掌握这些细节恰恰是开发者不该承担的负担。解决代码中的“大O”问题才是正事。若涉及“大O”层面的特殊情况——比如“你以为常见操作是O(1)却实际是O(N^2)”这类反直觉的例外(若真存在的话)——只需保持警惕,专注完成任务即可。

    或许你偶尔会遇到关键时刻:2倍加速决定成败,而距离需要4倍加速的期限只剩一周。但这种情况并不常见。

  23. 关于这些数字对普通软件工程师的关联性,存在诸多讨论。

    首先需明确基础系统为macOS/M4Pro,因此:

    – 内存访问速度*可能*远超x86服务器。- 磁盘访问速度*可能*远逊于x86服务器。

    *) 选择x86服务器作为基准,因当前多数应用运行于x86 Linux主机,尽管其他ARM处理器也有相当数量的应用。

    虽然内存占用可能变化不大,但加载的库及其架构(即是否通过Rosetta运行)会改变进程的整体占用。

    正如某条关联评论所述 -> 在做出假设前,务必先检查/追踪自身工作流/性能表现。更高层次的性能优化完全取决于具体使用场景。

  24. 惊讶于列表推导式仅比for循环快26%。以前感觉能快4-5倍

  25. 为何整型占用28字节,而1000个整型的列表却仅占7.87KB?

    1. 这似乎是列表本身的大小,不包含其内容:每个条目占用8字节用于对象指针,加上千位与千分位单位转换。所有Python值都经过“装箱”处理——对Python程序员而言,理解这个机制可能比这些具体数字更重要。

      浮点数列表占用更大,尽管它同样只是由1000个8字节指针组成的数组。我推测这是因为整型数组由 range() 构造,其具有 __len__() 特性,因此列表能精确分配所需空间;而浮点数组由生成器表达式构造,运行时会动态扩展,末尾保留部分冗余空间。

      1. 你竟能推断出浮点数列表与整数列表容器大小差异的原因,这相当了不起——若作为面试题,我认为难度相当高。

      2. 确实如此。我已更新结果包含内部元素,并调整浮点数列表的创建方式使其与整数列表一致。

  26. 需注意这些数值会因测量对象、硬件架构及Python二进制文件的构建方式而变化。

    例如,我的M4 Max设备运行Homebrew编译的Python 3.14.2(是编译版而非预编译版)时,启动REPL(在提示符下运行`python3`)需占用19.73MB内存。

    在同一系统上启动相同版本的Python,仅执行一次`time.sleep()`[1]就占用了11.70MB内存。

    我的英特尔Mac运行通过Homebrew安装的Python 3.14.2(poured版本),启动REPL需要37.22MB内存,执行`time.sleep`则占用9.48MB。

    “内存占用量”数据源于执行`ps auxw | grep python`命令,提取驻留集大小(RSS列)数值并除以1,024。

    1: python3 -c ‘from time import sleep; sleep(100)’

  27. 以下内容令我困惑:

    “`
    Python的字符串操作同样高效。f字符串是最快的格式化方式,而即使最慢的格式化方式也仅需纳秒级时间。

    字符串连接 (+) 39.1 ns (25.6M 操作/秒)
    f-字符串 64.9 ns (15.4M 操作/秒)
    “`

    说明中称f-字符串最快,但数据却显示字符串连接耗时更短?我以为可能是笔误,但图表中的柱状图也反映了这种情况?

    1. 或许因为除最简单情况外,通常需要两次及以上连接才能实现单个字符串字符串的效果?

      “`
      “literal1 ” + str(expression) + “ literal2”
      “`

      vs

      “`
      f“literal1 {expression} literal2”
      “`

      唯一可能更快的场景是类似“foo” + str(expression)的形式

    2. 字符串拼接通常不被视为“格式化风格”,该术语特指表格中其他三行所采用的模板字符串——其内部使用专属语法对值进行格式化。

  28. 对代码中操作的成本至少有个粗略了解总是明智的,但有时代价高昂的错误会出现在难以察觉的地方。

    如果我只安装了基础Python环境,且需要测试一个.py文件,那么获取调用树(或类似结构)及各元素计算成本的可视化结果最简便的方式是什么?

  29. 嗯…这类工作绝对应该有标准偏差。另外,N次运行指什么?文档里有说明吗?

    1. 这是开源项目,你可以直接查看代码 🙂 但这里给你个简要说明:并非仅运行一次就取数值:

      基准测试迭代流程

      核心方法:

      – 预热阶段:100次迭代用于准备操作(默认值)
      – 计时运行:5次重复运行(默认值),每次执行指定次数的操作
      – 结果:5次运行中每项操作的平均耗时中位数

      按操作速度划分的迭代次数:- 极快操作(算术):每次运行100,000次迭代

      – 快速操作(字典/列表访问):每次运行10,000次迭代
      – 中速操作(列表成员检测):每次运行1,000次迭代
      – 较慢操作(数据库、文件I/O):每次运行1,000-5,000次迭代

      质量控制:
      – 计时期间禁用垃圾回收以避免干扰

      – 预热运行消除冷启动偏差

      – 5次运行中位数值可降低异常值噪声

      – 结果被捕获以防止编译器优化消除

      总执行次数:典型基准测试包含1,000次迭代和5次重复,每项操作在报告中位数结果前运行5,100次(100次预热 + 5×1,000次计时)。

      1. 这解释了N的含义(为何不在正文中说明)。若仅报告中位数,是否附有置信区间或标准差等补充统计数据?严肃的基准测试理应展示数据分布或变异性,不是吗?

  30. 令人意外的是`isinstance()`的比较方式采用`type() == type`而非`type() is type`——后者本应更快,毕竟`==`的实现通常仍会调用`isinstance`。

    1. 另外仓库似乎已设为私有,因此无法提交问题或复现这些数据。

    1. @esafak 说得有道理。我已根据2012年原文中的数字倍增图表更新了文章。

  31. 作为长期使用性能慢几个数量级的语言开发者——从CPU速度还以两位数兆赫计的年代就如此——想到这里竟有任何操作以纳秒为单位计时,我简直要哭了

  32. 起初我还感叹字符串多么高效…但后来才明白算术运算有多低效。有趣的对比,不过实际速度和I/O性能取决于诸多因素,况且生产环境中很少有人用Mac mini,所以这些数据绝对不具代表性。

  33. 为什么?如果这些微基准测试对你的领域至关重要,你根本不会选择Python。

    1. 这是“非此即彼”的谬误。使用Python并接受某些性能损失,并不意味着你对所有性能损失都无所谓——尤其当存在更优方案时。

      举个简单例子:用集合代替列表检查成员资格是基础替换方案,能显著提升Python运行效率。使用Python并不意味着性能问题可以放任不管。

      1. 先生,这属于算法优化范畴(log n与n的区别),而非微基准测试。

        1. “谬误先生”?还有更成熟的蔑称吗?

          该案例在原文示例中明确列出:

          “按键查找字典”、“列表成员检测”

          难道必须标注“此处差异源于算法”才能使比较有效?

          反之,内存与磁盘访问时间的差异是否因非算法因素而微不足道?

    2. …还有其他搞笑笑话你可以自己编!

  34. 我认为许多评论者都偏离了重点。

    无论使用Python、汇编还是HDL,分析性能数据都至关重要。若不明白代码为何缓慢,你总能通过观察操作耗时周期来深入理解代码运行机制。随着编程经验积累,这些原理终将豁然开朗,但借助这类参考资料加速学习进程至关重要。观察性能数据并追问“为何某些操作耗时更长”——或“为何某些操作耗时完全相同”——正是绝佳的学习契机。

    在我Python生涯早期,曾编写过一个跨磁盘查找重复文件的脚本。初版运行极其缓慢,随着我逐步掌握多层次优化技巧,脚本经历了多次迭代优化。整个过程从未涉及C语言,仅通过缓存机制、快速枚举磁盘文件的方法,以及用集合替代列表等手段就实现了性能飞跃。最终优化后,脚本运行时间从15分钟缩短至10秒。或许用C语言实现能进一步缩短至1秒,但若我当初认定脚本慢是Python的局限,就会耗费数小时改写C代码,结果可能只是从15分钟变为14分51秒。

    有人认为将Python与C语言的运行数据并列展示很有价值,但正如没人会直接建议你用FPGA替代C语言编程一样,贸然断言Python是错误选择也同样失当——它往往并非如此。

  35. 整体参考价值很高,但部分数据在实践中会存在偏差:例如100字符字符串占用141字节的结论不适用于非ASCII字符串,且当对象头开销发生变化时数据也会改变。

  36. 优质资源。我希望能看到不同作用域下变量访问时间的对比。

  37. 我对JAX如此兴奋的原因之一,正是期待它能让我编写高效的Python代码,而无需纠结这些细节。

  38. 不明白为何反复强调slots类比普通dict类更大,却不计算dict本身的大小

  39. 天啊,比起JVM或.NET平台,更别说C++或Rust了,这种内存膨胀简直超乎想象!

  40. > Python中的数字占用空间出奇地大

    不禁让人怀疑cpython开发者是否考虑过类似v8的NaN装箱或指针填充机制。

  41. 整型大于浮点型,但浮点列表却大于整型列表

    话说回来,若你对本文任何数字感到担忧,或许根本不该用Python。开个玩笑,但请务必使用Numba或Numpy,否则每个微小数据都创建对象将付出巨大开销。

  42. > 属性读取 (obj.x) 14 ns

    注意 protobuf 属性读取效率比这低 20-50 倍

  43. 好奇为何空集合占用的内存远超空字典

  44. 大型语言模型能提升Python代码性能。我已在多个项目中亲自验证过。

  45. 在审查结果和基准测试代码后,我注意到一些疑问和可疑行为,具体如下:

    – 若槽位属性读取与常规属性读取具有相同延迟,我推测常规类可能缺乏足够的“附加功能”(如继承/元编程/双向属性覆盖等),无法抵消缓存属性访问的简单优化,从而使其速度等同于槽位类。我明白随着时间推移,槽位机制的性能优势会逐渐减弱——但这只是我的直觉判断,可能有误——目前我尚未感受到这种趋势。

    – 同样地,“从@property读取”的速度也让我感到可疑。即便类查找缓存具备描述符协议感知能力,调用方法的开销竟与访问字段的开销惊人地接近。这或许能用属性描述符的“get”方法解释:作为所有调用形式中最简单易优化的形式(绑定方法,保证永不带参数),其栈帧/参数设置开销可能大幅降低…但这仅当属性方法体为“return 1”这类极快操作时成立。然而本次基准测试中的属性操作涉及查找类中的其他字段,因此我预期其速度会比字段访问慢得多,而非仅仅稍慢([https://github.com/mikeckennedy/python-numbers-everyone-shou…](https://github.com/mikeckennedy/python-numbers-everyone-should-know/blob/main/code/attributes/other_ops.py#L32C7-L32C24))。

    – 关于“访问对象字段”(属性/数据类/槽/MRO等)的话题,基准测试结果实在难以解读——不仅限于这些测试,我见过的所有测试皆如此。究其根本,因为涉及两项操作:*解析*字段以获取其数据,以及*访问*该数据。例如,@property 位于类的方法缓存中,因此解析“instance.propname”的速度取决于方法缓存。这可能比访问“instance.attribute”(普通字段而非@property或其他描述符)更快,具体取决于继承结构、槽位、“__getattr__”覆盖等因素。另一方面,对多数@property而言,*访问*“instance.propname”的数据开销会高出*许多*(因其需调用函数、使用参数栈,通常还涉及其他属性查找/调用其他函数/操作局部变量等); 而访问“instance.attribute”的数据则快速且呈常数时间——最多只需一两次指针追踪。

    – 细节问题:为何将序列化归类到文件I/O?这些基准测试并未计时执行I/O的pickle函数,而是测试序列化/反序列化功能,因此应归入上文的json/pydantic/同类工具组。

    – Asyncio虽非新生事物,但多数基准测试结果被过度夸大,因其未区分协程、任务和未来对象。协程的创建和调用成本低廉,但任务和未来对象在使用时存在额外开销(即便是快速的CFutures),其构造过程更是消耗巨大开销——因为它们需要的数据资源远超生成器函数(虽然原始协程在某种程度上可视为生成器函数的简化形式,但这种认知并不如多数人想象的那般准确…这又是另一个话题了)。现在,`run_until_complete{}`和`gather()`会先将参数强制转换为任务/未来对象——这种检测、强制转换和构造过程耗时且消耗大量开销。这点值得注意(毕竟许多人不知不觉承担着这种强制转换的代价),但它模糊了“等待 asyncio 操作完成的开销”与“启动 asyncio 操作的开销”之间的界限。要么调用 run_until_complete()/gather() 内部使用的底层函数,要么将基准测试拆分为传递 Futures/Tasks/常规协程的版本,这样可能更合适。

    – 将 “asyncio.sleep(0)” 作为确定 Python 事件循环最小等待时间的基准测试是个糟糕的主意。sleep(0) 具有特殊机制(详见:[https://news.ycombinator.com/item?id=46056895](https://news.ycombinator.com/item?id=46056895)),无法代表实际情况。若要测试“事件循环完成一次迭代并产生结果所需时间”(即Python中process.nextTick的等效操作),更推荐使用低级循环方法如“call_soon”,或将完成操作委托给Task对象并等待其返回。

  46. tfa提到在多核平台运行基准测试,但未说明是否启用多线程…代码简要分析表明并未启用

  47. 嗯…不行。我深耕Python十余年,这类微优化需求最多出现过两次。

    1. 抱歉,此处禁止反对过早优化或为Python辩护。

    1. 遗憾你的评论被负评了。但确实需要澄清几点:

      1) 测量结果存在缺陷。1000个整数的列表实际占用空间可能小4倍。多数时间测量依赖未提及的具体条件,因此无法复现。

      2) 这是典型的AI式思维谬误。哈希表绝非“比列表快200倍!”——复杂度理论并非如此运作。

      3) orjson/ujson存在缺陷,这正是它们无法取代标准库实现的原因之一。使用时需警惕崩溃、JSON数据损坏等各类问题。

      4) 实际用于数值计算的应用场景——如numpy等库——在讨论中完全未被提及。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

你也许感兴趣的: