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() | 直接读取 TSC | 64位计数值 | 最低开销读取 |
rte_rdtsc_precise() | 精确读取 TSC | 64位计数值 | 需要严格顺序时 |
第三课:第一个计时程序
3.1 程序目标
创建一个程序,演示:
- 如何获取时钟频率
- 如何测量代码执行时间
- 如何将周期数转换为实际时间
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-pci3.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: 常见原因:
- CPU 频率动态变化
- 操作系统中断干扰
- 缓存状态不同
- 其他进程竞争 CPU
解决方案:
- 多次测量取中位数
- 使用隔离的 CPU 核心
- 设置 CPU 为性能模式
Q3: 如何测量纳秒级的操作?
A: 单次操作太快无法精确测量,需要:
- 执行大量次数(如 1000 万次)
- 测量总时间
- 计算平均值
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_tsc和nonstop_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)= 迭代次数 × 频率 / 总周期数下一步学习
- DPDK Timer 定时器:基于时钟周期实现定时任务
- 性能调优:使用这些工具优化你的 DPDK 应用
- 延迟分析:测量数据包处理的端到端延迟
Happy Benchmarking!
