使用PACKET_MMAP和PACKET_TX_RING发送数据比正常"正常" (不含)

时间:2017-04-03 20:42:51

标签: c performance sockets network-programming circular-buffer

我正在使用PACKET_MMAP套接字选项在C中编写流量生成器,以创建环形缓冲区以通过原始套接字发送数据。环形缓冲区填充了要发送的以太网帧,并调用sendto。环形缓冲区的全部内容通过套接字发送,这应该比内存中的缓冲区提供更高的性能,并且对需要发送的缓冲区中的每一帧重复调用sendto

当不使用PACKET_MMAP时,在调用sendto时,将单个帧从用户区内存中的缓冲区复制到内核内存中的SK buf,然后内核必须将该数据包复制到NIC访问的内存中对于DMA并发信号通知NIC将帧DMA映射到它自己的硬件缓冲区并对其进行排队以进行传输。当使用PACKET_MMAP套接字选项时,mmapped内存由应用程序分配并链接到原始套接字。应用程序将数据包放入mmapped缓冲区,调用sendto而不是内核必须将数据包复制到SK buf中,它可以直接从mmapped缓冲区中读取它们。还有"块"可以从环形缓冲区而不是单独的分组/帧中读取分组。因此,性能提升是一个系统调用,用于复制多个帧,每个帧复制操作少一个,以使其进入NIC硬件缓冲区。

当我将使用PACKET_MMAP的套接字的性能与“普通”套接字(其中包含单个数据包的char缓冲区)进行比较时,根本没有性能优势。为什么会这样?在Tx模式下使用PACKET_MMAP时,每个环形块只能放入一个帧(而不是像Rx模式那样每个环形块有多个帧)但是我创建了256个块,所以我们应该发送256个单个sendto电话中的帧权限?

使用PACKET_MMAP进行效果,main()调用packet_tx_mmap()

bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0   Tx Gbps 17.65 (2206128128) pps 1457152
2. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.08 (2385579520) pps 1575680
3. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.28 (2409609728) pps 1591552
4. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.31 (2414260736) pps 1594624
5. Rx Gbps 0.00 (0) pps 0   Tx Gbps 19.30 (2411935232) pps 1593088

没有PACKET_MMAP的效果,main()调用packet_tx()

bensley@ubuntu-laptop:~/C/etherate10+$ sudo taskset -c 1 ./etherate_mt -I 1
Using inteface lo (1)
Running in Tx mode
1. Rx Gbps 0.00 (0) pps 0   Tx Gbps 18.44 (2305001412) pps 1522458
2. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.30 (2537520018) pps 1676037
3. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.29 (2535744096) pps 1674864
4. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.26 (2533014354) pps 1673061
5. Rx Gbps 0.00 (0) pps 0   Tx Gbps 20.32 (2539476106) pps 1677329

packet_tx()函数稍微快于packet_tx_mmap()函数,但它也略短,所以我认为最小的性能提升只是{{1 }}。所以在我看来,这两个功能几乎具有相同的性能,为什么呢?为什么PACKET_MMAP不会更快,因为据我所知,系统调用和副本应该少得多?

packet_tx

请注意,以下功能会调用void *packet_tx_mmap(void* thd_opt_p) { struct thd_opt *thd_opt = thd_opt_p; int32_t sock_fd = setup_socket_mmap(thd_opt_p); if (sock_fd == EXIT_FAILURE) exit(EXIT_FAILURE); struct tpacket2_hdr *hdr; uint8_t *data; int32_t send_ret = 0; uint16_t i; while(1) { for (i = 0; i < thd_opt->tpacket_req.tp_frame_nr; i += 1) { hdr = (void*)(thd_opt->mmap_buf + (thd_opt->tpacket_req.tp_frame_size * i)); data = (uint8_t*)(hdr + TPACKET_ALIGN(TPACKET2_HDRLEN)); memcpy(data, thd_opt->tx_buffer, thd_opt->frame_size); hdr->tp_len = thd_opt->frame_size; hdr->tp_status = TP_STATUS_SEND_REQUEST; } send_ret = sendto(sock_fd, NULL, 0, 0, NULL, 0); if (send_ret == -1) { perror("sendto error"); exit(EXIT_FAILURE); } thd_opt->tx_pkts += thd_opt->tpacket_req.tp_frame_nr; thd_opt->tx_bytes += send_ret; } return NULL; } 而不是setup_socket()

setup_socket_mmap()

套接字设置功能的唯一区别是粘贴在下面,但基本上是设置SOCKET_RX_RING或SOCKET_TX_RING的要求:

void *packet_tx(void* thd_opt_p) {

    struct thd_opt *thd_opt = thd_opt_p;

    int32_t sock_fd = setup_socket(thd_opt_p); 

    if (sock_fd == EXIT_FAILURE) {
        printf("Can't create socket!\n");
        exit(EXIT_FAILURE);
    }

    while(1) {

        thd_opt->tx_bytes += sendto(sock_fd, thd_opt->tx_buffer,
                                    thd_opt->frame_size, 0,
                                    (struct sockaddr*)&thd_opt->bind_addr,
                                    sizeof(thd_opt->bind_addr));
        thd_opt->tx_pkts += 1;

    }

}

更新1:针对物理接口的结果 有人提到我使用PACKET_MMAP时可能没有看到性能差异的一个原因是因为我正在向loopback接口发送流量(一方面,它没有QDISC)。由于运行// Set the TPACKET version, v2 for Tx and v3 for Rx // (v2 supports packet level send(), v3 supports block level read()) int32_t sock_pkt_ver = -1; if(thd_opt->sk_mode == SKT_TX) { static const int32_t sock_ver = TPACKET_V2; sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver)); } else { static const int32_t sock_ver = TPACKET_V3; sock_pkt_ver = setsockopt(sock_fd, SOL_PACKET, PACKET_VERSION, &sock_ver, sizeof(sock_ver)); } if (sock_pkt_ver < 0) { perror("Can't set socket packet version"); return EXIT_FAILURE; } memset(&thd_opt->tpacket_req, 0, sizeof(struct tpacket_req)); memset(&thd_opt->tpacket_req3, 0, sizeof(struct tpacket_req3)); //thd_opt->block_sz = 4096; // These are set else where //thd_opt->block_nr = 256; //thd_opt->block_frame_sz = 4096; int32_t sock_mmap_ring = -1; if (thd_opt->sk_mode == SKT_TX) { thd_opt->tpacket_req.tp_block_size = thd_opt->block_sz; thd_opt->tpacket_req.tp_frame_size = thd_opt->block_sz; thd_opt->tpacket_req.tp_block_nr = thd_opt->block_nr; // Allocate per-frame blocks in Tx mode (TPACKET_V2) thd_opt->tpacket_req.tp_frame_nr = thd_opt->block_nr; sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_TX_RING , (void*)&thd_opt->tpacket_req , sizeof(struct tpacket_req)); } else { thd_opt->tpacket_req3.tp_block_size = thd_opt->block_sz; thd_opt->tpacket_req3.tp_frame_size = thd_opt->block_frame_sz; thd_opt->tpacket_req3.tp_block_nr = thd_opt->block_nr; thd_opt->tpacket_req3.tp_frame_nr = (thd_opt->block_sz * thd_opt->block_nr) / thd_opt->block_frame_sz; thd_opt->tpacket_req3.tp_retire_blk_tov = 1; thd_opt->tpacket_req3.tp_feature_req_word = 0; sock_mmap_ring = setsockopt(sock_fd, SOL_PACKET , PACKET_RX_RING , (void*)&thd_opt->tpacket_req3 , sizeof(thd_opt->tpacket_req3)); } if (sock_mmap_ring == -1) { perror("Can't enable Tx/Rx ring for socket"); return EXIT_FAILURE; } thd_opt->mmap_buf = NULL; thd_opt->rd = NULL; if (thd_opt->sk_mode == SKT_TX) { thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0); if (thd_opt->mmap_buf == MAP_FAILED) { perror("mmap failed"); return EXIT_FAILURE; } } else { thd_opt->mmap_buf = mmap(NULL, (thd_opt->block_sz * thd_opt->block_nr), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock_fd, 0); if (thd_opt->mmap_buf == MAP_FAILED) { perror("mmap failed"); return EXIT_FAILURE; } // Per bock rings in Rx mode (TPACKET_V3) thd_opt->rd = (struct iovec*)calloc(thd_opt->tpacket_req3.tp_block_nr * sizeof(struct iovec), 1); for (uint16_t i = 0; i < thd_opt->tpacket_req3.tp_block_nr; ++i) { thd_opt->rd[i].iov_base = thd_opt->mmap_buf + (i * thd_opt->tpacket_req3.tp_block_size); thd_opt->rd[i].iov_len = thd_opt->tpacket_req3.tp_block_size; } } packet_tx_mmap()例程中的任何一个都可以生成超过10Gbps的速度而且我只有10Gbps接口供我使用,我将两个绑定在一起,这些是结果,显示与上面几乎相同,这两个函数之间的速度差异很小:

packet_tx()到20G bond0

  • 1个主题:平均10.77Gbps~ / 889kfps~
  • 2个主题:平均19.19Gbps~ / 1.58Mfps~
  • 3个主题:平均19.67Gbps~ / 1.62Mfps~(这是 债券将会快速发展)

packet_tx()到20G bond0:

  • 1个主题:平均11.08Gbps~ / 913kfps~
  • 2个主题:平均19.0Gbps~ / 1.57Mfps~
  • 3个主题:平均19.66Gbps~ / 1.62Mfps~(这是 债券将会快速发展)

这是帧大小为1514字节(保持与上面的原始环回测试相同)。

在所有上述测试中,软IRQ的数量大致相同(使用this script测量)。当一个线程运行packet_tx_mmap()时,CPU核心上每秒大约有40k个中断。有2个和3个线程分别在2和3个核心上运行40k。使用packet_tx()时的结果相同。大约40k个软IRQ,用于一个CPU内核上的单个线程。运行2和3个线程时,每个核心40k。

更新2:完整源代码

我现在已经上传了完整的源代码,我还在编写这个应用程序,所以它可能有很多缺陷,但它们超出了这个问题的范围:https://github.com/jwbensley/EtherateMT

0 个答案:

没有答案