Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cohttp.headers: use faster comparison #778

Merged
merged 11 commits into from
Jul 7, 2021
Merged

Conversation

mseri
Copy link
Collaborator

@mseri mseri commented Apr 23, 2021

I have benchmarked the latency of cohttp using https://github.com/ocaml-multicore/retro-httpaf-bench/tree/master/cohttp-lwt-unix and, with the new headers module, the measures are ~33% slower on my machine.

With this change, we seem to get ~10% slowdown compared to the old metrics on my laptop. Still suboptimal but it is an improvement.

Signed-off-by: Marcello Seri [email protected]

@dinosaure
Copy link
Member

It's weird that the standard implementation of String.equal was too slow comparing to what you did 😕 .

@mseri
Copy link
Collaborator Author

mseri commented Apr 23, 2021

It's weird that the standard implementation of String.equal was too slow comparing to what you did 😕 .

Actually I did not check that 😂 let me try

@mseri
Copy link
Collaborator Author

mseri commented Apr 23, 2021

These are very unscientific and unrealistic benchmarks, since they run on my laptop and test a very dumb server, but here they give practically equivalent results 🤷 I added a commit to use String.equal since it is cleaner and hopefully faster on more realistic examples

@anuragsoni
Copy link
Contributor

anuragsoni commented Jun 18, 2021

@mseri These are very unscientific and unrealistic benchmarks, since they run on my laptop and test a very dumb server, but here they give practically equivalent results

I have recently switched to using Cohttp types for my http 1.0/1.1 parser and I ran a small benchmark on one of my parser tests. The change in your PR does seem to offer improvements in my little microbenchmark when compared to the previous version on the headersv2 branch. That said there could potentially be further improvements if breaking changes are acceptable, ex: using case insensitive comparison for header keys instead of modifying user input via String.lowercase_ascii. It should offer similar behavior but it'd be a breaking change compared to the current Cohttp header module since if anyone relies on keys being lowercase (not that they should since the spec mentions that header keys' case shouldn't change any behavior), that would no longer be the case. I'll attach the microbenchmark runs from my test:

The parser is defined at: https://github.com/anuragsoni/h1/blob/d659ad37f1222523725e8f37f6250de549f64112/h1_parser/src/h1_parser.ml and the benchmark code is https://github.com/anuragsoni/h1/blob/d659ad37f1222523725e8f37f6250de549f64112/bench/main.ml

Note: Tests were run on an m1 macbook air

The payload is:

let req = "GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\
   Host: www.kittyhell.com\r\n\
   User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; \
   rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\
   Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\
   Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\
   Accept-Encoding: gzip,deflate\r\n\
   Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\
   Keep-Alive: 115\r\n\
   Connection: keep-alive\r\n\
   Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; \
   __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; \
   __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral\r\n\
   \r\n"

Results were as follows:

  1. Upstream Cohttp with the map based header implementation
┌───────────────────────┬────────────┬─────────┬──────────┬──────────┬────────────┐
│ Name                  │   Time/Run │ mWd/Run │ mjWd/Run │ Prom/Run │ Percentage │
├───────────────────────┼────────────┼─────────┼──────────┼──────────┼────────────┤
│ H1 (httparse example) │ 1_630.81ns │ 605.00w │    0.30w │    0.30w │    100.00% │
└───────────────────────┴────────────┴─────────┴──────────┴──────────┴────────────┘
  1. list based headers from feature/headerv2
┌───────────────────────┬────────────┬─────────┬──────────┬──────────┬────────────┐
│ Name                  │   Time/Run │ mWd/Run │ mjWd/Run │ Prom/Run │ Percentage │
├───────────────────────┼────────────┼─────────┼──────────┼──────────┼────────────┤
│ H1 (httparse example) │ 1_350.81ns │ 507.00w │    0.29w │    0.29w │    100.00% │
└───────────────────────┴────────────┴─────────┴──────────┴──────────┴────────────┘
  1. List based headers with String.equal as in this PR
┌───────────────────────┬────────────┬─────────┬──────────┬──────────┬────────────┐
│ Name                  │   Time/Run │ mWd/Run │ mjWd/Run │ Prom/Run │ Percentage │
├───────────────────────┼────────────┼─────────┼──────────┼──────────┼────────────┤
│ H1 (httparse example) │ 1_140.57ns │ 475.00w │    0.19w │    0.19w │    100.00% │
└───────────────────────┴────────────┴─────────┴──────────┴──────────┴────────────┘
  1. A version using a case insensitive comparison for header keys instead of making any modification to user input
┌───────────────────────┬──────────┬─────────┬──────────┬──────────┬────────────┐
│ Name                  │ Time/Run │ mWd/Run │ mjWd/Run │ Prom/Run │ Percentage │
├───────────────────────┼──────────┼─────────┼──────────┼──────────┼────────────┤
│ H1 (httparse example) │ 968.67ns │ 414.00w │    0.15w │    0.15w │    100.00% │
└───────────────────────┴──────────┴─────────┴──────────┴──────────┴────────────┘

The caseless comparison change can be seen at https://github.com/mirage/ocaml-cohttp/compare/feature/headersV2...anuragsoni:caseless-compare-header-key?expand=1

@mseri
Copy link
Collaborator Author

mseri commented Jun 23, 2021

Thanks a lot for these tests.

Since we are breaking the header module anyway, given the small difference, I think this is one of the instances in which we could go with it if we can justify it.

I don't personally tend to rely on the case of the keys, since I use the Header module functions for comparisons in general. I hope I am not too biased thinking that this is what most people do. And seeing how little needed to change in the tests, I think this breakage is subtle but should be minimal in practice (and we could still be very vocal and explicit about it).

I would like to hear some other bell on this. Is a 200 nanoseconds improvement over this PR worth the extra semantic change?

Since it seems that a lot of the latency cost is hidden somewhere in the guts of the IO module and Lwt, are we overdoing for the header module?

@mseri
Copy link
Collaborator Author

mseri commented Jun 23, 2021

PS If I had to cast a vote, I would vote for using @anuragsoni case-insensitive patch.

@anuragsoni
Copy link
Contributor

Since it seems that a lot of the latency cost is hidden somewhere in the guts of the IO module and Lwt, are we overdoing for the header module?

There are definitely other improvements that can be made to cohttp that'll make more of an impact :) I think your String.equal change would be good to merge. As for the case-insensitive comparisons, i agree that even if its technically a breaking change i'm not sure if relying on the case of http keys was a safe idea at any point! That said, there are other more high impact changes that should help with the latency issues so i'd probably lean towards keeping whichever implementation feels better from a maintainability perspective :)

@mseri
Copy link
Collaborator Author

mseri commented Jun 25, 2021

I think your solution is also low maintenance, it is very close to my first commit fwiw :)

@lyrm
Copy link
Contributor

lyrm commented Jun 25, 2021

It seems like a good (and cheap !) improvement 👍.

Signed-off-by: Marcello Seri <[email protected]>
@mseri
Copy link
Collaborator Author

mseri commented Jun 30, 2021

I have added your change here, and now I also remember why I had ditched the case-insensitive comparison when working on my initial PR. There are a number of property tests that need to be fixed :P

@anuragsoni
Copy link
Contributor

There are a number of property tests that need to be fixed :P

Thanks for pulling in the commit here. I can help updating the necessary tests later in the week if that works :)

Signed-off-by: Marcello Seri <[email protected]>
@mseri
Copy link
Collaborator Author

mseri commented Jun 30, 2021

Oh, thanks a lot. I fixed the immediate one. We only need to update the Header.mem has the same behavior than List.mem_assoc test, it would be great if you have time

@anuragsoni
Copy link
Contributor

We only need to update the Header.mem has the same behavior than List.mem_assoc test, it would be great if you have time

I proposed a PR to this branch on your fork mseri#2
I believe these were the last 2 test failures.

@mseri
Copy link
Collaborator Author

mseri commented Jul 7, 2021

Thanks all for the help

@mseri mseri merged commit d67a184 into mirage:feature/headersV2 Jul 7, 2021
@mseri mseri deleted the parser branch July 7, 2021 13:28
mseri added a commit to mseri/opam-repository that referenced this pull request Dec 15, 2021
…wt-unix, cohttp-lwt-jsoo and cohttp-async (5.0.0)

CHANGES:

- Cohttp.Header: new implementation (lyrm mirage/ocaml-cohttp#747)

  + New implementation of Header modules using an associative list instead of a map, with one major semantic change (function ```get```, see below), and some new functions (```clean_dup```, ```get_multi_concat```)
  + More Alcotest tests as well as fuzzing tests for this particular module.

  ### Purpose

  The new header implementation uses an associative list instead of a map to represent headers and is focused on predictability and intuitivity: except for some specific and documented functions, the headers are always kept in transmission order, which makes debugging easier and is also important for [RFC7230§3.2.2](https://tools.ietf.org/html/rfc7230#section-3.2.2) that states that multiple values of a header must be kept in order.

  Also, to get an intuitive function behaviour, no extra work to enforce RFCs is done by the basic functions. For example, RFC7230§3.2.2 requires that a sender does not send multiple values for a non list-value header. This particular rule could require the ```Header.add``` function to remove previous values of non-list-value headers, which means some changes of the headers would be out of control of the user. With the current implementation, an user has to actively call dedicated functions to enforce such RFCs (here ```Header.clean_dup```).

  ### Semantic changes
  Two functions have a semantic change : ```get``` and ```update```.

  #### get
  ```get``` was previously doing more than just returns the value associated to a key; it was also checking if the searched header could have multiple values: if not, the last value associated to the header was returned; otherwise, all the associated values were concatenated and returned. This semantics does not match the global idea behind the new header implementation, and would also be very inefficient.

  + The new ```get``` function only returns the last value associated to the searched header.
  + ```get_multi_concat``` function has been added to get a result similar to the previous ```get``` function.

  #### update
  ```update``` is a pretty new function (mirage/ocaml-cohttp#703) and changes are minor and related to ```get``` semantic changes.

  + ```update h k f``` is now modifying only the last occurrences of the header ```k``` instead of all its occurrences.
  + a new function ```update_all``` function has been added and work on all the occurrences of the updated header.

  ### New functions :

  + ```clean_dup```  enables the user to clean headers that follows the {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2} (no duplicate, except ```set-cookie```)
  + ```get_multi_concat``` has been added to get a result similar to the previous ```get``` function.

- Cohttp.Header: performance improvement (mseri, anuragsoni mirage/ocaml-cohttp#778)
  **Breaking** the headers are no-longer lowercased when parsed, the headers key comparison is case insensitive instead.

- cohttp-lwt-unix: Adopt ocaml-conduit 5.0.0 (smorimoto mirage/ocaml-cohttp#787)
  **Breaking** `Conduit_lwt_unix.connect`'s `ctx` param type chaged from `ctx` to  `ctx Lazy.t`

- cohttp-mirage: fix deprecated fmt usage (tmcgilchrist mirage/ocaml-cohttp#783)
- lwt_jsoo: Use logs for the warnings and document it (mseri mirage/ocaml-cohttp#776)
- lwt: Use logs to warn users about leaked bodies and document it (mseri mirage/ocaml-cohttp#771)
- lwt, lwt_unix: Improve use of logs and the documentation, fix bug in the Debug.enable_debug function (mseri mirage/ocaml-cohttp#772)
- lwt_jsoo: Fix exception on connection errors in chrome (mefyl mirage/ocaml-cohttp#761)
- lwt_jsoo: Fix `Lwt.wakeup_exn` `Invalid_arg` exception when a js
  stack overflow happens in the XHR completion handler (mefyl mirage/ocaml-cohttp#762).
- lwt_jsoo: Add test suite (mefyl mirage/ocaml-cohttp#764).
mseri added a commit to mseri/opam-repository that referenced this pull request Dec 15, 2021
…wt-unix, cohttp-lwt-jsoo and cohttp-async (5.0.0)

CHANGES:

- Cohttp.Header: new implementation (lyrm mirage/ocaml-cohttp#747)

  + New implementation of Header modules using an associative list instead of a map, with one major semantic change (function ```get```, see below), and some new functions (```clean_dup```, ```get_multi_concat```)
  + More Alcotest tests as well as fuzzing tests for this particular module.

  ### Purpose

  The new header implementation uses an associative list instead of a map to represent headers and is focused on predictability and intuitivity: except for some specific and documented functions, the headers are always kept in transmission order, which makes debugging easier and is also important for [RFC7230§3.2.2](https://tools.ietf.org/html/rfc7230#section-3.2.2) that states that multiple values of a header must be kept in order.

  Also, to get an intuitive function behaviour, no extra work to enforce RFCs is done by the basic functions. For example, RFC7230§3.2.2 requires that a sender does not send multiple values for a non list-value header. This particular rule could require the ```Header.add``` function to remove previous values of non-list-value headers, which means some changes of the headers would be out of control of the user. With the current implementation, an user has to actively call dedicated functions to enforce such RFCs (here ```Header.clean_dup```).

  ### Semantic changes
  Two functions have a semantic change : ```get``` and ```update```.

  #### get
  ```get``` was previously doing more than just returns the value associated to a key; it was also checking if the searched header could have multiple values: if not, the last value associated to the header was returned; otherwise, all the associated values were concatenated and returned. This semantics does not match the global idea behind the new header implementation, and would also be very inefficient.

  + The new ```get``` function only returns the last value associated to the searched header.
  + ```get_multi_concat``` function has been added to get a result similar to the previous ```get``` function.

  #### update
  ```update``` is a pretty new function (mirage/ocaml-cohttp#703) and changes are minor and related to ```get``` semantic changes.

  + ```update h k f``` is now modifying only the last occurrences of the header ```k``` instead of all its occurrences.
  + a new function ```update_all``` function has been added and work on all the occurrences of the updated header.

  ### New functions :

  + ```clean_dup```  enables the user to clean headers that follows the {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2} (no duplicate, except ```set-cookie```)
  + ```get_multi_concat``` has been added to get a result similar to the previous ```get``` function.

- Cohttp.Header: performance improvement (mseri, anuragsoni mirage/ocaml-cohttp#778)
  **Breaking** the headers are no-longer lowercased when parsed, the headers key comparison is case insensitive instead.

- cohttp-lwt-unix: Adopt ocaml-conduit 5.0.0 (smorimoto mirage/ocaml-cohttp#787)
  **Breaking** `Conduit_lwt_unix.connect`'s `ctx` param type chaged from `ctx` to  `ctx Lazy.t`

- cohttp-mirage: fix deprecated fmt usage (tmcgilchrist mirage/ocaml-cohttp#783)
- lwt_jsoo: Use logs for the warnings and document it (mseri mirage/ocaml-cohttp#776)
- lwt: Use logs to warn users about leaked bodies and document it (mseri mirage/ocaml-cohttp#771)
- lwt, lwt_unix: Improve use of logs and the documentation, fix bug in the Debug.enable_debug function (mseri mirage/ocaml-cohttp#772)
- lwt_jsoo: Fix exception on connection errors in chrome (mefyl mirage/ocaml-cohttp#761)
- lwt_jsoo: Fix `Lwt.wakeup_exn` `Invalid_arg` exception when a js
  stack overflow happens in the XHR completion handler (mefyl mirage/ocaml-cohttp#762).
- lwt_jsoo: Add test suite (mefyl mirage/ocaml-cohttp#764).
mseri added a commit to mseri/opam-repository that referenced this pull request Dec 17, 2021
…wt-unix, cohttp-lwt-jsoo and cohttp-async (5.0.0)

CHANGES:

- Cohttp.Header: new implementation (lyrm mirage/ocaml-cohttp#747)

  + New implementation of Header modules using an associative list instead of a map, with one major semantic change (function ```get```, see below), and some new functions (```clean_dup```, ```get_multi_concat```)
  + More Alcotest tests as well as fuzzing tests for this particular module.

  ### Purpose

  The new header implementation uses an associative list instead of a map to represent headers and is focused on predictability and intuitivity: except for some specific and documented functions, the headers are always kept in transmission order, which makes debugging easier and is also important for [RFC7230§3.2.2](https://tools.ietf.org/html/rfc7230#section-3.2.2) that states that multiple values of a header must be kept in order.

  Also, to get an intuitive function behaviour, no extra work to enforce RFCs is done by the basic functions. For example, RFC7230§3.2.2 requires that a sender does not send multiple values for a non list-value header. This particular rule could require the ```Header.add``` function to remove previous values of non-list-value headers, which means some changes of the headers would be out of control of the user. With the current implementation, an user has to actively call dedicated functions to enforce such RFCs (here ```Header.clean_dup```).

  ### Semantic changes
  Two functions have a semantic change : ```get``` and ```update```.

  #### get
  ```get``` was previously doing more than just returns the value associated to a key; it was also checking if the searched header could have multiple values: if not, the last value associated to the header was returned; otherwise, all the associated values were concatenated and returned. This semantics does not match the global idea behind the new header implementation, and would also be very inefficient.

  + The new ```get``` function only returns the last value associated to the searched header.
  + ```get_multi_concat``` function has been added to get a result similar to the previous ```get``` function.

  #### update
  ```update``` is a pretty new function (mirage/ocaml-cohttp#703) and changes are minor and related to ```get``` semantic changes.

  + ```update h k f``` is now modifying only the last occurrences of the header ```k``` instead of all its occurrences.
  + a new function ```update_all``` function has been added and work on all the occurrences of the updated header.

  ### New functions :

  + ```clean_dup```  enables the user to clean headers that follows the {{:https://tools.ietf.org/html/rfc7230#section-3.2.2} RFC7230§3.2.2} (no duplicate, except ```set-cookie```)
  + ```get_multi_concat``` has been added to get a result similar to the previous ```get``` function.

- Cohttp.Header: performance improvement (mseri, anuragsoni mirage/ocaml-cohttp#778)
  **Breaking** the headers are no-longer lowercased when parsed, the headers key comparison is case insensitive instead.

- cohttp-lwt-unix: Adopt ocaml-conduit 5.0.0 (smorimoto mirage/ocaml-cohttp#787)
  **Breaking** `Conduit_lwt_unix.connect`'s `ctx` param type chaged from `ctx` to  `ctx Lazy.t`

- cohttp-mirage: fix deprecated fmt usage (tmcgilchrist mirage/ocaml-cohttp#783)
- lwt_jsoo: Use logs for the warnings and document it (mseri mirage/ocaml-cohttp#776)
- lwt: Use logs to warn users about leaked bodies and document it (mseri mirage/ocaml-cohttp#771)
- lwt, lwt_unix: Improve use of logs and the documentation, fix bug in the Debug.enable_debug function (mseri mirage/ocaml-cohttp#772)
- lwt_jsoo: Fix exception on connection errors in chrome (mefyl mirage/ocaml-cohttp#761)
- lwt_jsoo: Fix `Lwt.wakeup_exn` `Invalid_arg` exception when a js
  stack overflow happens in the XHR completion handler (mefyl mirage/ocaml-cohttp#762).
- lwt_jsoo: Add test suite (mefyl mirage/ocaml-cohttp#764).
@gasche
Copy link
Contributor

gasche commented Jan 6, 2022

I came here from the Discuss release announcement, always curious about some performance hacking. It looks like the fun is in the following caseless_equal function:

let caseless_equal a b =
  if a == b then true
  else
    let len = String.length a in
    len = String.length b
    &&
    let stop = ref false in
    let idx = ref 0 in
    while (not !stop) && !idx < len do
      let c1 = String.unsafe_get a !idx in
      let c2 = String.unsafe_get b !idx in
      if Char.lowercase_ascii c1 <> Char.lowercase_ascii c2 then stop := true;
      incr idx
    done;
    not !stop

I have a suggestion and an idle idea:

  • Suggestion: setup a microbenchmark for this function so that you can easily evaluate the impact of performance changes -- it sounds like it is performance-critical for cohttp.
  • Idle idea: I think you could speedup the function by using word-sized reads of both string: if the words at (word-aligned) index i are equal, you can skip the lowercase comparison of the 4/8 characters of the word.

Word-sized reads are available in the standard library as String.get_int{32,64}_ne. (They use int32, int64, so one needs to check that the uses are correctly unboxed, but I expect they would.)

@gasche
Copy link
Contributor

gasche commented Jan 6, 2022

(Technically it's possible to go even further as those functions perform bound checks and also assume that the memory-access is not word-aligned, so one could generate simpler unsafe code, but it's probably not worth it, and certainly not before trying with the exposed, safe get_int<n> functions.)

@gasche
Copy link
Contributor

gasche commented Jan 6, 2022

Hacking on a quick benchmark is less demanding or less boring than most of my other day tasks, so I couldn't resist.

module Orig = struct
let caseless_equal a b =
  if a == b then true
  else
    let len = String.length a in
    len = String.length b
    &&
    let stop = ref false in
    let idx = ref 0 in
    while (not !stop) && !idx < len do
      let c1 = String.unsafe_get a !idx in
      let c2 = String.unsafe_get b !idx in
      if Char.lowercase_ascii c1 <> Char.lowercase_ascii c2 then stop := true;
      incr idx
    done;
    not !stop
end

module New = struct
let caseless_equal a b =
  if a == b then true
  else
    let len = String.length a in
    len = String.length b
    &&
    let rec word_loop a b i len = 
      if i = len then true
      else
        let i' = i + 8 in
        if i' > len then byte_loop a b i len
        else begin
          if String.get_int64_ne a i = String.get_int64_ne b i
          then word_loop a b i' len
          else
            byte_loop a b i i'
            && word_loop a b i' len
        end
    and byte_loop a b i len =
      let c1 = String.unsafe_get a i in
      let c2 = String.unsafe_get b i in
      Char.lowercase_ascii c1 = Char.lowercase_ascii c2
      && (let i' = i + 1 in i' = len || byte_loop a b i' len)
    in
    word_loop a b 0 len
end


let impls = [
  "orig", Orig.caseless_equal;
  "new", New.caseless_equal;
]

let () =
  match List.assoc Sys.argv.(1) impls,
        int_of_string Sys.argv.(2),
        Sys.argv.(3),
        Sys.argv.(4)
  with
  | exception _ ->
    prerr_endline
      "Usage:\n./test [orig|new] <iter_count> '<input1>' '<input2>'";
    exit 2
  | caseless_equal, iter_count, input1, input2 ->

    (* some quick correctnss testing *)
    assert (caseless_equal "foo" "foo");
    assert (caseless_equal "transfer-encoding" "traNsfer-eNcoding");
    assert (caseless_equal "transfer-encoding" "transfer-Encoding");
    assert
      (* dash missing on the right-hand side*)
      (not (caseless_equal "transfer-encoding" "traNsfer eNcoding"));

    (* actual benchmark *)
    for i = 1 to iter_count do
      for i = 1 to 1_000 do
        ignore (caseless_equal input1 input2);
      done;
    done;

    Printf.printf "%b\n%!" (caseless_equal input1 input2)
$ time ./test orig 100_000 'transfer-encoding' 'transfer-encoding'
true

real	0m5.191s
$ time ./test new 100_000 'transfer-encoding' 'transfer-encoding'
true

real	0m1.671s

@rgrinberg
Copy link
Member

Would you like to throw up a PR gasche?

gasche added a commit to gasche/ocaml-cohttp that referenced this pull request Jan 6, 2022
gasche added a commit to gasche/ocaml-cohttp that referenced this pull request Jan 6, 2022
gasche added a commit to gasche/ocaml-cohttp that referenced this pull request Jan 6, 2022
gasche added a commit to gasche/ocaml-cohttp that referenced this pull request Jan 6, 2022
@gasche
Copy link
Contributor

gasche commented Jan 6, 2022

@rgrinberg sure, done. But I don't know how to benchmark cohttp, so it would help if someone else could do that.

@rgrinberg
Copy link
Member

rgrinberg commented Jan 6, 2022 via email

rgrinberg pushed a commit to gasche/ocaml-cohttp that referenced this pull request Jan 7, 2022
rgrinberg pushed a commit to gasche/ocaml-cohttp that referenced this pull request Jan 7, 2022
mseri pushed a commit to gasche/ocaml-cohttp that referenced this pull request Jan 7, 2022
mseri pushed a commit to gasche/ocaml-cohttp that referenced this pull request Jan 8, 2022
bikallem pushed a commit to bikallem/ocaml-cohttp that referenced this pull request Jan 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants