最省事方案是安装jupyter_contrib_nbextensions并启用execute time插件,自动显示单元格运行耗时;%time测单行、%%time测整单元格但不持久;gpu代码需同步;%%timeit适合多轮性能对比;手写计时推荐perf_counter()并避免变量污染。

直接启用 Execute Time 扩展最省事
如果你只是想在每个单元格下方自动显示「上次运行耗时」,不用写代码、不改配置,就装 jupyter_contrib_nbextensions 并启用 Execute Time 插件。它会把时间写在单元格底部,重启 kernel 后也保留(只要没清空输出)。
常见失败点:jupyter nbextension enable execute_time/Exec 报错或没反应,大概率是没装配套的 configurator 或版本不匹配。必须按顺序执行:
pip install jupyter_contrib_nbextensions jupyter_nbextensions_configuratorjupyter contrib nbextension install --userjupyter nbextension enable execute_time/Exec
装完重启 Jupyter(不是 reload 页面,是关掉进程再 jupyter notebook 重开),然后在菜单栏找 Nbextensions → 搜索 time → 勾选 Execute Time → 确保右下角 Disable configuration for user extensions **没勾选**。
%time 和 %%time 的行为差异必须分清
%time 只测单行,%%time 测整个 cell —— 但它们都只在当前执行时显示一次结果,不会持久记录。而且 %%time 必须写在 cell 第一行,后面不能跟代码;%time 则可插在任意行前。
容易踩的坑:
-
%%time包含所有语句,包括 import、变量定义、循环体,但不包含 cell 之前的 state(比如前面 cell 已经加载的模块) - GPU 代码(如 PyTorch/CUDA)用
%%time会严重低估真实耗时,因为 CUDA 是异步提交。必须加torch.cuda.synchronize()或cupy.cuda.Device().synchronize()才准 - 如果 cell 输出被
;抑制,%%time的耗时信息仍会打印在输出区最上方,不会消失
%%timeit 更适合对比算法性能
当你想判断「A 写法 vs B 写法哪个快」,别信单次 %%time,用 %%timeit。它自动跑多轮、剔除异常值、给均值和标准差,比如:
%%timeit -n 10 -r 5 x = [i**2 for i in range(10000)]
意思是:每轮跑 10 次,共跑 5 轮,最后报告「单次平均耗时 ± 标准差」。注意:%%timeit 默认会忽略第一轮(预热),且不计入 setup 代码(比如 import)—— 如果 setup 很重,得用 -s 参数单独指定。
关键限制:
-
%%timeit不支持交互式变量引用(比如依赖上个 cell 定义的df),所有依赖必须写在当前 cell 里 - 它会反复执行整个 cell,所以不能用于有副作用的操作(如写文件、改全局状态、调 API)
- 默认最大尝试次数是 1000 万次,太慢的代码会被提前中止,可加
-o查看详细配置
自定义计时器要小心内核状态污染
如果扩展和 magic 都不够用(比如想跨 cell 累计耗时、或打点多个阶段),可以手写 time.perf_counter()。但别直接用 time.time() —— 它受系统时钟调整影响,perf_counter() 才是高精度单调时钟。
示例:
import time
start = time.perf_counter()
# your code here
end = time.perf_counter()
print(f"耗时: {end - start:.4f}s")
真正容易被忽略的是:Jupyter kernel 是持续运行的,变量不会自动清理。如果你在不同 cell 里重复定义 start,旧值可能被意外复用。更安全的做法是加前缀或用字典存:
- 每次用独立变量名:
start_load_data,start_train - 或者统一用
timing_log = {},配合timing_log['load'] = time.perf_counter() - 千万别在函数里用
global start—— kernel 重启后变量丢失,但函数还在,会报NameError











