【问题标题】:Monitoring Thread performance of server监控服务器的线程性能
【发布时间】:2018-09-08 16:39:23
【问题描述】:

我使用 gcc 和 pthreads 开发了一个 C 服务器,它接收 UDP 数据包并根据配置将它们丢弃或转发到特定目标。在某些情况下,这些数据包不受影响,只是被重定向,在某些情况下,数据包中的标头被修改,在其他情况下,服务器的另一个模块会修改数据包的每个字节。

要配置此服务器,有一个用 Java 编写的 GUI,它使用 TCP 连接到 C 服务器(以交换配置命令)。可以同时连接多个 GUI。

为了衡量服务器的利用率,我编写了一种启动两个单独线程(#2 和#3)的模块。完成整个转发工作的主线程(#1)基本上是这样工作的:

struct monitoring_struct data; //contains 2 * uint64_t for start and end time among other fields
for(;;){
    recvfrom();
    data.start = current_time();
    modifyPacket();
    sendPacket(); //sometimes to multiple destinations
    data.end = current_time();
    writeDataToPipe();
}

current_time 函数:

 //give a timestamp in microsecond precision
    uint64_t current_time(void){
        struct timespec spec;
        clock_gettime(CLOCK_REALTIME, &spec);
        uint64_t ts = (uint64_t) ((((double) spec.tv_sec) * 1.0e6) +
 (((double) spec.tv_nsec) / 1.0e3));
        return ts;
    }

如主线程所示,数据结构被写入管道,线程#2 等待从中读取。每次从管道中读取数据时,线程#2 使用给定的聚合函数将数据存储在内存中的另一个位置。线程 #3 是一个循环,它总是休眠约 1 秒,然后发出聚合值(中位数、平均值、最小值、最大值、下四分位数、上四分位数...),然后重置聚合数据。线程 #2 和 #3 由互斥锁同步。

GUI 侦听此数据(如果监控窗口打开),这些数据通过 UDP 发送给侦听器(可以有更多),然后 GUI 将这些数字转换为图表、图形和“压力”指标。

我想出了这个,因为在我看来,这是最不干扰线程 #1 的解决方案(假设它运行在多核系统上,它始终如此,并且仅在操作系统和可能的 SSH 之外运行)。

由于性能对我的服务器至关重要(配置更简单的“1.0”版能够管理使用千兆以太网可能的最大流数)我想问一下我的解决方案是否不如我认为这是为了确保对线程 #1 的性能影响最小,如果您认为会有更好的设计呢?至少我想不出另一种解决方案,即不对数据本身使用锁(避免管道,但可能锁定线程#1)或使用 rwlock 的共享列表实现,可能会导致读取器饥饿。

存在数据包较大的情况,但我们目前使用该模式来测量性能,其中 1 个 Streams 每秒恰好发送 1000 个数据包。我们目前希望确保 2.0 版至少可以处理 12 个流(因此每秒 12000 个数据包),但是之前服务器能够管理 84 个流。

将来我想将其他里程碑时间戳添加到线程 #1,例如在modifyPacket() 内部(有多个步骤)和sendPacket() 之前。

我曾尝试修改current_time() 函数,主要是通过存储clock_gettime() 的值来删除它以节省时间,但在我的简单测试程序中current_time() 函数总是优于clock_gettime。

提前感谢您的任何意见。

【问题讨论】:

    标签: c multithreading performance pipe monitoring


    【解决方案1】:

    如果您认为有更好的设计?

    简短的回答是使用Data Plane Development Kit (DPDK) 及其设计模式和库。这可能是一个相当长的学习曲线,但就性能而言,它是目前最好的解决方案。它是免费和开源的(BSD 许可)。

    更详细一点的答案:

    数据结构被写入管道

    由于线程#1 和#2 是同一进程的线程,因此使用共享内存而不是管道传递数据会快得多。就像您在线程 #2 和 #3 之间使用的一样。

    线程 #2 使用给定的聚合函数将数据存储在内存中的另一个位置

    这两个线程似乎没有必要。线程#2 可以读取线程#1 传递的数据,聚合并发送出去?

    我想不出另一种不对数据本身使用锁的解决方案

    查看称为"rings" in DPDK 的无锁队列。这个想法是在线程之间有一个共同的circular buffer,并使用无锁算法将缓冲区入队/出队。

    我们目前希望确保 2.0 版至少可以处理 12 个流(因此每秒 12000 个数据包),但是之前服务器能够管理 84 个流。

    测量性能并找到瓶颈(似乎您仍然不能 100% 确定代码中的瓶颈是什么)。

    仅供参考,英特尔发布了performance reports for DPDK。用于 L3 转发(即路由)的参考数字高达每秒 3000 万个数据包。

    当然,您的处理器和 NIC 可能不那么强大,但是使用正确的技术可以很容易地达到每秒几百万个数据包。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-26
      • 2019-12-25
      • 2016-07-24
      • 2021-05-23
      相关资源
      最近更新 更多