【问题标题】:Unexpected bandwidth with TCP benchmarking in C使用 C 语言进行 TCP 基准测试的意外带宽
【发布时间】:2020-07-24 05:25:31
【问题描述】:

因此,我正在对 TCP 进行一些基准测试,以研究每个连接传输的数据量对结果带宽的影响。所以我用 C 写了一个服务器和一个客户端来衡量我需要什么。然后我使用 Python 脚本多次运行实验(为了获得 +/- 1% 的精度,我运行了 100 秒的测试。对于每个数据点,我运行了 33 次实验以获得一个不错的平均值。),不同的输入,并收集结果。

我得到的结果似乎是正确的(我什至可以观察到每个连接传输的数据量较高时的预期稳定期),但带宽仅为应有的 10%...

因为我只能访问一台计算机来运行此基准测试,所以我在 localhost 上进行测试,但这应该不是问题。

这是我的结果:

如您所见,我能获得的最佳带宽似乎略高于 300 MB/s...但是如果我使用 iperf 运行带宽测试(我确保使用相同的 TCP 窗口大小),在本地主机上,我得到大约 3 GB/s 的带宽。

这是我客户的代码:

int main(int argc, char const *argv[])
{
    unsigned int size;
    unsigned int timeout;
    int sockfd;
    struct sockaddr_in server_addr;

    if(argc < 4 || argc > 5 ){
        usage();
        exit(1);
    }

    const char * ip = "127.0.0.1";
    ip = argv[3];

    int port = PORT;
    if(argc == 5) {
        port = atoi(argv[4]);
    }

    size = atoi(argv[1]);
    timeout = atoi(argv[2]);

    unsigned int count = 0;

    struct timespec start, end;

    clock_gettime(CLOCK_MONOTONIC_RAW, &start);
    clock_gettime(CLOCK_MONOTONIC_RAW, &end);

    while((end.tv_sec - start.tv_sec) < timeout) {

        // socket create and varification
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd == -1) {
            perror("Could not create socket\n");
            exit(0);
        }

        bzero(&server_addr, sizeof(server_addr));

        // assign IP, PORT of the server
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr(ip);
        server_addr.sin_port = htons(port);

        // connect the client socket to server socket
        if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) != 0) {
            perror("connection with the server failed");
            exit(1);
        }

        unsigned int nread = 0;
        unsigned int nreadcum = 0;
        char* buf = malloc(size);
        char* bufbuf = buf;

        while(nreadcum < size){
            nread = read(sockfd, bufbuf, size-nreadcum);
            nreadcum += nread;
            bufbuf+=nread;
        }
        // close connection
        close(sockfd);
        count++;
        clock_gettime(CLOCK_MONOTONIC_RAW, &end);
        free(buf);
    }


    uint64_t sec = (end.tv_sec - start.tv_sec);
    double bandwidth = (count*size)/sec;

    printf("%u,%lf,%u,%lu\n", size, bandwidth, count, sec);

    return 0;
}

这是我的服务器的代码:

int serv_sock_fd;

int main(int argc, char const *argv[])
{
    int size;
    struct sockaddr_in serv_addr;
    struct sockaddr_in client_addr;
    int bound_port;

    if(argc != 2){
        usage();
        exit(1);
    }

    size = atoi(argv[1]);

    int serv_sock_fd = socket(AF_INET,SOCK_STREAM,0);
    int true = 1;
    setsockopt(serv_sock_fd,SOL_SOCKET,SO_REUSEADDR,&true,sizeof(int));
    if(serv_sock_fd == -1) {
        perror("Failed to open server socket");
        exit(1);
    }

    bzero(&serv_addr, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);

    // Bind socket to the chosen port
    if ((bound_port = bind(serv_sock_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))) <0){
        perror("Could not bind socket to local port");
        exit(1);
    }

    // Listen on the port
    if (listen(serv_sock_fd, 16))
    {
        perror("Could not listen");
        exit(1);
    }

    signal(SIGINT, sigint_handler);

    printf("Waiting for connection on %d ...\n", PORT);

    int returned = 1;


    while(returned) {
        printf(".\n");
        int new_socket_fd;
        unsigned int client_addr_len = sizeof(client_addr);
        if ((new_socket_fd = accept(serv_sock_fd, (struct sockaddr *)&client_addr,
                            &client_addr_len))<0) {
                perror("Could not accept client connection");
                exit(1);
            }
        printf("connection received, start sending ... ");
        char * payload = sequence_payload(size);
        returned = write(new_socket_fd, payload, size);
        printf("finished sending\n");
        printf("Returned value = %d\n", returned);
        close(new_socket_fd);
        free(payload);
    }


    close(serv_sock_fd);
    return 0;
}

char * sequence_payload(int size) {
    char * payload = malloc(size);
    for (int i = 0; i < size; i++)
    {
        payload[i] = i%256;
    }
    return payload;
}

基本上我的代码在做的是:

  • 对于服务器:等待客户端连接,发送所需大小的虚拟负载并关闭与该客户端的连接,重复。
  • 对于客户端:打开与服务器的连接,读取服务器发送的内容,直到服务器关闭连接,重复直到达到确定的超时。

要计算带宽,我可以做(number_of_connections_completed * size_transferred_by_connection) / duration_of_all_transfers。我使用 python 来计算带宽,以免在 C 中出现任何溢出。

TLDR: 我的 C 程序获得的带宽比本地主机上的带宽少 10 倍。这个问题的根源是什么?

【问题讨论】:

  • 您确定您的 iperf 结果是以字节为单位,而不是每秒位数吗?
  • 当您尝试测量吞吐量时,为什么每次迭代都使用mallocfreeconnectclose?你应该尽可能少地在你的计时循环中做,当然要尽量减少可避免的系统调用的数量。连接一次并发送大量数据。在循环外分配和初始化缓冲区一次
  • 您的测试需要多长时间?您的时间计算已完成,忽略了 tv_nsec 组件,如果您的测试每次运行需要 1,000 秒(但它一直很草率),这不会太重要,但如果它们需要 1 或 2 秒则非常重要。跨度>
  • 你有循环 while(nreadcum &lt; size){ nread = read(sockfd, bufbuf, size-nread); nreadcum += nread; bufbuf+=nread; } — 不应该在 read() 调用引用 nreadcum 中使用 size - nread 而不是 nread
  • @whitsundale 你完全误解了。我不是在谈论您编写的数据的大小,而是在谈论您在计时循环中包含的所有额外工作,尽管这不是您想要计时的事情。调用 send 任意次数,使用任何你喜欢的块大小,但不要每次都调用 mallocfree,除非你真的在分析你的分配器。不要每次都connectclose,保持已建立的 TCP 会话处于稳定状态,除非您实际上是在分析 TCP 启动。

标签: c tcp benchmarking bandwidth low-bandwidth


【解决方案1】:

mallocfree 是这里的主要问题。由于它们是系统调用,因此它们需要大量时间,并且由于我正在测量 TCP 的性能而不是内存分配的性能,mallocfree 应该在我的分析循环之外。同样,同样的事情也适用于服务器端循环中的printf,虽然没有malloc那么糟糕,但在测量TCP。

【讨论】:

  • malloc 和 free 本身不是系统调用。对于大分配,glibc 的 malloc 实现将只使用mmap(MAP_ANONYMOUS) 而不是移动中断(brk),并使用munmap 释放它而不是添加到用户空间空闲列表中。所以是的,在分配器在用户空间进行一些簿记之后,这些调用确实会导致系统调用。您可以使用strace ./my_program 进行验证,以查看它进行的所有系统调用。
猜你喜欢
  • 2011-01-21
  • 1970-01-01
  • 2010-12-16
  • 2011-04-07
  • 2011-03-28
  • 2018-09-09
  • 2017-07-24
  • 2010-11-14
  • 2011-07-18
相关资源
最近更新 更多