为什么 libcurl 多接口在我的某些系统上异常缓慢?

问题描述

我正在尝试使用 C++ 中的 libcurl 多接口编写一个小型应用程序,但遇到了一个问题,它的执行速度(远)比预期慢:针对静态 HTML 页面(由 Nginx 提供服务)的 49 个请求(任意数量)在我在互联网上租用的小型虚拟机上)它需要一个相当恒定的 32 秒。

针对 Google 或 GitHub 运行应用程序所需的时间与使用 hey 对静态页面进行基准测试的时间一样长,会产生超过 1500 个请求/秒,因此我很确定这不是服务器的问题。连续循环 50 次 curl 命令行应用程序的速度也相当快,总共只需 13 秒。

这是有问题的代码(为了可读性而使用的最少代码):

#include <curl/curl.h>

#include <memory>
#include <string>
#include <vector>


struct Request {
  std::shared_ptr<CURL> request;
};


int request(std::string url,CURLM *curlm,std::vector<Request> &requests) {
  Request r = {
    .request = std::shared_ptr<CURL>(curl_easy_init(),curl_easy_cleanup),};

  curl_easy_setopt(r.request.get(),CURLOPT_URL,url.c_str());

  curl_multi_add_handle(curlm,r.request.get());
  requests.push_back(r);

  return 0;
}

int main(__attribute__((unused)) int argc,char *argv[]) {
  curl_global_init(CURL_GLOBAL_ALL);

  std::vector<Request> requests;
  CURLM *multi = curl_multi_init();

  // generate an arbitrary amount of requests
  for(int i = 0; i < 49; i++)
    request(std::string(argv[1]),multi,requests);


  int running = 0;
  curl_multi_perform(multi,&running);

  do {
    int numfds = 0;

    curl_multi_perform(multi,&running);

    // select
    fd_set fdread,fdwrite,fdexcep;
    FD_ZERO(&fdread);
    FD_ZERO(&fdwrite);
    FD_ZERO(&fdexcep);

    struct timeval timeout = {
      .tv_sec = 0,.tv_usec = 500
    };

    curl_multi_fdset(multi,&fdread,&fdwrite,&fdexcep,&numfds);
    select(numfds+1,&timeout);

    // or poll
    //curl_multi_poll(multi,nullptr,1000,&numfds);
  } while(running > 0);

  return 0;
}

我在多个客户端系统上测试过:我的工作站是 Ubuntu 20.04,结果可以在 CentOS 8、Ubuntu 18.04 和 Debian 10(所有 libcurl 7.60 系列)上重现,但是在 CentOS 7(libcurl 7.29.0)上相同的代码在 3-5 秒内完成!除了我的工作站之外,我测试的所有这些系统都是全新安装的 VM,它们运行在具有相同网络配置的同一主机上。同时,互联网上的虚拟机(Debian 10、libcurl 7.64)与我预期的一样快,结果是亚秒级。

使用 Wireshark,我可以看到与网络服务器的所有连接都立即打开,但实际的 HTTP 请求以递增的间隔分批发送 - 最后一个在打开连接后约 30 秒后发送。

select() 的做事方式与使用 curl_multi_poll() 的做事方式之间也没有时间差异,我已经浏览了 cURL 文档,寻找可能提高性能的选项;启用流水线没有任何区别,禁用 HTTP2 和大多数限制(例如最大连接数)似乎认设置为无限制。

现在是有趣的部分:当我通过 strace 运行这个应用程序时,在任何本地 VM 上每次运行只需要 2-4 秒。起初我认为 strace 可能会破坏一些东西,但是如果我通过 shell 重定向丢弃 strace输出,它看起来应用程序运行正常,那么是什么导致的?

为什么使用 strace 会改善一切?什么可能导致这些系统的运行时间大不相同?

知道我缺少什么吗?这次我的 Google-fu 离开了我。

解决方法

curl_multi_fdset 只添加它自己的描述符,它不会归零或以其他方式删除任何其他描述符。您必须在 curl_multi_fdset

之前执行以下操作
FD_ZERO(&fdread);
FD_ZERO(&fdwrite);
FD_ZERO(&fdexcep);

如果 curl_multi_fdset 为 0,则 select 和进一步的 running 也没有意义。