1. QoS策略配置与性能基准测试
任务目标
在DDS(Data Distribution Service)通信中,QoS(Quality of Service)策略直接决定数据分发的行为特征。本案例通过配置两种关键QoS策略——可靠性(Reliability)与历史记录深度(History Depth),测量它们对端到端延迟(Latency)和吞吐量(Throughput)的影响,建立QoS参数与性能指标之间的量化关系。
环境与依赖
- 操作系统:Ubuntu 20.04+ / Windows 10+(需支持C++11)
- 中间件:Eclipse Cyclone DDS(开源,支持C++ API)
- 编译工具:CMake ≥ 3.10,支持C++11的编译器(GCC/Clang/MSVC)
- 安装依赖:
sudo apt install libcyclonedds-dev(Ubuntu)或从官方仓库源码编译
实现方案
设计一个发布-订阅(Pub-Sub)基准测试程序:发布端以固定频率发送带时间戳的小型数据包,订阅端接收后计算单包延迟(接收时刻 - 发送时刻),并统计累计吞吐量(每秒接收字节数)。通过命令行参数切换QoS配置组合,对比不同策略下的性能差异。
核心测量逻辑:
- 延迟:每个数据包携带发送端单调时钟时间戳,接收端用同一时钟计算差值,取滑动窗口平均值。
- 吞吐量:接收端每秒统计收到的字节总数,输出瞬时值。
关键完整代码
// qos_bench.cpp
#include <cstdlib>
#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <atomic>
#include <cstring>
#include <vector>
#include "dds/dds.hpp"
using namespace dds::core;
using namespace dds::pub;
using namespace dds::sub;
using namespace dds::topic;
using namespace std::chrono;
// 数据类型定义
struct BenchData {
uint64_t seq;
uint64_t send_time_ns;
};
// 注册类型(Cyclone DDS要求)
namespace dds { namespace topic {
template<> struct topic_type_support<BenchData> {
static std::vector<uint8_t> to_bytes(const BenchData& d) {
std::vector<uint8_t> buf(sizeof(BenchData));
std::memcpy(buf.data(), &d, sizeof(BenchData));
return buf;
}
static BenchData from_bytes(const std::vector<uint8_t>& buf) {
BenchData d;
std::memcpy(&d, buf.data(), sizeof(BenchData));
return d;
}
static std::string type_name() { return "BenchData"; }
};
}}
// 全局统计
std::atomic<uint64_t> g_recv_count{0};
std::atomic<uint64_t> g_recv_bytes{0};
std::atomic<uint64_t> g_latency_sum_ns{0};
int main(int argc, char** argv) {
// 解析参数: mode(0=pub,1=sub) reliability(0=best_effort,1=reliable) depth(1/10/100)
if (argc < 4) {
std::cerr << "Usage: " << argv[0] << " <mode:0/1> <reliability:0/1> <depth:int>\n";
return 1;
}
int mode = std::atoi(argv[1]);
bool reliable = std::atoi(argv[2]) != 0;
int depth = std::atoi(argv[3]);
// 创建DomainParticipant(Domain ID=0)
dds::domain::DomainParticipant dp(0);
// 注册Topic
dds::topic::Topic<BenchData> topic(dp, "bench_topic");
// 构造QoS
dds::pub::qos::DataWriterQos wqos;
dds::sub::qos::DataReaderQos rqos;
if (reliable) {
wqos.reliability(dds::core::policy::Reliability::Reliable());
rqos.reliability(dds::core::policy::Reliability::Reliable());
} else {
wqos.reliability(dds::core::policy::Reliability::BestEffort());
rqos.reliability(dds::core::policy::Reliability::BestEffort());
}
wqos.history(dds::core::policy::History::KeepLast(depth));
rqos.history(dds::core::policy::History::KeepLast(depth));
if (mode == 0) { // 发布端
dds::pub::Publisher pub(dp);
dds::pub::DataWriter<BenchData> writer(pub, topic, wqos);
BenchData sample;
sample.seq = 0;
auto start = high_resolution_clock::now();
while (true) {
sample.send_time_ns = duration_cast<nanoseconds>(
high_resolution_clock::now().time_since_epoch()).count();
writer.write(sample);
sample.seq++;
// 控制发送频率约10kHz(100us间隔)
std::this_thread::sleep_for(microseconds(100));
// 每2秒打印一次发送速率
auto now = high_resolution_clock::now();
if (duration_cast<seconds>(now - start).count() >= 2) {
std::cout << "[PUB] sent=" << sample.seq << " rate="
<< sample.seq / 2.0 << " msg/s\n";
start = now;
sample.seq = 0;
}
}
} else { // 订阅端
dds::sub::Subscriber sub(dp);
dds::sub::DataReader<BenchData> reader(sub, topic, rqos);
auto start = high_resolution_clock::now();
uint64_t window_bytes = 0;
while (true) {
auto samples = reader.take();
for (auto& s : samples) {
if (s.info().valid()) {
auto now_ns = duration_cast<nanoseconds>(
high_resolution_clock::now().time_since_epoch()).count();
uint64_t latency = now_ns - s.data().send_time_ns;
g_latency_sum_ns += latency;
g_recv_count++;
g_recv_bytes += sizeof(BenchData);
window_bytes += sizeof(BenchData);
}
}
auto now = high_resolution_clock::now();
if (duration_cast<seconds>(now - start).count() >= 2) {
double avg_lat_us = (g_recv_count > 0) ?
(g_latency_sum_ns / (double)g_recv_count / 1000.0) : 0.0;
double throughput_mbps = (window_bytes * 8) / 2.0 / 1e6;
std::cout << "[SUB] count=" << g_recv_count
<< " avg_latency_us=" << avg_lat_us
<< " throughput_mbps=" << throughput_mbps << "\n";
// 重置窗口
g_recv_count = 0;
g_latency_sum_ns = 0;
window_bytes = 0;
start = now;
}
std::this_thread::sleep_for(milliseconds(1));
}
}
return 0;
}
编译与运行
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(qos_bench)
find_package(CycloneDDS REQUIRED)
add_executable(qos_bench qos_bench.cpp)
target_link_libraries(qos_bench CycloneDDS::CycloneDDS)
mkdir build && cd build
cmake ..
make -j4
# 终端1:发布端(BestEffort, depth=1)
./qos_bench 0 0 1
# 终端2:订阅端(BestEffort, depth=1)
./qos_bench 1 0 1
验证方法与判定标准
按以下矩阵运行4组实验,每组运行至少30秒,记录订阅端输出的平均延迟与吞吐量:
| 组号 | Reliability | History Depth | 预期观察 |
|---|---|---|---|
| A | BestEffort | 1 | 最低延迟,可能丢包 |
| B | Reliable | 1 | 延迟略高,无丢包 |
| C | Reliable | 10 | 延迟与B接近,容忍短暂网络抖动 |
| D | Reliable | 100 | 内存占用增加,延迟可能因缓存而略升 |
通过条件:
- 程序能稳定运行,订阅端每2秒输出统计信息,无崩溃或死锁。
- 对比A组与B组:A组平均延迟应显著低于B组(通常差距>20%),证明可靠性QoS引入额外确认开销。
- 对比B/C/D组:延迟差异应在测量噪声范围内(<15%),证明历史深度对稳态延迟影响有限,但深度越大,订阅端内存占用越高(可通过
ps -o rss观察)。 - 所有组吞吐量应接近发布速率(约10k msg/s × 24B ≈ 1.9 Mbps),若Reliable组吞吐量明显下降,说明网络或中间件存在瓶颈。
注意:以上预期基于典型局域网环境,实际数值受硬件、网络和系统负载影响。若A组与B组延迟差异不明显,请检查是否在同一台机器上运行(回环接口延迟极低),建议在两台物理机或使用tc模拟网络延迟(sudo tc qdisc add dev lo root netem delay 1ms)以放大差异。
常见问题
- 编译错误:找不到CycloneDDS:确认已安装
libcyclonedds-dev,且CMake能找到CycloneDDSConfig.cmake。 - 订阅端收不到数据:检查发布端和订阅端的Domain ID是否一致(本例均为0),且Topic名称相同("bench_topic")。
- 延迟测量不准确:确保发布端和订阅端使用同一台机器的单调时钟(
steady_clock),跨机器时需使用NTP同步或硬件时间戳。