代码分析能够评估各部分代码的时间消耗,即进行时间复杂度分析。通过这一过程,我们可以识别影响整体运行效率的关键部分,从而更高效地利用底层计算资源。此外,代码分析也可用于评估内存使用情况,即空间复杂度,以优化内存管理并提升其使用效率。本文主要关注时间复杂度分析的内容。
Python默认提供了两个实用的性能分析库:cProfile和profile。它们能够按函数维度输出性能数据,但无法展示函数内部逐行的执行详情。而Python公开库line_profiler弥补了这一不足,使我们能够清晰查看代码中每一行的执行耗时。
line_profiler是Python中一款强大的逐行性能分析工具,能够帮助开发者精确定位代码中的性能瓶颈。对于初学者而言,该工具在代码优化过程中具有重要价值。line_profiler官方开源仓库见:line_profiler-github,官方文档见:line_profiler-doc。

本文将提供一份简单易用的line_profiler教程,重点讲解其在代码中的使用方法;命令行端的使用方式,可参考:Line by Line Profiling of Python Code。line_profiler安装命令如下:
pip install line_profiler
python
import line_profiler
# 查看line_profiler版本
line_profiler.__version__
'4.1.3'
目录
- [1 使用入门](#1 使用入门)
- [1.1 基础使用](#1.1 基础使用)
- [1.2 进阶使用](#1.2 进阶使用)
- [2 参考](#2 参考)
1 使用入门
1.1 基础使用
简单调用
以下代码为示例耗时代码,它会经过不同时长的休眠,随后生成大量随机数并计算其平均值。
python
import time
import math
import random
def power_sum(a, b):
"""计算a到b的平方和"""
return sum(math.pow(x, 2) for x in range(a, b))
def calculate(sleep_time):
time.sleep(sleep_time)
# 随机范围计算平方和的平均值
start = 0
end = 1000000
total = power_sum(start, end)
return total / (end - start)
def test_func():
calculate(sleep_time=1)
calculate(sleep_time=3)
calculate(sleep_time=5)
使用line_profiler分析函数运行时间,首先创建一个LineProfiler对象,再通过调用该实例并传入目标函数,生成一个分析器。执行该分析器即可自动运行目标函数。函数执行完成后,调用分析器实例的print_stats方法即可输出分析结果。注意:直接调用print_stats仅会显示目标函数中顶层代码的运行时间。
python
from line_profiler import LineProfiler
lprofiler = LineProfiler()
lp_wrapper = lprofiler(test_func)
lp_wrapper()
lprofiler.print_stats()
Timer unit: 1e-09 s
Total time: 9.92798 s
File: /tmp/ipykernel_14853/1826372064.py
Function: test_func at line 17
Line # Hits Time Per Hit % Time Line Contents
==============================================================
17 def test_func():
18 1 1242792669.0 1e+09 12.5 calculate(sleep_time=1)
19 1 3340978413.0 3e+09 33.7 calculate(sleep_time=3)
20 1 5344205767.0 5e+09 53.8 calculate(sleep_time=5)
print_stats输出的各列含义如下:
-
Timer unit
时间单位(默认自动根据代码执行速度调整),常见的有秒(s)、毫秒(ms,1e-3 秒)、微秒(us,1e-6 秒)、纳秒(ns,1e-9 秒)
-
Total time
整个函数的总执行时间
-
Line #
代码行号
-
Hits
该行代码的执行次数(循环体通常次数较多,函数定义、return等语句通常为1次)
-
Time
该行代码的累计执行时间(单位同Timer unit),包含该行调用的所有函数耗时
-
Per Hit
每次执行该行代码的平均时间,计算公式为Time / Hits
-
% Time
该行耗时占函数总时间的百分比,是识别性能瓶颈的关键指标,需重点关注占比较高的行,循环体通常是性能瓶颈所在
-
Line Contents
代码内容本身
深层调用
使用line_profiler分析函数运行时间时,如需同时分析其调用的下一层或更深层函数,则需在创建LineProfiler对象时明确指定所有待追踪的目标函数及其子函数。此时,line_profiler会为每个被监控的函数分别生成分析报告,且父函数的耗时统计中将包含所有子函数的执行时间。
python
from line_profiler import LineProfiler
lprofiler = LineProfiler()
lprofiler.add_function(calculate)
lprofiler.add_function(power_sum)
lp_wrapper = lprofiler(test_func)
lp_wrapper()
# 追踪的函数列表
lprofiler.functions
# output_unit:设置输出单位,可选值,1(秒),1e-3(毫秒),1e-6(微秒)
# details:是否显示详细的行级分析信息,默认是
# summarize:是否对结果进行汇总,默认否
# rich:是否使用富文本格式输出,默认否
lprofiler.print_stats(output_unit=1e-3, details=True, summarize=False, rich=False)
Timer unit: 0.001 s
Total time: 0.756163 s
File: /tmp/ipykernel_14853/1826372064.py
Function: power_sum at line 5
Line # Hits Time Per Hit % Time Line Contents
==============================================================
5 def power_sum(a, b):
6 """计算a到b的平方和"""
7 3 756.2 252.1 100.0 return sum(math.pow(x, 2) for x in range(a, b))
Total time: 9.76247 s
File: /tmp/ipykernel_14853/1826372064.py
Function: calculate at line 9
Line # Hits Time Per Hit % Time Line Contents
==============================================================
9 def calculate(sleep_time):
10 3 9006.3 3002.1 92.3 time.sleep(sleep_time)
11 # 随机范围计算平方和的平均值
12 3 0.0 0.0 0.0 start = 0
13 3 0.0 0.0 0.0 end = 1000000
14 3 756.2 252.1 7.7 total = power_sum(start, end)
15 3 0.0 0.0 0.0 return total / (end - start)
Total time: 9.76259 s
File: /tmp/ipykernel_14853/1826372064.py
Function: test_func at line 17
Line # Hits Time Per Hit % Time Line Contents
==============================================================
17 def test_func():
18 1 1250.6 1250.6 12.8 calculate(sleep_time=1)
19 1 3253.7 3253.7 33.3 calculate(sleep_time=3)
20 1 5258.2 5258.2 53.9 calculate(sleep_time=5)
使用装饰器调用
这种以分析器实例作为装饰器的方式,实质上是一种语法糖。它将被装饰的函数包装为一个代理,使分析器能够监听每次调用,从而精确记录其每一行的执行情况。
python
from line_profiler import LineProfiler
# 创建性能分析器
lprofiler = LineProfiler()
# 使用装饰器标记要分析的函数
@lprofiler
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# 没有装饰器标记的函数
def run_analysis():
result = fibonacci(10)
print(f"Fibonacci result: {result}")
# 执行并获取分析结果
run_analysis()
lprofiler.print_stats()
Fibonacci result: 55
Timer unit: 1e-09 s
Total time: 0.000116988 s
File: /tmp/ipykernel_14853/2361721755.py
Function: fibonacci at line 7
Line # Hits Time Per Hit % Time Line Contents
==============================================================
7 @lprofiler
8 def fibonacci(n):
9 177 31407.0 177.4 26.8 if n <= 1:
10 89 10762.0 120.9 9.2 return n
11 88 74819.0 850.2 64.0 return fibonacci(n-1) + fibonacci(n-2)
分析类中的方法
使用line_profiler分析类方法时,需遵循特定流程:首先,必须将目标类的类方法在分析器中显式注册;其次,这些方法必须绑定到具体的类实例上。执行分析时,应通过分析器返回的包装器函数来触发,而非直接调用原始方法,以确保性能数据能被正确捕获。
python
from line_profiler import LineProfiler
class DataProcessor:
def __init__(self, data):
self.data = data
self.cache = {}
def process_data(self):
"""处理数据的主方法"""
return [self._process_single_item(item) for item in self.data]
def _process_single_item(self, item):
"""处理单个数据项"""
if item not in self.cache:
self.cache[item] = self._complex_calculation(item)
return self.cache[item]
def _complex_calculation(self, x):
"""模拟复杂计算"""
return sum((x + i) **2 for i in range(100))
# 初始化并分析
processor = DataProcessor(list(range(100)))
lprofiler = LineProfiler(
processor.process_data,
processor._process_single_item,
processor._complex_calculation
)
lp_wrapper = lprofiler(processor.process_data)
results = lp_wrapper()
print(f"Processed {len(results)} items")
lprofiler.print_stats()
Processed 100 items
Timer unit: 1e-09 s
Total time: 0.00321407 s
File: /tmp/ipykernel_14853/2028146768.py
Function: process_data at line 8
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 def process_data(self):
9 """处理数据的主方法"""
10 1 3214073.0 3e+06 100.0 return [self._process_single_item(item) for item in self.data]
Total time: 0.00308825 s
File: /tmp/ipykernel_14853/2028146768.py
Function: _process_single_item at line 12
Line # Hits Time Per Hit % Time Line Contents
==============================================================
12 def _process_single_item(self, item):
13 """处理单个数据项"""
14 100 19064.0 190.6 0.6 if item not in self.cache:
15 100 3049582.0 30495.8 98.7 self.cache[item] = self._complex_calculation(item)
16 100 19599.0 196.0 0.6 return self.cache[item]
Total time: 0.00296471 s
File: /tmp/ipykernel_14853/2028146768.py
Function: _complex_calculation at line 18
Line # Hits Time Per Hit % Time Line Contents
==============================================================
18 def _complex_calculation(self, x):
19 """模拟复杂计算"""
20 100 2964705.0 29647.0 100.0 return sum((x + i) **2 for i in range(100))
使用上下文管理器控制分析范围
python
from line_profiler import LineProfiler
def expensive_operation(n):
"""昂贵的操作"""
result = 0
for i in range(n):
# 模拟CPU密集型工作
for j in range(1000):
result += (i * j) % (n + 1)
return result
def quick_operation(data):
"""快速操作"""
return [x * 2 for x in data]
lprofiler = LineProfiler(expensive_operation) # 直接在初始化时指定要分析的函数
# 使用with语句启动性能分析上下文,仅对该上下文中的代码进行分析
with lprofiler:
result1 = expensive_operation(1000)
print(f"Expensive operation result: {result1}")
# 不分析的快速操作
print(f"Quick operation result: {quick_operation(range(10))}")
lprofiler.print_stats()
Expensive operation result: 497084589
Quick operation result: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Timer unit: 1e-09 s
Total time: 0.379707 s
File: /tmp/ipykernel_14853/795522087.py
Function: expensive_operation at line 3
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def expensive_operation(n):
4 """昂贵的操作"""
5 1 512.0 512.0 0.0 result = 0
6 1001 164615.0 164.5 0.0 for i in range(n):
7 # 模拟CPU密集型工作
8 1001000 145481901.0 145.3 38.3 for j in range(1000):
9 1000000 234060120.0 234.1 61.6 result += (i * j) % (n + 1)
10 1 242.0 242.0 0.0 return result
手动控制调用与结果保存
在手动控制模式下,LineProfiler类的enable_by_count与disable_by_count方法分别用于启动和终止性能计数,显式调用二者可直接对中间代码段进行性能分析;因无需依赖包装函数,能更灵活地控制分析范围。
python
from line_profiler import LineProfiler
import time
import math
# 创建一个示例模块的函数
def process_data(n):
"""模拟一个需要性能分析的函数"""
result = 0
# 模拟一些计算密集型操作
for i in range(n):
result += math.sqrt(i) * math.sin(i)
time.sleep(0.1)
return result
# 创建分析器实例
lprofiler = LineProfiler()
# 添加要分析的函数
lprofiler.add_function(process_data)
# 运行分析
print("开始性能分析...")
lprofiler.enable_by_count()
result = process_data(100)
lprofiler.disable_by_count()
print(f"函数结果: {result}")
# 使用with open保存结果到文件
with open('profile_results.txt', 'w', encoding='utf-8') as f:
lprofiler.print_stats(f)
print("\n=== 控制台输出分析结果 ===")
lprofiler.print_stats()
开始性能分析...
函数结果: -4.787834159955427
=== 控制台输出分析结果 ===
Timer unit: 1e-09 s
Total time: 0.100183 s
File: /tmp/ipykernel_14853/4142849225.py
Function: process_data at line 6
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 def process_data(n):
7 """模拟一个需要性能分析的函数"""
8 1 388.0 388.0 0.0 result = 0
9
10 # 模拟一些计算密集型操作
11 101 16515.0 163.5 0.0 for i in range(n):
12 100 51063.0 510.6 0.1 result += math.sqrt(i) * math.sin(i)
13
14 1 100114931.0 1e+08 99.9 time.sleep(0.1)
15 1 367.0 367.0 0.0 return result
1.2 进阶使用
内存与运行时间综合分析
以下代码展示了一种整合内存与运行时间的性能分析示例,通过memory_profiler库监控内存消耗,借助LineProfiler分析代码执行时间。
python
from line_profiler import LineProfiler
# pip install memory_profiler
from memory_profiler import profile as memory_profile
class DataProcessor:
# 监控该函数的内存运行情况
@memory_profile
def process_large_dataset(self, data):
return list(map(self.transform_item, data))
def transform_item(self, item):
return dict(x=item** 2, y=item * 3)
processor = DataProcessor()
line_profiler = LineProfiler()
# 该函数被memory_profile装饰器包装,需要直接访问其原始未包装版本
line_profiler.add_function(processor.process_large_dataset.__wrapped__)
line_profiler.add_function(processor.transform_item)
test_data = list(range(100000))
line_profiler.enable()
result = processor.process_large_dataset(test_data)
line_profiler.disable()
line_profiler.print_stats()
Line # Mem usage Increment Occurrences Line Contents
=============================================================
5 54.4 MiB 54.4 MiB 1 @memory_profile
6 def process_large_dataset(self, data):
7 85.1 MiB 30.7 MiB 1 return list(map(self.transform_item, data))
Timer unit: 1e-07 s
Total time: 0.21659 s
File: /tmp/ipykernel_14853/4261432908.py
Function: process_large_dataset at line 5
Line # Hits Time Per Hit % Time Line Contents
==============================================================
5 @memory_profile
6 def process_large_dataset(self, data):
7 1 2165899.0 2.17e+06 100.0 return list(map(self.transform_item, data))
Total time: 0.0929938 s
File: /tmp/ipykernel_14853/4261432908.py
Function: transform_item at line 9
Line # Hits Time Per Hit % Time Line Contents
==============================================================
9 def transform_item(self, item):
10 100000 929938.0 9.3 100.0 return dict(x=item** 2, y=item * 3)
函数在多线程中的执行
line_profiler本身不区分线程,它会记录所有线程中被包装函数的执行情况。这意味着如果在多个线程中运行同一个被分析的函数,分析结果会将所有线程中该函数的执行数据汇总在一起。
python
from line_profiler import LineProfiler
import concurrent.futures
import time
# 要分析的函数
def process_data(data):
result = []
# 模拟一些处理操作
for i in range(data):
result.append(i ** 2)
time.sleep(0.001) # 模拟耗时操作
return sum(result)
lprofiler = LineProfiler()
lp_wrapper = lprofiler(process_data)
data_list = [100, 200, 150, 300, 250]
# 使用线程池并行处理
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
# 提交任务时使用被包装的函数
futures = [executor.submit(lp_wrapper, data) for data in data_list]
results = [f.result() for f in futures]
print("处理结果:", results)
# 打印分析结果
print("\n=== 性能分析结果 ===")
lprofiler.print_stats()
处理结果: [328350, 2646700, 1113775, 8955050, 5177125]
=== 性能分析结果 ===
Timer unit: 1e-09 s
Total time: 1.05548 s
File: /tmp/ipykernel_14853/3950115841.py
Function: process_data at line 6
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 def process_data(data):
7 5 3495.0 699.0 0.0 result = []
8 # 模拟一些处理操作
9 1005 250388.0 249.1 0.0 for i in range(data):
10 1000 686896.0 686.9 0.1 result.append(i ** 2)
11 1000 1054522882.0 1e+06 99.9 time.sleep(0.001) # 模拟耗时操作
12 5 21281.0 4256.2 0.0 return sum(result)
适用范围
Line_profiler更适合分析执行时间远大于工具自身开销的代码,而不适用于过于简单的函数。当函数单次执行时间极短时,性能分析工具的追踪开销可能显著超过函数本身的运行时间,导致分析结果受到干扰,无法准确反映代码的真实性能。例如,对同一简单函数进行分析后,其执行时间反而比未分析时更长。反之,若整体执行时间足够长,性能分析工具的开销便可忽略不计,从而准确揭示代码的性能瓶颈。
python
from line_profiler import LineProfiler
import numpy as np # 用numpy生成测试数据,放大工作量差异
# 微小操作函数(未被性能分析)
def add_one(x):
return x + 1 # 单次执行时间极短(纳秒级)
# 微小操作函数(将被性能分析)
def add_one_profile(x):
return x + 1 # 单次执行时间极短(纳秒级)
# 定义分析函数
def profile_add_one():
# 循环调用,强制让微小操作累积执行
for _ in range(1000000):
add_one(1)
add_one_profile(1)
# 测试数据
lprofiler = LineProfiler()
lprofiler.add_function(add_one_profile)
lp_wrapper = lprofiler(profile_add_one)
lp_wrapper()
# 将时间单位设置为毫秒
# add_one_profile比add_one耗时
lprofiler.print_stats(output_unit=1e-3)
Timer unit: 0.001 s
Total time: 0.114556 s
File: /tmp/ipykernel_14853/4261432908.py
Function: add_one_profile at line 8
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 def add_one_profile(x):
9 1000000 114.6 0.0 100.0 return x + 1 # 单次执行时间极短(纳秒级)
Total time: 1.14907 s
File: /tmp/ipykernel_14853/4261432908.py
Function: profile_add_one at line 12
Line # Hits Time Per Hit % Time Line Contents
==============================================================
12 def profile_add_one():
13 # 循环调用多次add_one(强制让微小操作累积执行)
14 1000001 148.9 0.0 13.0 for _ in range(1000000):
15 1000000 286.9 0.0 25.0 add_one(1)
16 1000000 713.3 0.0 62.1 add_one_profile(1)