Sure, so the first thing I did was time the NIF calls, just using timer:tc/1, which I think is good enough here (if you run it a few times) because we’re not benchmarking, we just want a rough idea. You should look at the shortest times (assuming a CPU bound workload), since longer times are probably caused by the OS scheduler context switching. What I saw is that erqwest_nif:make_client/2 is consistently very slow (~30 ms). I checked what it was doing with perf and it was spending its time in openssl, which we can assume means it’s CPU bound, so I marked it as a CPU-bound dirty NIF.
For erqwest_nif:req/1, times were generally well under 1 ms, which makes sense because all it’s doing is queueing something to be processed by another thread. I went looking for any edge cases which might cause it to consistently take more than 1 ms, and found that it did when the request body was large. This is because it copies the body into a Vec<u8>. I considered marking this NIF as dirty too, however some benchmarking showed that this caused a ~30% slowdown in the (probably more common) case that the request body is small, so I looked for another solution. It turns out that copying a binary is very cheap, since binaries over 64-bytes are reference counted. So I changed the code to just copy the binary/iodata to an OwnedEnv, and decode it on a tokio thread where we have no execution time restrictions.
(I’m getting told my post has too many links so I’m splitting it up)






















