当前位置: 首页 > news >正文

linux性能提升之sendmmsg和recvmmsg

目录

  • sendmsg、sendmmsg和recvmmsg
  • 相关结构体:mmsghdr、msghdr、iovec
  • sendmmsg性能测试
  • 关于connect

sendmsg、sendmmsg和recvmmsg

以udp发送为例。
sendmsg 和 sendmmsg :两者都能发送多块数据,区别在于sendmsg会将所有数据整合成一个UDP包发出,sendmmsg是每个 mmsghdr 一个UDP包。sendmmsg 是 sendmsg 的复合加强版。

/*** @brief 系统调用 sendmsg* 在发送时, iovec 数组中的每个元素都表示要发送的一个数据块。sendmsg 会将这些数据块作为一个整体发送。* * @param sockfd * @param msg * @param flags * @return ssize_t */
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
/*** @brief 系统调用 sendmmsg ,可以发送多个mmsghdr数据,每个mmsghdr包含一个msghdr,msghdr中包含多个数据包构成的数组iovec** * @param __fd          fd* @param __vmessages   指向 mmsghdr 数组的指针* @param __vlen        mmsghdr 数组的成员个数* @param __flags * @return int          成功发送 mmsghdr 的个数*/
extern int sendmmsg (int __fd, struct mmsghdr *__vmessages,unsigned int __vlen, int __flags);
/*** @brief recvmmsg消息接收* 在接收时, iovec 数组中的每个元素都表示接收的数据块的位置和大小。recvmsg 会将接收到的数据分散到这些缓冲区中。* @param __fd          fd* @param __vmessages   消息结构体数组mmsghdr ** @param __vlen        消息结构体mmsghdr*数组大小* @param __flags       标识* @param __tmo         超时* @return int       Returns the number of bytes read or -1 for errors*/
extern int recvmmsg (int __fd, struct mmsghdr *__vmessages,unsigned int __vlen, int __flags,const struct timespec *__tmo);

相关结构体:mmsghdr、msghdr、iovec

sendmmsg消息结构体mmsghdr。

struct mmsghdr
{struct msghdr msg_hdr;	/* 消息头.  */unsigned int msg_len;	/* 传输的字节数.  */
};

描述sendmsg'和recvmsg’的消息结构体msghdr。

struct msghdr
{void *msg_name;		/* 发送/接收的网络地址.  */socklen_t msg_namelen;	/* 网络地址长度.  */struct iovec *msg_iov;	/* 指向数据缓冲区的指针,可以是数组.  */size_t msg_iovlen;		/* 数据缓存区数组 msg_iov 的成员数量.  */void *msg_control;		/* 指向辅助数据的缓冲区 (用于控制消息) */size_t msg_controllen;	/* 辅助数据缓冲区的大小 */int msg_flags;		/* 接收消息时设置的标志.  */
};
struct iovec 
{void  *iov_base;    // 指向数据缓冲区的指针size_t iov_len;     // 数据缓冲区的长度
};

sendmmsg性能测试

使用官方手册的例子,进行改写:
https://man7.org/linux/man-pages/man2/sendmmsg.2.html
https://man7.org/linux/man-pages/man2/recvmmsg.2.html

测试用例1:sendto模式

Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
Average:         eth0      9.72      9.32      1.27      1.56      0.00      0.00      0.00
Average:           lo 568170.44 568170.44  78788.46  78788.46      0.00      0.00      0.00Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all    6.73    0.00   18.75    0.00    0.00   26.53    0.00    0.00    0.00   47.98
Average:       0    8.58    0.00   27.74    0.00    0.00   40.52    0.00    0.00    0.00   23.15
Average:       1    4.98    0.00    9.76    0.00    0.00   12.45    0.00    0.00    0.00   72.81

测试用例2:sendmmsg模式,1个mmsghdr只包含一个udp包,不组包

Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
Average:         eth0      7.67      6.94      0.85      1.74      0.00      0.00      0.00
Average:           lo 665072.24 665072.24  92226.18  92226.18      0.00      0.00      0.00Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all    1.45    0.00   19.90    0.00    0.00   30.60    0.00    0.00    0.00   48.05
Average:       0    0.60    0.00   30.06    0.00    0.00   47.19    0.00    0.00    0.00   22.14
Average:       1    2.20    0.00    9.80    0.00    0.00   14.10    0.00    0.00    0.00   73.90

sendmmsg 相对 sendto 的优势:
1、降低了用户使用CPU占比(%usr),但对内核系统(%sys)和软中断(%soft)没有改善;传输速率提升15%左右。

2、sendmmsg :经测试,如果每个原始udp包都设置一个 mmsghdr 头, __vlen 数量和原始udp包数量一致,观察CPU软中断、性能消耗等和sendto类似,%usr消耗下降,速率有提升但有限。

3、sendmmsg :如果把多个原始udp包都放在一个 mmsghdr 头, __vlen 数量是1,CPU软中断下降,速率有提升,但所有udp包会被合并成一个udp大包,使用同一个ip/udp头部,相当于调用了 sendmsg 。

4、思考: sendmsg/sendmmsg/sendto 等系统调用只是把数据拷贝到内核缓存区,这个拷贝的过程并不会消耗太多CPU性能,真正消耗性能触发软中断的是内核把数据包通过网卡发送出去。

sndmmsg.cpp源码

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string>#define BUFF_SIZE    100
#define ARR_SIZE     32int
main(void)
{int                 retval;int                 sockfd;struct sockaddr_in  addr;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1) {perror("socket()");exit(EXIT_FAILURE);}addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);addr.sin_port = htons(1234);
#if 0 // 是否使用connect
/*
1、如果使用connect,则可以用send代替sendto; sendmmsg 的msg_name和msg_namelen可以不赋值;但如果对端没有启动,sendmmsg 发送的返回值只有1。
2、不使用connect,sendmmsg 的msg_name和msg_namelen需要赋值才能发送成功,且不管对方有没有启动,sendmmsg的正常返回值就是第三个参数大小,即mmsghdr数组的大小。
*/if (connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {perror("connect()");exit(EXIT_FAILURE);}
#endif#if 0 // sendto模式
/*
Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
Average:         eth0      9.72      9.32      1.27      1.56      0.00      0.00      0.00
Average:           lo 568170.44 568170.44  78788.46  78788.46      0.00      0.00      0.00Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all    6.73    0.00   18.75    0.00    0.00   26.53    0.00    0.00    0.00   47.98
Average:       0    8.58    0.00   27.74    0.00    0.00   40.52    0.00    0.00    0.00   23.15
Average:       1    4.98    0.00    9.76    0.00    0.00   12.45    0.00    0.00    0.00   72.81
*/std::string msg = "hello";char* buf = new char[BUFF_SIZE];while(1){sendto(sockfd, buf,BUFF_SIZE,0,(struct sockaddr*)&addr,sizeof(addr));}
#endif#if 0 // sendmmsg模式1,1个mmsghdr只包含一个udp包,不组包
/*
Average:        IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
Average:         eth0      7.67      6.94      0.85      1.74      0.00      0.00      0.00
Average:           lo 665072.24 665072.24  92226.18  92226.18      0.00      0.00      0.00Average:     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
Average:     all    1.45    0.00   19.90    0.00    0.00   30.60    0.00    0.00    0.00   48.05
Average:       0    0.60    0.00   30.06    0.00    0.00   47.19    0.00    0.00    0.00   22.14
Average:       1    2.20    0.00    9.80    0.00    0.00   14.10    0.00    0.00    0.00   73.90sendmmsg 相对 send 的优势:降低了用户使用CPU占比(%usr),但对内核系统(%sys)和软中断(%soft)没有改善;传输速率提升15%左右。
*/struct iovec    _iovec[ARR_SIZE];struct mmsghdr  _hdrvec[ARR_SIZE];printf("iovec.size=%d\n",sizeof(iovec));printf("_iovec.size=%d\n",sizeof(_iovec));memset(_iovec, 0 ,sizeof(_iovec));memset(_hdrvec, 0 ,sizeof(_hdrvec));for(int index=0;index<ARR_SIZE;index++){char* buf = new char[BUFF_SIZE];_iovec[index].iov_base = buf;_iovec[index].iov_len  = BUFF_SIZE;_hdrvec[index].msg_hdr.msg_iov = &_iovec[index];_hdrvec[index].msg_hdr.msg_iovlen = 1;_hdrvec[index].msg_hdr.msg_name = (void *)&addr;    // 指向目标地址结构体的指针 _hdrvec[index].msg_hdr.msg_namelen = sizeof(addr);     // 目标地址结构体的大小 }while(1){retval = sendmmsg(sockfd, _hdrvec, ARR_SIZE, 0);// if (retval == -1) //     perror("sendmmsg()");// else//     printf("%d messages sent\n", retval);}
#endif// sendmmsg模式2,1个mmsghdr包含多个udp包,组包
// 如果可以组包发送,建议直接在应用层组包,而不是通过系统调用组包,后者只会增加开发难度。// 原测试模块,发送文本,msg1是2个包,发送时合并成一个udp包发送,最终发送2个udp包:"onetwo"和"three"
// 每个mmsghdr头代表一个udp包struct iovec        msg1[2], msg2;struct mmsghdr      msg[2];memset(msg1, 0, sizeof(msg1));msg1[0].iov_base = (void*)"one";msg1[0].iov_len = 3;msg1[1].iov_base = (void*)"two";msg1[1].iov_len = 3;memset(&msg2, 0, sizeof(msg2));msg2.iov_base = (void*)"three";msg2.iov_len = 5;memset(msg, 0, sizeof(msg));msg[0].msg_hdr.msg_iov = msg1;msg[0].msg_hdr.msg_iovlen = 2;msg[0].msg_hdr.msg_name = (void *)&addr;    // 指向目标地址结构体的指针 msg[0].msg_hdr.msg_namelen = sizeof(addr);     // 目标地址结构体的大小 msg[1].msg_hdr.msg_iov = &msg2;msg[1].msg_hdr.msg_iovlen = 1;msg[1].msg_hdr.msg_name = (void *)&addr;    // 指向目标地址结构体的指针 msg[1].msg_hdr.msg_namelen = sizeof(addr);     // 目标地址结构体的大小 retval = sendmmsg(sockfd, msg, 2, 0);if (retval == -1)perror("sendmmsg()");elseprintf("%d messages sent\n", retval);exit(0);
}

rcvmmsg.cpp源码

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>int
main(void)
{
#define VLEN 10
#define BUFSIZE 200
#define TIMEOUT 1int                 sockfd, retval;char                bufs[VLEN][BUFSIZE+1];struct iovec        iovecs[VLEN];struct mmsghdr      msgs[VLEN];struct timespec     timeout;struct sockaddr_in  addr;sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd == -1) {perror("socket()");exit(EXIT_FAILURE);}addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);addr.sin_port = htons(1234);if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {perror("bind()");exit(EXIT_FAILURE);}memset(msgs, 0, sizeof(msgs));for (size_t i = 0; i < VLEN; i++) {iovecs[i].iov_base         = bufs[i];iovecs[i].iov_len          = BUFSIZE;msgs[i].msg_hdr.msg_iov    = &iovecs[i];msgs[i].msg_hdr.msg_iovlen = 1;}timeout.tv_sec = TIMEOUT;timeout.tv_nsec = 0;while(1) {retval = recvmmsg(sockfd, msgs, VLEN, 0, &timeout);if (retval == -1) {perror("recvmmsg()");exit(EXIT_FAILURE);}printf("%d messages received\n", retval);for (size_t i = 0; i < retval; i++) {bufs[i][msgs[i].msg_len] = 0;printf("%zu %s\n", i+1, bufs[i]);}}exit(EXIT_SUCCESS);
}

关于connect

1、如果使用connect,则可以用send代替sendto; sendmmsg 的msg_name和msg_namelen可以不赋值;但如果对端没有启动,sendmmsg 发送的返回值只有1。
2、不使用connect,sendmmsg 的msg_name和msg_namelen需要赋值才能发送成功,且不管对方有没有启动,sendmmsg的正常返回值就是第三个参数大小,即mmsghdr数组的大小。


http://www.mrgr.cn/news/71834.html

相关文章:

  • 13、指向Member Function的指针
  • 刘知远LLM——大模型微调:prompt-learningdelta tuning
  • python opencv3
  • SNMP原理与配置
  • Vue3 : Tailwindcss之margin样式类
  • 瞧瞧别人家的异常处理,那叫一个优雅!
  • kafka夺命连环三十问(16-22)
  • A/B测试的误区与优化策略:如何最大化客户留存ROI?
  • 【LeetCode】【算法】136. 只出现一次的数字
  • 数据结构《链表》
  • ML 系列: 第 23 节 — 离散概率分布 (多项式分布)
  • 【MySQL 保姆级教学】事务的自动提交和手动提交(重点)--上(13)
  • 移动电源测试中最核心的测试项目有哪些?-纳米软件
  • 多线程和线程同步复习
  • 鸿蒙next版开发:ArkTS组件通用属性(Flex布局)
  • python语言基础-4 常用模块-4.7 pyinstaller模块
  • Spring生态学习路径与源码深度探讨
  • 今天出了10个4声母 .com
  • 1163:阿克曼(Ackmann)函数
  • 词汇积累之倒行逆施、上行下效极简理解
  • 百度富文本禁止编辑
  • 华为OD机试真题-寻找最大价值的矿堆-2024年OD统一考试(E卷)
  • Flink运行时架构以及核心概念
  • 非常惨痛的一次lockbit经历
  • 华为路由策略配置
  • 【系统架构设计师】真题论文: 论数据挖掘技术的应用(包括解题思路和素材)