是否有一种非阻塞方法来检查数据的速度比select()和poll()更快?

时间:2014-05-28 22:21:45

标签: c linux performance sockets posix

我有一个C程序使用sendto()方法尽快发送数据,从发送方到接收方,用recvfrom()方法接收。数据封装在第2层以太网帧中,应用程序将以太网帧直接写入线路(无TCP或UDP甚至IP)。这是在x86_64 Linux上(开发机器只是库存Ubuntu 14.04)。我无意移植到任何其他操作系统,应用程序设计范围适用于Linux,因此其他操作系统并不重要。

发件人:

    while (true)
    {
        sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0,
            (struct sockaddr*)&socket_address, sizeof socket_address);
    }

接收器:

    while (true)
    {
        recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
    }

我希望发件人能够检查收到的数据包;如果接收方应用程序退出,它会将数据发送回发送方,并说“退出”#34;所以发件人将停止发送数据。我在发送器上使用poll()来检查接收到的消息,如下所示,但这显着降低了传输速度,从1Gbps(968Mbps)到大约10Mbps。我在使用1Gbps网卡的两台PC之间使用交叉电缆进行测试。发送方计算发送的帧数和帧大小,接收方计算接收到的帧数和帧大小,因此要确认,应用程序实际上是以接近线速接收,我不仅仅看看NIC利用率或类似情况。

投票()方法:

int rv;
struct pollfd ufds[1];
ufds[0].fd = sockFD;
ufds[0].events = POLLIN


while (true)
{
    sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));

    // wait for events on the sockets, 1ms timeout
    rv = poll(ufds, 1, 1);

    if (rv > 0) {
        if (ufds[0].revents & POLLIN) {
            recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
        }
    }

}

1毫秒是可以为poll()方法设置的最低超时。这就是我的传输程序只能以10Mbps传输的原因。该应用程序可以轻松地使1Gbps链路饱和,尽管CPU使用率最低,我之前提到的速度达到了968Mbps(顺便说一句,我并不意味着峰值,即持续吞吐量)。

我删除了poll()调用并使用下面的示例切换到select(),但同样,使用最小的延迟我可以在这里使用我的传输应用程序只能获得175Mbps。不接近原来的968Mbps;

选择()方法:

fd_set readfds;
struct timeval tv;
int rv, n;

FD_ZERO(&readfds);
FD_SET(sockFD, &readfds);
n = sockFD + 1;


while (true)
{
    sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));

    tv.tv_sec = 0;
    tv.tv_usec = 000001;
    rv = select(n, &readfds, NULL, NULL, &tv);
    if (rv > 0) {
        if (FD_ISSET(sockFD, &readfds)) {
            recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
    }

}

对于今天的系统来说,这两种方法似乎都太慢了(对于上面的所有测试,我的cpu使用率约为2%)。我希望尽快将这个应用程序移到一些10GigE机器上并开始测试,但我显然无法使用这两种方法中的任何一种。有没有更快的方法可以检查?

我虽然这些应该是非阻塞的,但是由于要求超时,它们会在某种程度上阻塞;我看过this thread,但它不是我要求的答案。有没有方法我可以在调用它的那个时刻调用那些检查,等待读取数据,然后如果没有数据等待读取则立即返回?

作为一个副节点,我还没有读过recvfrom方法()来查看发布之前延迟的位置,但是我确实尝试了以下内容,因为它只需要30秒来更改代码,它导致最差的结果,低于1Mbps;

发件人:

    while (true)
    {
        // Continually send a frame then check for a frame, send a frame then check for a frame...
        sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
        recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
    }

5 个答案:

答案 0 :(得分:7)

我根本不会使用非阻止模式。只需在阻塞模式下使用一个线程。这样您只需要进行一次系统调用:recvfrom(),这样您就可以将上下文切换保存到内核中。

答案 1 :(得分:2)

正如您所想,您的性能遭受的原因是您限制自己每秒发送不超过1000个数据包。

如果您愿意使用两个线程,那么EJP's answer是最佳选择。如果您真的只想使用单个线程,最好的选择是使用select()poll()来告知您传输队列饱和时是否有事情要做。因此,您可以将套接字设置为非阻塞模式,或者在执行I / O时可以使用MSG_DONTWAIT标志。当您收到EAGAIN / EWOULDBLOCK通知时停止执行该I / O,然后再次阻止等待select()poll()中的相应事件(不设置超时)。在具有简化错误处理的伪代码中:

writeable = true;
readable = false;
make_nonblock(s);
for (;;) {
    if (readable) {
        while (recvfrom(s,...) > 0) {
            done = done_check();
        }
        if (done) break;
        assert(errno == EAGAIN);
        readable = false;
    }
    if (writeable) {
        while (sendto(s,...) > 0) {}
        assert(errno == EAGAIN);
        writeable = false;
    }
    poll_socket(s, &readable, &writeable);
}

答案 2 :(得分:2)

pollselect无需在任何时间段内屏蔽。如果timeoutpoll的{​​{1}}参数设置为零,则两个调用都会立即返回i / o可用性的指示。这消除了计时器的使用和随后的舍入。

注意,我不清楚为什么如果只监视一个文件描述符,这将比简单的非阻塞轮询读取快得多。我预计当多个FD在使用时,这种方法的任何好处都会开始产生,所以有趣的是你的测试发现了这一点。

答案 3 :(得分:1)

您可以尝试查看围绕C10k problem的讨论,了解一些信息。

我认为你可以试试epoll,但对于许多连接而言,这不仅仅是单插槽吞吐量。看看你使用它会得到什么结果会很有趣。

答案 4 :(得分:0)

select放在另一个帖子中可能会有所帮助。

主题1:

while (gRunning)
{
   sendResult = sendto(...)
}

主题2:

rv = select(n, &readfds, NULL, NULL, &tv);
if (rv > 0) {
    if (FD_ISSET(sockFD, &readfds)) {
        recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
        if (!strcmp(rxBuffer,"I QUIT")) {
            gRunning = FALSE;
        }
    }
}

选择线程应该在等待时屈服于CPU,可能会给发送者更多周期。