c ++中的非阻塞套接字写性能

时间:2012-02-20 16:16:32

标签: c++ linux performance sockets

我正在尝试衡量网络代码的性能,我得到的结果非常多样。到目前为止,我无法解释它,也许其他人将能够帮助或指出正确的方向。

所以我创建了一个套接字:

socket(AF_INET, SOCK_STREAM, 0);
int one = 1;
setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)));
current = fcntl(socket, F_GETFL);
fcntl(socket, F_SETFL, O_NONBLOCK | current);

我的消息总是大约200个字节。 发送消息的代码是:

uint64_t start (nanotimestamp());
unsigned char * buf;
... 
//build a message
//few calls to snprintf
//buffer is preallocated
...
write(socket, buf, size);
uint64_t end (nanotimestamp());

performance = end - start;

uint64_t nanotimestamp()
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
return now.tv_sec * 1e9 + now.tv_nsec;
}

代码在64位redhat 6上运行。用gcc 4.4编译 时间在20到80微秒之间变化很大,在极少数情况下> 100我们。

因此,如果写入的调用是非阻塞的,为什么我会看到这样的差异?

5 个答案:

答案 0 :(得分:2)

您应该考虑使用CLOCK_MONOTONIC进行此测量 - 它的开销明显低于获取CLOCK_REALTIME的开销。对于我的性能测量(我们需要纳秒级精度),我使用RDTSC计数器:

对于可以使用的英特尔系统,GCC 4.4+(不是100%肯定这个,4.6.1肯定会实现这一点):

#include <x86intrin.h>
uint64_t clock_count = __rdtsc();

如果不是:

extern "C" {
    __inline__ uint64_t rdtsc()
    {
        uint32_t lo, hi;
        __asm__ __volatile__ (
            "xorl %%eax,%%eax \n        cpuid"
            ::: "%rax", "%rbx", "%rcx", "%rdx");
        __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
        return (uint64_t)hi << 32 | lo;
    }
}

然后将时钟计数的增量除以CPU频率乘以CPU频率赫兹数将为您提供非常精确的测量,而成本仅为clock_gettime()

的一小部分

编辑:

现在回答实际问题:)

在您的代码中,您实际上正在测量两件事 - 构建消息并发送消息。您可以单独测量它们,也可以在块外移动数据。当你测量微秒时,写入数据是很昂贵的。

我认为问题在于组合snprintf()和缓存未命中。格式化函数的性能非常差,并且由于每次都在重建数据,因此有可能每隔一段时间就会出现缓存,这应该可以回答有关可变性的问题。

答案 1 :(得分:1)

已经提到过其他线程,硬件或软件中断的中断。

但是还有另外一件事要考虑。根据各种因素,非阻塞write()调用可能会采用非常不同的代码路径。例如。它可能需要分配额外的缓冲区,这需要时间,或者可能不需要。或者它可能决定数据应该立即发送,并直接进入金属&#34; (调用驱动程序将数据传送到网络接口进行传输)。

分配缓冲区需要时间,将数据传递到网络接口更是如此。

因此write()可能非常快(缓冲现有缓冲区中的数据),稍微慢一些(分配额外的缓冲区)或者#34;真的&#34;慢(直接走向金属)。

答案 2 :(得分:0)

您无法仅测量1次写入,因为在调用write时可能会暂停该进程(这可能会占用> 100us)。此外,进行系统调用可能会引入一些差异。

您需要更频繁地调用写入并测量所有这些调用的组合时间。

答案 3 :(得分:0)

  1. 最好只包围write()调用,以便知道执行“非阻塞”任务需要多长时间。
  2. 内核可以随时提供运行该代码的线程。这将在当前核心上安排另一个线程/进程。它将在以后重新安排您的流程。 20-80us可能是另一个进程的执行时间。 (虽然我更期待20-50毫秒,但这取决于你的内核及其配置)。

答案 4 :(得分:0)

将EAGAIN失败的write()调用与实际传输数据的调用区分开来会很有意思。这可能会导致很多差异。

我也想知道在这种情况下TCP_NODELAY是否真的能帮到你。也可能值得切换它。