Very cool!
I looked at bench/bench_angstrom.ml and made some modifications to the “fair” benchmark so it is IMO closer to the angstrom benchmark - that is, convert numbers to float while parsing; factor out whitespace skipping; use the unfused sep_by over sep_by_take_span; use the same character predicates (probably insignificant)
diff --git a/bench/bench_angstrom.ml b/bench/bench_angstrom.ml
index 0c26b32..f8c5104 100644
--- a/bench/bench_angstrom.ml
+++ b/bench/bench_angstrom.ml
@@ -1,8 +1,8 @@
open Benchmark
let json_input = {|[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]|}
-let is_digit_or_sign c = (c >= '0' && c <= '9') || c = '-' || c = '.'
-let is_ws c = c = ' ' || c = '\t' || c = '\n' || c = '\r'
+let is_digit_or_sign = function '0' .. '9' | '-' | '.' -> true | _ -> false
+let is_ws = function ' ' | '\t' | '\n' | '\r' -> true | _ -> false
module Parseff_JSON = struct
let[@inline always] float_of_span (s : Parseff.span) =
@@ -16,9 +16,6 @@ module Parseff_JSON = struct
else
float_of_string (Parseff.span_to_string s)
- let[@inline] float_of_span_fair (s : Parseff.span) =
- float_of_string (Parseff.span_to_string s)
-
let json_array () =
Parseff.skip_while_then_char is_ws '[';
Parseff.skip_while is_ws;
@@ -27,11 +24,17 @@ module Parseff_JSON = struct
Parseff.skip_while_then_char is_ws ']';
elements
+ let number () =
+ let s = Parseff.take_while is_digit_or_sign in
+ float_of_string s
+
+ let ws () = Parseff.skip_while is_ws
+
let json_array_fair () =
Parseff.skip_while_then_char is_ws '[';
- Parseff.skip_while is_ws;
- let spans = Parseff.sep_by_take_span is_ws ',' is_digit_or_sign in
- let elements = List.map float_of_span_fair spans in
+ ws ();
+ let sep () = ws (); ignore (Parseff.char ','); ws () in
+ let elements = Parseff.sep_by number sep () in
Parseff.skip_while_then_char is_ws ']';
elements
@@ -53,10 +56,10 @@ end
module Angstrom_JSON = struct
open Angstrom
- let ws = skip_while (function ' ' | '\t' | '\n' | '\r' -> true | _ -> false)
+ let ws = skip_while is_ws
let number =
- take_while1 (function '0' .. '9' | '-' | '.' -> true | _ -> false)
+ take_while1 is_digit_or_sign
>>| float_of_string
let json_array =
I found that I missed the _ *> _ combinator from angstrom a bit - I had to make a local binding for the separator. I also had to be a bit careful where to put (). Overall not a big deal.
With that I found that angstrom and parseff were more comparable in the “fair” case:
Benchmarking Parseff vs Angstrom
==================================
Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Latencies for 100000 iterations of "Parseff (span)", "Parseff (fair)", "Angstrom" (3 runs):
Parseff (span): 0.03 WALL ( 0.03 usr + 0.00 sys = 0.03 CPU, minor = 185.60 MB, major = 0 B) @ 3268080.66/s (n=100000)
(warning: too few iterations for a reliable count)
0.03 WALL ( 0.03 usr + 0.00 sys = 0.03 CPU, minor = 185.60 MB, major = 0 B) @ 3280947.54/s (n=100000)
(warning: too few iterations for a reliable count)
0.03 WALL ( 0.03 usr + 0.00 sys = 0.03 CPU, minor = 185.60 MB, major = 0 B) @ 3279441.18/s (n=100000)
(warning: too few iterations for a reliable count)
Parseff (fair): 0.17 WALL ( 0.17 usr + 0.00 sys = 0.17 CPU, minor = 564.80 MB, major = 0 B) @ 594643.45/s (n=100000)
(warning: too few iterations for a reliable count)
0.17 WALL ( 0.17 usr + 0.00 sys = 0.17 CPU, minor = 564.80 MB, major = 0 B) @ 594915.85/s (n=100000)
(warning: too few iterations for a reliable count)
0.17 WALL ( 0.17 usr + 0.00 sys = 0.17 CPU, minor = 564.80 MB, major = 0 B) @ 593884.18/s (n=100000)
(warning: too few iterations for a reliable count)
Angstrom: 0.14 WALL ( 0.14 usr + 0.00 sys = 0.14 CPU, minor = 584.00 MB, major = 0 B) @ 710767.42/s (n=100000)
(warning: too few iterations for a reliable count)
0.14 WALL ( 0.14 usr + 0.00 sys = 0.14 CPU, minor = 584.00 MB, major = 0 B) @ 712565.38/s (n=100000)
(warning: too few iterations for a reliable count)
0.14 WALL ( 0.14 usr + 0.00 sys = 0.14 CPU, minor = 584.00 MB, major = 0 B) @ 719393.41/s (n=100000)
(warning: too few iterations for a reliable count)
Rate Parseff (fair) Angstrom Parseff (span)
Parseff (fair) 594481+- 415/s -- -17% -82%
Angstrom 714242+-3530/s 20% -- -78%
Parseff (span) 3276156+-5456/s 451% 359% --
Note: Lower latency is better.
So the “fair” implementation is now a bit slower than angstrom, but not by that much. The memory usage is also comparable to angstrom. Still very cool because you have access to the fused stuff if you need to tweak for performance.