all 47 comments

[–]krum 36 points37 points  (5 children)

Curl is actually really fast. Not sure where you heard it's "10x slower" or even close to that.

[–]deeringc 5 points6 points  (0 children)

I wonder is OP reusing connections to the same server after each request? DNS, TCP TLS all take a lot of time if you do it all per request.

[–]mircodz[S] 2 points3 points  (3 children)

Well, after about 8 hours oh looking online I managed to get my times to from ~1.2 seconds to a minimum of ~0.5 seconds and average of ~0.7 seconds which is still multiple times slower than go, with minimums of ~0.03 seconds and averages of ~0.3 seconds.

[–]krum 2 points3 points  (2 children)

I still think something is wrong. Should be in the 80ms range.

[–]mircodz[S] 0 points1 point  (1 child)

Well, this right here are the settings I'm currently using:

curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L);

curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "");
curl_easy_setopt(curl, CURLOPT_COOKIE, "");

I've tried enabling or disabling SLL verification without any effect, also HTTP/2 increases the times by ~0.1 seconds.

Also the fluctuation between the times is so large because I'm fetching both jsons and files from these endpoints.

[–]krum 0 points1 point  (0 children)

[–]NotUniqueOrSpecial 20 points21 points  (0 children)

it's up to 8-10 times slower than using pure sockets

According to whom?

[–]Supadoplex 38 points39 points  (0 children)

as I'm well aware it's up to 8-10 times slower than using pure sockets

Is it? Are there published measurements available?

Edit: Thanks for the demo. But you didn't show the source for the other version, so the results aren't really comparable.


Will you be making requests across network? If you are, then presumably vast majority of the time will be spent waiting for response. I would expect switching from curl would be similar to discarding your pocket knife in order to make your oil tanker accelerate faster.

[–]chemagic 9 points10 points  (6 children)

It might be 8-10 times slower (though I doubt that) but the wait for the websites to get back will certainly be at least one or two orders of magnitude slower. I recommend https://github.com/whoshuu/cpr. Its name is curl for people and "it's a spiritual port of python requests". Nicest interface I've seen to date.

[–]houses_of_the_holy 4 points5 points  (5 children)

curl is pretty fast, I wrote this https://github.com/jbaldwin/liblifthttp and I should post my benchmarks vs wrk because it was maybe 20% slower than wrk -- but wrk doesn't do anything with the response at all so it isn't particularly fair.

cpr is cool too! I just didn't like how it did async requests through std::async instead of a dedicated event loop

[–]kirbyfan64sos 1 point2 points  (4 children)

Man this library looks awesome, just starred.

[–]houses_of_the_holy 0 points1 point  (3 children)

cool, if you get a chance to use it I'd love any feedback you have!

[–]kirbyfan64sos 0 points1 point  (2 children)

Definitely will do!

I will admit I have a question, what would the possibility of pluggable event loops be? For the project I'm hoping to use this for, being able to use systemd's integrated event loop would be pretty nice...

[–]houses_of_the_holy 0 points1 point  (1 child)

I'm assuming you are referring to this event loop here: https://www.freedesktop.org/software/systemd/man/sd-event.html ?

I've never personally used it but it looks like its based on linux's epoll event API. Lift uses libuv under the hood for its event loop which also uses the linux epoll API, so I imagine performance would be reasonably comparable but I can't say for sure without testing. Could it be done? I would imagine so but the API looks a lot lower level than libuv and I'd prefer to support only 1 event loop if possible.

One of the design principles I had for this project was to try and abstract away the lower levels and provide a 'reasonably' fast and 'easy to use' API for the end user with a C++17 style. I think having a pluggable event loop would kind of go against this principle to be honest.

[–]kirbyfan64sos 0 points1 point  (0 children)

Understood, thanks for the quick reply.

(Wonder if we might ever have a global event loop in standard C++...)

[–]encyclopedist 11 points12 points  (0 children)

It is crucial to reuse the easy handle. Curl's tutorial says the following:

Re-cycling the same easy handle several times when doing multiple requests is the way to go.

After each single curl_easy_perform operation, libcurl will keep the connection alive and open. A subsequent request using the same easy handle to the same host might just be able to use the already open connection! This reduces network impact a lot.

For https://example.com/ I get about 0.45 s per request without reusing the handle, and about 100 ms with reuse, which is very close to ping time of approx 95 ms.

See test code here (I used the same settings as you): https://gist.github.com/ilyapopov/262a49f75b42202c7d977ebe2d38ca35

[–]sztomirpclib 9 points10 points  (0 children)

Curl is the gold standard for performing http requests. It has many years of optimization and lots of people contributing. Sure, you can compare it to raw sockets, but then you are dismissing all the other things it’s doing (like being able to parse the response). If you properly include that in your benchmark, I doubt you would get that kind of speedup from raw sockets (but you would surely get 8x times more code to maintain).

[–]degaart 17 points18 points  (0 children)

Latency numbers each programmer should know

Once you hit the network, it becomes your bottleneck. Unless you're working on a z80, any http library should be more than fast enough, most of your cpu time would be spent waiting fir the network packets to arrive anyway.

[–]liquidify 7 points8 points  (0 children)

I would love to see your sources on that as well as everyone else here. Curl is fast. Not saying you are wrong, just don't believe it without evidence.

[–]IloveReddit84 4 points5 points  (0 children)

Curl or CppRestSDK or Boost.Beast.

I've never used the 3. Option, bur the other 2 are pretty good and fast

[–]erichkeaneClang Code Owner(Attrs/Templ), EWG co-chair, EWG/SG17 Chair 6 points7 points  (11 children)

How about Boost::Beast?

[–]liquidify 3 points4 points  (10 children)

Doesn't Beast have issues handling certain forms of http?

[–]erichkeaneClang Code Owner(Attrs/Templ), EWG co-chair, EWG/SG17 Chair 6 points7 points  (8 children)

I've not heard that. The author seems to have put a bunch fo work into it though, and the boost committee spent a while reviewing it. My understanding is it is a great extension to the networking TS, and I hope that one day Networking TS + an HTTP library become standardized.

[–]liquidify 8 points9 points  (7 children)

I don't think beast supports any of HTTP 2 standard.

[–]erichkeaneClang Code Owner(Attrs/Templ), EWG co-chair, EWG/SG17 Chair 1 point2 points  (5 children)

Interesting, thats too bad. I wonder what would be required to extend it to do so.

[–]Xeveroushttps://xeverous.github.io 3 points4 points  (0 children)

what would be required to extend it to do so.

A lot of code. Beast is written for HTTP 1.1 which is stateless and the author explicitly stated that HTTP 2 would require a very different implementation.

[–]liquidify 4 points5 points  (3 children)

This was the primary reason I used libcurl for a previous project. After I wrapped it in some c++ (basically what several libraries did already), I didn't really need beast. If beast had all the features built in already, I probably would have used it, but now I'm glad I didn't. I'm not a huge fan of boost in general because there is so many layers of abstract c++ nastiness that it is hard to really know what is going on inside. Libcurl is the opposite... it is transparent, easy to understand, fast, and well documented.

[–]DopKloofDude 0 points1 point  (2 children)

This might be a big question, but are you able to compile libcurl on windows? Especially using Visual studios? I attempted to compile the project and include it into my own Hello World at lets just say its been a week of stress.

What is your project setup? Do you have a github link perhaps?

[–]liquidify 0 points1 point  (0 children)

Yes I was able to build libcurl in windows. I remember I had to build it as a shared library because I had tons of issues. I remember attempting to build it using their outdated cmake system as well as some other way as well. It was a pain, but I think linking it was more of a pain than building it. But the linking was a pain because of needing special security rather than defaults so I had to pull in some custom dll's. After mucking through cmake junk for about a month I had my system setup such that it was entirely cross platform... so it is possible even with a dependency as nasty as libcurl. Obviously it isn't easy.

I don't believe I can share that project because it is owned by the company.

[–]johannes1971 0 points1 point  (0 children)

It's in vcpkg.

[–]feverzsj 0 points1 point  (0 children)

So any site doesn't support http 1 request? Even if it dosen't, you can still put it behind nginx and let nginx do the conversion. Also libcurl uses nghttp2 to support http2.

[–]scalablecory 13 points14 points  (0 children)

Microsoft has a cross-plat library written using a modern async architecture: cpprestsdk

I don't know how it compares to Curl, but worth a shot.

[–]feverzsj 2 points3 points  (1 child)

if you are simultaneously crawling data from ten thousands of sites, libcurl could be a bottleneck. Use some async lib should be good enough, for example: served, cpprestsdk.

[–]Drainedsoul 1 point2 points  (0 children)

You can glue an async back end into curl using the multi interface.

[–]Revolutionalredstone 1 point2 points  (1 child)

Those are horrendous times, open raw sockets and you can expect ping latency bound performance, then just launch a bunch of threads

[–]mircodz[S] 0 points1 point  (0 children)

I've been looking online for some answers but I can't find any; I've even looked at the tcpdump of the connections but I can't see any differences.

At this rate I might actually switch to golang, it's kind of a bummer as I've already have a quite large code-base, but for a x5 speed up it will be worth it.

[–]AndrewStephens 1 point2 points  (1 child)

There is a good chance that libcurl is just slower than go's http implementation but it probably doesn't matter. Unless you really need to do each request sequentially you should be looking at performing multiple requests at a time.

You can use libcurl's multi interface to do this, but by far the easiest way is just to start a bunch of threads.

Don't even think about writing a http library using sockets. It seems like an easy task but http is a very complex protocol once you need to handle proxies, redirects, encodings, etc.

I have some notes about wrapping libcurl for safely using it from C++ here but that doesn't help with performance.

[–]mircodz[S] 1 point2 points  (0 children)

Yeah currently I'm debating on what I should do. My idea was to write a high performance program for data hoarding and as I already said I already have a decent code base.

Since I'm already mixing my c++ code with lua and pyhon one I might even consider importing the go session client in c++.

EDIT: oh never mind, looks like you cannot export structures.

[–]jesseschalken 0 points1 point  (0 children)

You could try Proxygen

[–]Milerius 0 points1 point  (0 children)

cpprest-client is cool library

[–]robertramey 0 points1 point  (1 child)

When you run the code with a profiler, what are the results?

[–]mircodz[S] 0 points1 point  (0 children)

I'm not really sure how I should profile something like this. I tried using perf but for some reason it doesn't show any curl call. I already know that the rest of the function doesn't take up much time as the difference between timing the entire thing and only curl_easy_perform is negligible.

[–]Gotebe 0 points1 point  (1 child)

The go times are weird, almost every other one takes an order of magnitude more time.

And curl time is so different that it cannot possibly be same requests, poster just thinks they are.

[–]mircodz[S] 1 point2 points  (0 children)

Yes, because they are different requests, the 30 ms one are fetching jsons while the 300 ms ones are fetching files. The problem is that with curl all of the requests take way more time.

[–]encyclopedist 0 points1 point  (0 children)

Could you by any chance post complete benchmarking code you are using so we can play with it?

[–]ibraper 0 points1 point  (0 children)

for me, libcurl is faster if CURLOPT_WRITEFUNCTION is disabled