ZetCode

Python time.perf_counter_ns 函数

上次修改时间:2025 年 4 月 11 日

本综合指南探讨 Python 的 time.perf_counter_ns 函数,该函数返回以纳秒为单位的高分辨率性能计数器。我们将介绍高精度计时、基准测试和实用示例。

基本定义

time.perf_counter_ns 函数返回一个整数,表示以纳秒为单位的性能计数器。它为短时间测量提供最高可用的分辨率计时器。

主要特征:纳秒分辨率、单调递增(始终增加)、不受系统时钟更改的影响,非常适合基准测试和性能分析。计数器的参考点未定义(只有差异才有意义)。

基本性能测量

time.perf_counter_ns 最简单的用法是以纳秒精度测量代码执行时间。此示例显示了基本用法。

basic_timing.py
import time

def calculate_sum(n):
    return sum(range(n))

# Start timer
start = time.perf_counter_ns()

# Execute function
result = calculate_sum(1000000)

# End timer
end = time.perf_counter_ns()

# Calculate duration in nanoseconds
duration = end - start
print(f"Calculation took {duration} ns")
print(f"Result: {result}")

此示例演示如何以纳秒精度测量函数的执行时间。持续时间通过从结束时间戳中减去开始时间戳来计算。

请注意,perf_counter_ns 的绝对值没有意义 - 只有测量值之间的差异才有用。

比较 perf_counter_ns 与 perf_counter

此示例将 perf_counter_ns 与其浮点对应项 perf_counter 进行比较,以显示精度差异。

comparison.py
import time

def empty_loop(n):
    for _ in range(n):
        pass

iterations = 1000000

# Using perf_counter (float seconds)
start = time.perf_counter()
empty_loop(iterations)
end = time.perf_counter()
float_duration = end - start

# Using perf_counter_ns (integer nanoseconds)
start_ns = time.perf_counter_ns()
empty_loop(iterations)
end_ns = time.perf_counter_ns()
ns_duration = end_ns - start_ns

print(f"perf_counter: {float_duration:.9f} sec")
print(f"perf_counter_ns: {ns_duration} ns")
print(f"Converted ns: {ns_duration / 1e9:.9f} sec")

perf_counter_ns 提供整数纳秒,而 perf_counter 返回浮点秒。ns 版本避免了非常短的时间间隔的浮点精度问题。

对于大多数计时需求,两者都有效,但 perf_counter_ns 更适合极其精确的测量。

测量函数调用开销

perf_counter_ns 可以测量非常小的时间间隔,例如函数调用开销。此示例演示了此功能。

function_overhead.py
import time

def empty_function():
    pass

# Measure single call overhead
start = time.perf_counter_ns()
empty_function()
end = time.perf_counter_ns()
single_call = end - start

# Measure average overhead over many calls
trials = 1000000
start = time.perf_counter_ns()
for _ in range(trials):
    empty_function()
end = time.perf_counter_ns()
avg_call = (end - start) / trials

print(f"Single call overhead: {single_call} ns")
print(f"Average call overhead: {avg_call:.2f} ns")

这测量了调用空函数所花费的时间。单次调用测量显示了原始开销,而平均版本降低了噪声。

当优化性能关键型代码(每一纳秒都很重要)时,这种精确的测量非常有用。

基准测试不同的实现

此示例使用 perf_counter_ns 来比较相同算法的不同 Python 实现的性能。

benchmarking.py
import time

def sum_with_for(n):
    total = 0
    for i in range(n):
        total += i
    return total

def sum_with_builtin(n):
    return sum(range(n))

def measure(func, n, trials=100):
    start = time.perf_counter_ns()
    for _ in range(trials):
        func(n)
    end = time.perf_counter_ns()
    return (end - start) / trials

n = 10000
trials = 100

for_loop_time = measure(sum_with_for, n, trials)
builtin_time = measure(sum_with_builtin, n, trials)

print(f"For loop sum: {for_loop_time:.0f} ns per call")
print(f"Builtin sum: {builtin_time:.0f} ns per call")
print(f"Builtin is {for_loop_time/builtin_time:.1f}x faster")

该示例测量了两种对数字求和的方法:使用手动 for 循环与 Python 的内置 sum。结果显示了它们的相对性能。

多次试验的平均可以减少测量噪声,并提供更可靠的实现之间的比较。

精度限制和时钟分辨率

此示例通过测量最小可检测间隔来演示 perf_counter_ns 的实际分辨率限制。

resolution_test.py
import time

def measure_resolution():
    # Measure smallest detectable time difference
    min_diff = float('inf')
    for _ in range(1000):
        t1 = time.perf_counter_ns()
        t2 = time.perf_counter_ns()
        if t2 > t1:
            diff = t2 - t1
            if diff < min_diff:
                min_diff = diff
    return min_diff

resolution = measure_resolution()
print(f"Smallest detectable interval: {resolution} ns")

# Verify with time.sleep(0)
sleep_start = time.perf_counter_ns()
time.sleep(0)
sleep_end = time.perf_counter_ns()
print(f"time.sleep(0) duration: {sleep_end - sleep_start} ns")

第一部分测量计时器可以检测到的最小时间差。第二部分表明,即使 sleep(0) 也需要可测量的时间,因为 Python 的开销。

实际分辨率取决于硬件和操作系统。现代系统通常具有纳秒分辨率计时器,但开销可能会限制实际使用。

使用 perf_counter_ns 进行微基准测试

此示例演示如何使用 perf_counter_ns 创建微基准装饰器,以实现精确的函数计时。

microbenchmark.py
import time
from functools import wraps

def benchmark(n_trials=1000, warmup=100):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Warmup phase (avoid JIT/cache effects)
            for _ in range(warmup):
                func(*args, **kwargs)
            
            # Measurement phase
            total_ns = 0
            for _ in range(n_trials):
                start = time.perf_counter_ns()
                func(*args, **kwargs)
                end = time.perf_counter_ns()
                total_ns += (end - start)
            
            avg_ns = total_ns / n_trials
            print(f"{func.__name__}: {avg_ns:.1f} ns per call "
                  f"(over {n_trials} trials)")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@benchmark(n_trials=10000, warmup=1000)
def list_comprehension(n):
    return [i*i for i in range(n)]

@benchmark(n_trials=10000, warmup=1000)
def manual_loop(n):
    result = []
    for i in range(n):
        result.append(i*i)
    return result

list_comprehension(100)
manual_loop(100)

该装饰器处理预热运行以避免启动影响,然后测量多次试验的平均执行时间。这提供了可靠的微基准。

该示例比较了列表推导与手动循环性能,展示了如何正确地对小的代码差异进行基准测试。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程