器→工具, 编程语言

Python标准库之性能优化

钱魏Way · · 2 次浏览

timeit:计时小段代码的执行时间

timeit 是 Python 标准库中的一个模块,用于测量小段代码的执行时间。它提供了一种精确、可靠的方式来对代码的性能进行基准测试,避免了诸如系统时间变化和其他外部因素的影响。timeit 可以用于比较不同实现方式的性能,以确定哪种方式更高效。

主要功能

  • 精确测量代码执行时间:timeit 使用高精度的计时器来测量代码的执行时间,默认情况下,它会多次执行代码并返回平均时间,以获得更准确的结果。
  • 简易使用:该模块提供了简单的接口,可以直接在命令行或者通过 Python 脚本调用,非常方便。
  • 避免常见误差:timeit 避免了使用 time 模块时可能遇到的许多误差源,例如时间切片和 CPU 负载变化。

特性与优势

  • 高精度:timeit 使用高精度的时钟(在 Windows 上使用perf_counter(),在 Unix 上使用 time.time()),可以精确到微秒级别。
  • 多次执行,减少偶然误差:timeit 默认多次执行测试代码,并计算平均时间,以减少由于偶然因素导致的误差。
  • 可定制:你可以控制代码执行的次数(number 参数)以及使用的全局和局部命名空间。
  • 内置与跨平台支持:timeit 是 Python 标准库的一部分,无需额外安装,并且在所有支持 Python 的平台上都可以使用。

基本用法

timeit 可以直接从命令行使用,也可以在脚本中调用。

命令行用法

你可以直接在命令行中使用 timeit 模块。例如,要测量 for 循环的性能,可以运行以下命令:

python -m timeit "for i in range(100): pass"

这将会输出代码执行的平均时间。

脚本中使用

你也可以在 Python 脚本中使用 timeit 模块来测量代码块的执行时间:

import timeit

# 要测量的代码片段
code = """
result = []
for i in range(100):
    result.append(i)
"""

# 执行时间测量
execution_time = timeit.timeit(code, number=1000)
print(f"Execution time: {execution_time} seconds")

在这个示例中,timeit.timeit 会多次执行代码(number=1000 表示执行 1000 次),并返回总的执行时间。

使用 Timer 类

timeit 模块中的 Timer 类提供了更灵活的接口,允许你更精确地控制计时的过程:

import timeit

def test():
    result = []
    for i in range(100):
        result.append(i)

t = timeit.Timer("test()", "from __main__ import test")
print(t.timeit(number=1000))

在这个示例中,Timer 类的实例被用来测量 test 函数的执行时间。

测量单个语句

你还可以测量单个语句的执行时间:

import timeit

execution_time = timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
print(f"Execution time: {execution_time} seconds")

这里测量了将一系列数字转换为字符串并用连字符 – 连接的代码片段的执行时间。

比较不同实现方式

timeit 还可以用于比较不同实现方式的性能。例如,比较使用 for 循环和使用列表推导式生成列表的性能:

import timeit

# 使用 for 循环
for_loop_time = timeit.timeit("""
result = []
for i in range(100):
    result.append(i)
""", number=10000)

# 使用列表推导式
list_comp_time = timeit.timeit("result = [i for i in range(100)]", number=10000)

print(f"For loop time: {for_loop_time} seconds")
print(f"List comprehension time: {list_comp_time} seconds")

通过这个对比,你可以看到哪种方式执行更快。

Profile与cProfile:性能分析器

profile和cProfile是Python中用于性能分析(profiling)的模块,它们的主要区别在于实现方式和性能:

  • profile: 这个模块是用纯Python实现的。因此,它的代码比较容易阅读和理解,可以作为学习性能分析器实现原理的一个例子。
  • cProfile: 这是一个用C语言实现的模块,属于Python标准库的一部分。因为是用C实现的,所以性能要比profile好得多。

在使用上,两者的接口和功能是相似的,通常你可以用类似的方式来使用它们。为了最佳性能分析效果,通常建议使用cProfile。

主要功能

  • 函数级别的性能分析:profile 模块能够记录程序中各个函数的调用次数、调用时间以及在每次调用中花费的时间。
  • 可视化与分析:分析结果可以保存到文件中,之后可以使用其他工具(如 pstats 模块)进行更详细的分析和可视化。
  • 全面的性能测量:profile 模块适合对整个程序的执行进行全面的性能分析,特别是在较大的应用程序中,用于查找最消耗时间的函数。

基本用法

命令行用法

你可以通过命令行直接使用 cProfile 对 Python 脚本进行性能分析。例如,对名为 script.py 的脚本进行分析:

python -m cProfile script.py

这将输出每个函数的调用次数、总时间、每次调用的平均时间等信息。

在代码中使用

你可以在 Python 代码中使用 cProfile 进行性能分析。例如:

import cProfile

def my_function():
    total = 0
    for i in range(10000):
        total += i
    return total

cProfile.run('my_function()')

这段代码会分析 my_function 的执行时间,并打印性能分析结果。

生成并保存分析报告

你可以将分析结果保存到文件中,之后使用 pstats 模块进行进一步的分析:

import cProfile

def my_function():
    total = 0
    for i in range(10000):
        total += i
    return total

cProfile.run('my_function()', 'output.prof')

这会将性能分析结果保存到 output.prof 文件中。

使用 pstats 进行详细分析

cProfile 生成的 .prof 文件可以使用 pstats 模块进行更详细的分析。例如:

import pstats

p = pstats.Stats('output.prof')
p.sort_stats('cumulative').print_stats(10)

使用 Profile 类

cProfile 模块提供了 Profile 类,用于创建性能分析器对象,从而更灵活地控制性能分析过程:

import cProfile

def my_function():
    total = 0
    for i in range(10000):
        total += i
    return total

profiler = cProfile.Profile()
profiler.enable()
my_function()
profiler.disable()
profiler.print_stats()

通过这种方式,你可以手动启动和停止性能分析,并在结束时打印分析结果。

主要功能和方法

  • run(statement, filename=None, sort=-1):
    • 运行给定的语句并进行性能分析。结果可以保存到指定的文件中。
    • statement:要分析的 Python 语句。
    • filename:保存结果的文件名。如果为 None,则输出到控制台。
    • sort:结果排序方式,可以是 time、cumulative 等。
  • Profile():
    • 创建 Profile 对象,用于控制性能分析过程。
  • enable():
    • 启动性能分析。
  • disable():
    • 停止性能分析。
  • print_stats(sort=-1):
    • 打印性能分析结果。可以根据 sort 参数进行排序,常见的排序方式有 time、cumulative 等。

pstats:分析器输出格式化

pstats 是 Python 标准库中的一个模块,用于分析和格式化由 cProfile 和 profile 模块生成的性能分析报告。pstats 提供了一些方法,可以对性能数据进行排序、过滤和输出,使得开发者能够更加方便地理解和分析性能瓶颈。

主要功能

  • 加载和操作性能数据:通过 pstats,你可以加载之前由 cProfile 或 profile 生成的性能数据文件,并对这些数据进行操作和分析。
  • 结果排序和过滤:提供多种排序和过滤选项,帮助开发者查看最重要的性能数据。
  • 格式化输出:支持多种格式化输出方式,可以打印出详细的性能统计信息。

特性与优势

  • 详细的性能数据分析:pstats 可以详细分析性能数据,包括每个函数的调用次数、总时间、累计时间等。
  • 灵活的排序与过滤:提供多种排序和过滤选项,帮助开发者快速找到性能瓶颈。
  • 易于使用:pstats 提供了简单的接口来打印和分析性能数据,使得性能分析工作变得更加便捷。

加载性能数据

pstats 可以加载 cProfile 或 profile 生成的 .prof 文件,以进行进一步的分析:

import pstats

# 加载性能数据文件
stats = pstats.Stats('output.prof')

排序和过滤

pstats 提供了多种排序选项,可以按不同的标准对性能数据进行排序,以便更好地分析程序的性能瓶颈。常见的排序标准包括:

  • ‘time’:按每个函数的总时间排序。
  • ‘cumulative’:按每个函数的累计时间排序。
  • ‘calls’:按调用次数排序。

例如,要按累计时间排序并打印前 10 个函数的性能数据:

import pstats

stats = pstats.Stats('output.prof')
stats.sort_stats('cumulative').print_stats(10)

打印性能数据

pstats 提供了多种打印方法,允许开发者以不同的方式查看性能数据:

  • print_stats([*args]):打印性能统计信息。args 可以是整数(表示打印的函数数量)或字符串(表示过滤条件)。
  • print_callees(funcname):打印指定函数调用的所有子函数的性能数据。
  • print_callers(funcname):打印指定函数的所有调用者的性能数据。

例如,要打印调用次数最多的 20 个函数的性能数据:

import pstats

stats = pstats.Stats('output.prof')
stats.sort_stats('calls').print_stats(20)

过滤和分组

可以使用 pstats 对函数调用进行过滤,以便只查看特定模块或函数的信息。例如,查看某个模块中函数的性能数据:

import pstats

stats = pstats.Stats('output.prof')
stats.sort_stats('time').print_stats('my_module')

主要方法和功能

  • Stats(filename):创建一个 Stats 对象,用于加载性能数据文件。
  • sort_stats(*args):按照指定的标准对性能数据进行排序。*args 是排序标准,如 ‘time’、’cumulative’ 等。
  • print_stats(*args):打印排序后的性能数据。*args 可以是函数的个数或过滤条件。
  • print_callees(funcname):打印指定函数的所有子函数的性能数据。
  • print_callers(funcname):打印指定函数的所有调用者的性能数据。

发表回复

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