Skip to content

DPDK 时间、时钟周期与性能测试入门教程

用最简单的方式掌握 DPDK 中的高精度计时与性能测量


开始之前:为什么需要高精度计时?

场景一:网络数据包处理

想象你正在处理 10Gbps 的网络流量:

  • 每秒约 1480 万个 64 字节的数据包
  • 每个数据包的处理时间只有约 67 纳秒

用传统的 gettimeofday() 测量?精度只有微秒级,根本不够用!

场景二:性能优化

你写了两个版本的代码,想知道哪个更快:

c
// 版本A
for (int i = 0; i < 1000000; i++) {
    process_packet_v1(pkt);
}

// 版本B
for (int i = 0; i < 1000000; i++) {
    process_packet_v2(pkt);
}

如何精确测量它们的性能差异?

答案:使用 DPDK 的高精度时钟周期计数!


第一课:理解时钟周期(Cycles)

1.1 什么是时钟周期?

CPU 时钟周期是计算机中最小的时间单位。

打个比喻:

心跳        ❤️ ❤️ ❤️ ❤️ ❤️     →  人类的"时钟",约 1秒/次
秒表        ⏱️                  →  秒级精度
手表        ⌚                  →  毫秒级精度
CPU时钟     ⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡⚡  →  纳秒级精度!

现代 CPU 的时钟频率通常是 2-4 GHz,意味着:

  • 1 GHz = 10亿次/秒
  • 3 GHz 的 CPU 每纳秒跳动 3 次

1.2 DPDK 如何读取时钟周期?

DPDK 使用 CPU 的 TSC(Time Stamp Counter) 寄存器:

┌─────────────────────────────────────────────────────────┐
│                      CPU                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │              TSC 寄存器                          │   │
│  │    一个 64 位计数器,从开机就一直在计数          │   │
│  │    每个时钟周期 +1                               │   │
│  │                                                  │   │
│  │    当前值: 1234567890123456                      │   │
│  └─────────────────────────────────────────────────┘   │
│                         ↓                               │
│              rte_get_timer_cycles()                     │
│                    读取这个值                           │
└─────────────────────────────────────────────────────────┘

1.3 时钟周期 vs 实际时间

时钟周期(cycles)  ←→  实际时间(秒)

转换公式:
    时间(秒)= 时钟周期数 / 时钟频率

例如:
    频率 = 2.4 GHz = 2,400,000,000 Hz
    周期数 = 2,400,000,000
    时间 = 2,400,000,000 / 2,400,000,000 = 1 秒

第二课:核心 API 详解

2.1 获取时钟频率

c
uint64_t rte_get_timer_hz(void);

功能:返回每秒的时钟周期数(频率)

示例

c
uint64_t hz = rte_get_timer_hz();
printf("CPU 频率: %lu Hz\n", hz);
printf("即: %.2f GHz\n", hz / 1e9);

// 典型输出:
// CPU 频率: 2400000000 Hz
// 即: 2.40 GHz

常用时间单位转换

c
uint64_t hz = rte_get_timer_hz();

uint64_t one_second      = hz;           // 1 秒
uint64_t one_millisecond = hz / 1000;    // 1 毫秒
uint64_t one_microsecond = hz / 1000000; // 1 微秒
uint64_t half_second     = hz / 2;       // 0.5 秒

2.2 获取当前时钟周期

c
uint64_t rte_get_timer_cycles(void);

功能:返回当前的 TSC 计数值

示例

c
uint64_t start = rte_get_timer_cycles();

// ... 执行一些操作 ...

uint64_t end = rte_get_timer_cycles();
uint64_t elapsed = end - start;  // 消耗的时钟周期数

printf("消耗了 %lu 个时钟周期\n", elapsed);

2.3 精确延时

c
void rte_delay_us(unsigned us);           // 微秒级延时
void rte_delay_ms(unsigned ms);           // 毫秒级延时
void rte_delay_us_block(unsigned us);     // 阻塞式微秒延时

注意:这些是忙等待(busy-wait),会占用 CPU!

c
// 延时 100 微秒
rte_delay_us(100);

// 延时 10 毫秒
rte_delay_ms(10);

2.4 API 速查表

API功能返回值使用场景
rte_get_timer_hz()获取时钟频率每秒周期数初始化时调用一次
rte_get_timer_cycles()获取当前周期数64位计数值测量时间点
rte_delay_us(n)延时 n 微秒精确等待
rte_delay_ms(n)延时 n 毫秒较长等待
rte_rdtsc()直接读取 TSC64位计数值最低开销读取
rte_rdtsc_precise()精确读取 TSC64位计数值需要严格顺序时

第三课:第一个计时程序

3.1 程序目标

创建一个程序,演示:

  1. 如何获取时钟频率
  2. 如何测量代码执行时间
  3. 如何将周期数转换为实际时间

3.2 完整代码

创建 time_demo.c

c
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <rte_eal.h>
#include <rte_cycles.h>

/* 将时钟周期转换为微秒 */
static inline double cycles_to_us(uint64_t cycles, uint64_t hz)
{
    return (double)cycles * 1000000.0 / hz;
}

/* 将时钟周期转换为纳秒 */
static inline double cycles_to_ns(uint64_t cycles, uint64_t hz)
{
    return (double)cycles * 1000000000.0 / hz;
}

/* 模拟一些工作 */
static void do_some_work(int iterations)
{
    volatile int sum = 0;
    for (int i = 0; i < iterations; i++) {
        sum += i;
    }
}

int main(int argc, char *argv[])
{
    int ret;
    uint64_t hz;
    uint64_t start, end, elapsed;

    /* 初始化 EAL */
    ret = rte_eal_init(argc, argv);
    if (ret < 0) {
        printf("EAL 初始化失败\n");
        return -1;
    }

    printf("\n");
    printf("╔══════════════════════════════════════════════════════╗\n");
    printf("║     DPDK 时间与时钟周期演示                          ║\n");
    printf("╚══════════════════════════════════════════════════════╝\n\n");

    /* ===== 第1部分:获取时钟频率 ===== */
    printf("【第1部分】获取 CPU 时钟频率\n");
    printf("─────────────────────────────────────────────\n");

    hz = rte_get_timer_hz();

    printf("  时钟频率: %lu Hz\n", hz);
    printf("  即: %.3f GHz\n", hz / 1e9);
    printf("  每个时钟周期: %.3f 纳秒\n\n", 1e9 / hz);

    /* 打印常用时间单位对应的周期数 */
    printf("  常用时间单位对应的周期数:\n");
    printf("  ┌──────────────┬────────────────────┐\n");
    printf("  │ 时间         │ 周期数              │\n");
    printf("  ├──────────────┼────────────────────┤\n");
    printf("  │ 1 秒         │ %lu\n", hz);
    printf("  │ 1 毫秒       │ %lu\n", hz / 1000);
    printf("  │ 1 微秒       │ %lu\n", hz / 1000000);
    printf("  │ 100 纳秒     │ %lu\n", hz / 10000000);
    printf("  └──────────────┴────────────────────┘\n\n");

    /* ===== 第2部分:测量代码执行时间 ===== */
    printf("【第2部分】测量代码执行时间\n");
    printf("─────────────────────────────────────────────\n");

    /* 测量简单循环 */
    printf("\n  实验1: 测量 1000 次循环\n");
    start = rte_get_timer_cycles();
    do_some_work(1000);
    end = rte_get_timer_cycles();
    elapsed = end - start;

    printf("    消耗周期数: %lu\n", elapsed);
    printf("    消耗时间: %.2f 微秒\n", cycles_to_us(elapsed, hz));
    printf("    消耗时间: %.0f 纳秒\n", cycles_to_ns(elapsed, hz));

    /* 测量更多循环 */
    printf("\n  实验2: 测量 100000 次循环\n");
    start = rte_get_timer_cycles();
    do_some_work(100000);
    end = rte_get_timer_cycles();
    elapsed = end - start;

    printf("    消耗周期数: %lu\n", elapsed);
    printf("    消耗时间: %.2f 微秒\n", cycles_to_us(elapsed, hz));

    /* 测量 DPDK 延时函数的精度 */
    printf("\n  实验3: 验证 rte_delay_us() 精度\n");
    printf("    请求延时 100 微秒...\n");
    start = rte_get_timer_cycles();
    rte_delay_us(100);
    end = rte_get_timer_cycles();
    elapsed = end - start;

    printf("    实际消耗: %.2f 微秒\n", cycles_to_us(elapsed, hz));
    printf("    误差: %.2f%%\n", (cycles_to_us(elapsed, hz) - 100.0) / 100.0 * 100);

    /* ===== 第3部分:测量函数调用开销 ===== */
    printf("\n【第3部分】测量 API 调用开销\n");
    printf("─────────────────────────────────────────────\n");

    /* 测量 rte_get_timer_cycles() 本身的开销 */
    uint64_t overhead_total = 0;
    int num_iterations = 1000000;

    for (int i = 0; i < num_iterations; i++) {
        start = rte_get_timer_cycles();
        end = rte_get_timer_cycles();
        overhead_total += (end - start);
    }

    double avg_overhead = (double)overhead_total / num_iterations;
    printf("  rte_get_timer_cycles() 调用开销:\n");
    printf("    平均周期数: %.1f cycles\n", avg_overhead);
    printf("    平均时间: %.1f 纳秒\n\n", avg_overhead * 1e9 / hz);

    /* ===== 第4部分:时间单位转换示例 ===== */
    printf("【第4部分】时间转换工具函数\n");
    printf("─────────────────────────────────────────────\n");
    printf("\n  转换示例:\n");

    uint64_t sample_cycles = 2400000;  // 假设 2.4M 周期
    printf("    %lu 周期 = %.2f 毫秒\n",
           sample_cycles, (double)sample_cycles * 1000 / hz);
    printf("    %lu 周期 = %.2f 微秒\n",
           sample_cycles, cycles_to_us(sample_cycles, hz));
    printf("    %lu 周期 = %.0f 纳秒\n\n",
           sample_cycles, cycles_to_ns(sample_cycles, hz));

    /* 清理 */
    rte_eal_cleanup();

    printf("╔══════════════════════════════════════════════════════╗\n");
    printf("║                   演示结束                            ║\n");
    printf("╚══════════════════════════════════════════════════════╝\n\n");

    return 0;
}

3.3 编译和运行

bash
# 编译(假设在项目的 build 目录下)
gcc -o time_demo time_demo.c $(pkg-config --cflags --libs libdpdk)

# 运行
sudo ./time_demo -l 0 --no-pci

3.4 预期输出

╔══════════════════════════════════════════════════════╗
║     DPDK 时间与时钟周期演示                          ║
╚══════════════════════════════════════════════════════╝

【第1部分】获取 CPU 时钟频率
─────────────────────────────────────────────
  时钟频率: 2400000000 Hz
  即: 2.400 GHz
  每个时钟周期: 0.417 纳秒

  常用时间单位对应的周期数:
  ┌──────────────┬────────────────────┐
  │ 时间         │ 周期数              │
  ├──────────────┼────────────────────┤
  │ 1 秒         │ 2400000000          │
  │ 1 毫秒       │ 2400000             │
  │ 1 微秒       │ 2400                │
  │ 100 纳秒     │ 240                 │
  └──────────────┴────────────────────┘

【第2部分】测量代码执行时间
─────────────────────────────────────────────
  ...

第四课:性能测试实战

4.1 性能测试的基本模式

c
/*
 * 性能测试三步曲:
 * 1. 记录开始时间
 * 2. 执行 N 次操作
 * 3. 记录结束时间,计算平均值
 */

uint64_t start, end, elapsed;
uint64_t hz = rte_get_timer_hz();
int iterations = 1000000;

start = rte_get_timer_cycles();

for (int i = 0; i < iterations; i++) {
    // 要测量的操作
    your_function();
}

end = rte_get_timer_cycles();
elapsed = end - start;

double total_us = (double)elapsed * 1e6 / hz;
double per_op_ns = (double)elapsed * 1e9 / hz / iterations;

printf("总耗时: %.2f 微秒\n", total_us);
printf("每次操作: %.2f 纳秒\n", per_op_ns);
printf("吞吐量: %.2f M ops/sec\n", iterations / total_us);

4.2 完整的性能测试框架

创建 benchmark.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <rte_eal.h>
#include <rte_cycles.h>
#include <rte_malloc.h>
#include <rte_memcpy.h>
#include <rte_ring.h>

/* 性能测试结果结构 */
struct benchmark_result {
    const char *name;           /* 测试名称 */
    uint64_t total_cycles;      /* 总周期数 */
    uint64_t iterations;        /* 迭代次数 */
    double total_time_us;       /* 总时间(微秒) */
    double per_op_ns;           /* 每次操作耗时(纳秒) */
    double ops_per_sec;         /* 每秒操作数 */
};

static uint64_t g_hz;

/* 打印测试结果 */
static void print_result(struct benchmark_result *result)
{
    printf("  ├─ 测试项: %s\n", result->name);
    printf("  │   ├─ 迭代次数: %lu\n", result->iterations);
    printf("  │   ├─ 总耗时: %.2f 微秒\n", result->total_time_us);
    printf("  │   ├─ 每次操作: %.2f 纳秒\n", result->per_op_ns);
    printf("  │   └─ 吞吐量: %.2f M ops/sec\n", result->ops_per_sec / 1e6);
    printf("  │\n");
}

/* 计算测试结果 */
static void calc_result(struct benchmark_result *result,
                        uint64_t start, uint64_t end,
                        uint64_t iterations)
{
    result->total_cycles = end - start;
    result->iterations = iterations;
    result->total_time_us = (double)result->total_cycles * 1e6 / g_hz;
    result->per_op_ns = (double)result->total_cycles * 1e9 / g_hz / iterations;
    result->ops_per_sec = (double)iterations * g_hz / result->total_cycles;
}

/* ============ 测试用例 ============ */

/* 测试1: 空操作(测量循环开销) */
static void bench_empty_loop(void)
{
    struct benchmark_result result = {.name = "空循环(基准开销)"};
    uint64_t start, end;
    uint64_t iterations = 100000000;  // 1亿次

    start = rte_get_timer_cycles();
    for (uint64_t i = 0; i < iterations; i++) {
        /* 什么都不做 */
        __asm__ volatile("" ::: "memory");  // 防止编译器优化掉循环
    }
    end = rte_get_timer_cycles();

    calc_result(&result, start, end, iterations);
    print_result(&result);
}

/* 测试2: 读取时钟周期 */
static void bench_rdtsc(void)
{
    struct benchmark_result result = {.name = "rte_get_timer_cycles()"};
    uint64_t start, end;
    uint64_t iterations = 10000000;  // 1000万次
    volatile uint64_t dummy;

    start = rte_get_timer_cycles();
    for (uint64_t i = 0; i < iterations; i++) {
        dummy = rte_get_timer_cycles();
    }
    end = rte_get_timer_cycles();
    (void)dummy;

    calc_result(&result, start, end, iterations);
    print_result(&result);
}

/* 测试3: 内存分配 */
static void bench_malloc(void)
{
    struct benchmark_result result = {.name = "rte_malloc/free (64字节)"};
    uint64_t start, end;
    uint64_t iterations = 1000000;  // 100万次

    start = rte_get_timer_cycles();
    for (uint64_t i = 0; i < iterations; i++) {
        void *ptr = rte_malloc(NULL, 64, 0);
        rte_free(ptr);
    }
    end = rte_get_timer_cycles();

    calc_result(&result, start, end, iterations);
    print_result(&result);
}

/* 测试4: 内存拷贝 */
static void bench_memcpy(void)
{
    struct benchmark_result result = {.name = "rte_memcpy (1KB)"};
    uint64_t start, end;
    uint64_t iterations = 10000000;  // 1000万次

    char *src = rte_malloc(NULL, 1024, 64);
    char *dst = rte_malloc(NULL, 1024, 64);
    memset(src, 'A', 1024);

    start = rte_get_timer_cycles();
    for (uint64_t i = 0; i < iterations; i++) {
        rte_memcpy(dst, src, 1024);
    }
    end = rte_get_timer_cycles();

    calc_result(&result, start, end, iterations);
    print_result(&result);

    /* 计算带宽 */
    double bandwidth_gbps = (double)iterations * 1024 * 8 / result.total_time_us / 1000;
    printf("  │   └─ 带宽: %.2f Gbps\n", bandwidth_gbps);
    printf("  │\n");

    rte_free(src);
    rte_free(dst);
}

/* 测试5: Ring 入队/出队 */
static void bench_ring(void)
{
    struct benchmark_result result = {.name = "Ring enqueue/dequeue"};
    uint64_t start, end;
    uint64_t iterations = 10000000;  // 1000万次

    struct rte_ring *ring = rte_ring_create("bench_ring", 1024,
                                            rte_socket_id(),
                                            RING_F_SP_ENQ | RING_F_SC_DEQ);
    if (ring == NULL) {
        printf("  ├─ 创建 Ring 失败,跳过测试\n");
        return;
    }

    void *obj = (void *)0x12345678;

    start = rte_get_timer_cycles();
    for (uint64_t i = 0; i < iterations; i++) {
        rte_ring_enqueue(ring, obj);
        rte_ring_dequeue(ring, &obj);
    }
    end = rte_get_timer_cycles();

    calc_result(&result, start, end, iterations);
    print_result(&result);

    rte_ring_free(ring);
}

/* 测试6: 比较不同大小的 memcpy */
static void bench_memcpy_sizes(void)
{
    printf("  ├─ 测试项: 不同大小的 memcpy 性能\n");
    printf("  │   ┌─────────┬──────────┬──────────┬──────────┐\n");
    printf("  │   │  大小   │ 每次耗时  │ 吞吐量    │ 带宽     │\n");
    printf("  │   ├─────────┼──────────┼──────────┼──────────┤\n");

    int sizes[] = {64, 128, 256, 512, 1024, 2048, 4096};
    int num_sizes = sizeof(sizes) / sizeof(sizes[0]);

    for (int s = 0; s < num_sizes; s++) {
        int size = sizes[s];
        uint64_t iterations = 10000000;
        uint64_t start, end;

        char *src = rte_malloc(NULL, size, 64);
        char *dst = rte_malloc(NULL, size, 64);
        memset(src, 'A', size);

        start = rte_get_timer_cycles();
        for (uint64_t i = 0; i < iterations; i++) {
            rte_memcpy(dst, src, size);
        }
        end = rte_get_timer_cycles();

        uint64_t elapsed = end - start;
        double per_op_ns = (double)elapsed * 1e9 / g_hz / iterations;
        double ops_per_sec = (double)iterations * g_hz / elapsed;
        double bandwidth_gbps = (double)iterations * size * 8 /
                                ((double)elapsed * 1e6 / g_hz) / 1000;

        printf("  │   │ %4d B  │ %6.1f ns │ %5.1f M/s │ %5.1f Gb │\n",
               size, per_op_ns, ops_per_sec / 1e6, bandwidth_gbps);

        rte_free(src);
        rte_free(dst);
    }

    printf("  │   └─────────┴──────────┴──────────┴──────────┘\n");
    printf("  │\n");
}

int main(int argc, char *argv[])
{
    int ret;

    /* 初始化 EAL */
    ret = rte_eal_init(argc, argv);
    if (ret < 0) {
        printf("EAL 初始化失败\n");
        return -1;
    }

    /* 获取时钟频率 */
    g_hz = rte_get_timer_hz();

    printf("\n");
    printf("╔═══════════════════════════════════════════════════════════╗\n");
    printf("║              DPDK 性能测试工具                             ║\n");
    printf("╠═══════════════════════════════════════════════════════════╣\n");
    printf("║  CPU 频率: %-10.3f GHz                                 ║\n", g_hz / 1e9);
    printf("║  每周期: %-10.3f 纳秒                                   ║\n", 1e9 / g_hz);
    printf("╚═══════════════════════════════════════════════════════════╝\n\n");

    printf("┌─ 开始性能测试...\n");
    printf("│\n");

    /* 运行所有测试 */
    bench_empty_loop();
    bench_rdtsc();
    bench_malloc();
    bench_memcpy();
    bench_ring();
    bench_memcpy_sizes();

    printf("└─ 测试完成!\n\n");

    /* 清理 */
    rte_eal_cleanup();

    return 0;
}

4.3 预期输出示例

╔═══════════════════════════════════════════════════════════╗
║              DPDK 性能测试工具                             ║
╠═══════════════════════════════════════════════════════════╣
║  CPU 频率: 2.400      GHz                                 ║
║  每周期: 0.417        纳秒                                ║
╚═══════════════════════════════════════════════════════════╝

┌─ 开始性能测试...

  ├─ 测试项: 空循环(基准开销)
  │   ├─ 迭代次数: 100000000
  │   ├─ 总耗时: 41666.67 微秒
  │   ├─ 每次操作: 0.42 纳秒
  │   └─ 吞吐量: 2400.00 M ops/sec

  ├─ 测试项: rte_get_timer_cycles()
  │   ├─ 迭代次数: 10000000
  │   ├─ 总耗时: 83333.33 微秒
  │   ├─ 每次操作: 8.33 纳秒
  │   └─ 吞吐量: 120.00 M ops/sec

  ...
└─ 测试完成!

第五课:性能测试最佳实践

5.1 避免常见陷阱

陷阱1:编译器优化掉代码

c
// 错误示例:编译器可能优化掉这个循环
for (int i = 0; i < 1000000; i++) {
    int x = i * 2;  // 结果没被使用,可能被优化掉
}

// 正确示例:使用 volatile 或内存屏障
volatile int result;
for (int i = 0; i < 1000000; i++) {
    result = i * 2;
}

// 或者使用编译器屏障
for (int i = 0; i < 1000000; i++) {
    int x = i * 2;
    __asm__ volatile("" : "+r"(x) ::);  // 告诉编译器 x 被使用了
}

陷阱2:CPU 频率变化

现代 CPU 会动态调整频率(省电模式),导致测量不准确。

bash
# 查看当前 CPU 频率策略
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# 设置为性能模式(固定最高频率)
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

陷阱3:缓存预热

第一次访问内存会很慢(缓存未命中),需要"预热":

c
// 预热阶段(结果不计入统计)
for (int i = 0; i < 1000; i++) {
    your_function();
}

// 正式测量
start = rte_get_timer_cycles();
for (int i = 0; i < iterations; i++) {
    your_function();
}
end = rte_get_timer_cycles();

陷阱4:测量时间太短

c
// 错误:只执行一次,误差太大
start = rte_get_timer_cycles();
your_function();  // 只执行一次
end = rte_get_timer_cycles();

// 正确:执行足够多次,取平均值
start = rte_get_timer_cycles();
for (int i = 0; i < 1000000; i++) {
    your_function();
}
end = rte_get_timer_cycles();
double per_op = (end - start) / 1000000.0;

5.2 性能测试清单

在进行性能测试前,检查以下事项:

□ CPU 频率模式设为 "performance"
□ 禁用了 CPU 节能特性(C-states)
□ 隔离了测试用的 CPU 核心(isolcpus)
□ 关闭了不必要的后台进程
□ 代码已预热(排除冷启动影响)
□ 迭代次数足够多(至少 100 万次)
□ 使用了 volatile 或内存屏障防止优化
□ 多次运行取中位数(排除异常值)

5.3 实用的测量宏

c
/* 简单的计时宏 */
#define BENCHMARK_START(name) \
    do { \
        const char *__bench_name = (name); \
        uint64_t __bench_start = rte_get_timer_cycles();

#define BENCHMARK_END(iterations) \
        uint64_t __bench_end = rte_get_timer_cycles(); \
        uint64_t __bench_elapsed = __bench_end - __bench_start; \
        double __bench_per_op = (double)__bench_elapsed * 1e9 / \
                                rte_get_timer_hz() / (iterations); \
        printf("[BENCH] %s: %.2f ns/op\n", __bench_name, __bench_per_op); \
    } while (0)

/* 使用示例 */
BENCHMARK_START("my_function");
for (int i = 0; i < 1000000; i++) {
    my_function();
}
BENCHMARK_END(1000000);

第六课:常见问题解答

Q1: rte_get_timer_cycles()rte_rdtsc() 有什么区别?

A:

  • rte_rdtsc():直接读取 TSC,最快,但可能乱序执行
  • rte_rdtsc_precise():添加了内存屏障,保证顺序
  • rte_get_timer_cycles():更通用,跨平台兼容

一般情况下使用 rte_get_timer_cycles() 即可。

Q2: 为什么我的测量结果每次都不一样?

A: 常见原因:

  1. CPU 频率动态变化
  2. 操作系统中断干扰
  3. 缓存状态不同
  4. 其他进程竞争 CPU

解决方案

  • 多次测量取中位数
  • 使用隔离的 CPU 核心
  • 设置 CPU 为性能模式

Q3: 如何测量纳秒级的操作?

A: 单次操作太快无法精确测量,需要:

  1. 执行大量次数(如 1000 万次)
  2. 测量总时间
  3. 计算平均值
c
uint64_t total = 0;
for (int i = 0; i < 10000000; i++) {
    uint64_t s = rte_get_timer_cycles();
    fast_operation();
    uint64_t e = rte_get_timer_cycles();
    total += (e - s);
}
double avg_ns = (double)total * 1e9 / rte_get_timer_hz() / 10000000;

Q4: TSC 会溢出吗?

A: TSC 是 64 位计数器。以 3GHz 频率计算:

2^64 / 3,000,000,000 / 3600 / 24 / 365 ≈ 195 年

不用担心溢出问题!

Q5: 在多核系统上,不同核心的 TSC 一样吗?

A:

  • 现代 CPU(Intel Nehalem 之后)的 TSC 是同步的
  • DPDK 假设 TSC 是恒定速率且核心间同步的
  • 如果不确定,可以检查 CPU 标志位:constant_tscnonstop_tsc
bash
grep -E "constant_tsc|nonstop_tsc" /proc/cpuinfo

总结

核心知识点

概念说明API
时钟频率每秒的时钟周期数rte_get_timer_hz()
时钟周期当前的 TSC 计数值rte_get_timer_cycles()
时间转换cycles × 10^9 / hz = 纳秒手动计算
延时等待精确的忙等待rte_delay_us()

性能测试公式

总时间(秒)= 总周期数 / 频率
每次操作(纳秒)= 总周期数 × 10^9 / 频率 / 迭代次数
吞吐量(ops/sec)= 迭代次数 × 频率 / 总周期数

下一步学习

  1. DPDK Timer 定时器:基于时钟周期实现定时任务
  2. 性能调优:使用这些工具优化你的 DPDK 应用
  3. 延迟分析:测量数据包处理的端到端延迟

Happy Benchmarking!

Released under the MIT License.