Skip to content

Commit

Permalink
Merge pull request #55 from issuu/andersfugmann/no_result
Browse files Browse the repository at this point in the history
Speed up serialization and deserialization
  • Loading branch information
andersfugmann authored Jan 31, 2024
2 parents 23983af + 3b15f82 commit 509e5da
Show file tree
Hide file tree
Showing 52 changed files with 2,930 additions and 1,714 deletions.
28 changes: 21 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,33 @@ uninstall: build ## uninstall
%: %.proto
protoc --experimental_allow_proto3_optional -I $(dir $<) $< -o/dev/stdout | protoc --experimental_allow_proto3_optional --decode google.protobuf.FileDescriptorSet $(GOOGLE_INCLUDE)/descriptor.proto

src/spec/descriptor.ml: build
protoc "--plugin=protoc-gen-ocaml=_build/default/src/plugin/protoc_gen_ocaml.exe" \
PLUGIN = _build/default/src/plugin/protoc_gen_ocaml.exe
$(PLUGIN): force
dune build src/plugin/protoc_gen_ocaml.exe

src/spec/descriptor.ml: $(PLUGIN)
protoc "--plugin=protoc-gen-ocaml=$(PLUGIN)" \
-I /usr/include \
--ocaml_out=src/spec/. \
$(GOOGLE_INCLUDE)/descriptor.proto

src/spec/plugin.ml: build
protoc "--plugin=protoc-gen-ocaml=_build/default/src/plugin/protoc_gen_ocaml.exe" \
src/spec/plugin.ml: $(PLUGIN)
protoc "--plugin=protoc-gen-ocaml=$(PLUGIN)" \
-I /usr/include \
--ocaml_out=src/spec/. \
$(GOOGLE_INCLUDE)/compiler/plugin.proto

src/spec/options.ml: build
protoc "--plugin=protoc-gen-ocaml=_build/default/src/plugin/protoc_gen_ocaml.exe" \
src/spec/options.ml: $(PLUGIN)
protoc "--plugin=protoc-gen-ocaml=$(PLUGIN)" \
-I src/spec -I /usr/include \
--ocaml_out=src/spec/. \
src/spec/options.proto
.PHONY: bootstrap
bootstrap: src/spec/descriptor.ml src/spec/plugin.ml src/spec/options.ml
bootstrap: src/spec/descriptor.ml src/spec/plugin.ml src/spec/options.ml ## Regenerate files used for generation

%.ml: %.proto
protoc -I $(shell pkg-config protobuf --variable=includedir) -I $(dir $<) --plugin=protoc-gen-ocaml=_build/default/src/plugin/protoc_gen_ocaml.exe \
--ocaml_out=$(dir $@). $<


.PHONY: doc
Expand All @@ -62,6 +69,13 @@ gh-pages: doc ## Publish documentation
git -C .gh-pages push origin gh-pages -f
rm -rf .gh-pages

.PHONY: bench
bench: ## Run benchmark to compare with ocaml-protoc
dune exec bench/bench.exe

.PHONY: force
force:

.PHONY: help
help: ## Show this help
@grep -h -E '^[.a-zA-Z_-]+:.*## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
2 changes: 2 additions & 0 deletions bench/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
default:
dune build bench.exe
185 changes: 185 additions & 0 deletions bench/bench.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
[@@@ocaml.warning "-26"]
open Base
open Stdio

let meassure = Bechamel_perf.Instance.cpu_clock

[@@@ocaml.warning "-32"]
module type Protoc_impl = sig
type m
val encode_pb_m: m -> Pbrt.Encoder.t -> unit
val decode_pb_m: Pbrt.Decoder.t -> m
end

module type Plugin_impl = sig
module M : sig
type t
val name' : unit -> string
val show: t -> string
val equal: t -> t -> bool
val to_proto: t -> Ocaml_protoc_plugin.Writer.t
val to_proto': Ocaml_protoc_plugin.Writer.t -> t -> Ocaml_protoc_plugin.Writer.t
val from_proto_exn: Ocaml_protoc_plugin.Reader.t -> t
end
end

let make_tests (type v) (module Protoc: Protoc_impl) (module Plugin: Plugin_impl with type M.t = v) v_plugin =

(* Verify *)
let verify_identity ~mode data =
let writer = Plugin.M.to_proto' (Ocaml_protoc_plugin.Writer.init ~mode ()) data in
let data' = Plugin.M.from_proto_exn (Ocaml_protoc_plugin.Reader.create (Ocaml_protoc_plugin.Writer.contents writer)) in
let () = match Plugin.M.equal data data' with
| true -> ()
| false ->
eprintf "Orig: %s\n" (Plugin.M.show data);
eprintf "New: %s\n" (Plugin.M.show data');
failwith "Data not the same"
in
Ocaml_protoc_plugin.Writer.contents writer |> String.length,
Ocaml_protoc_plugin.Writer.unused_space writer
in
let size_normal, unused_normal = verify_identity ~mode:Ocaml_protoc_plugin.Writer.Balanced v_plugin in
let size_speed, unused_speed = verify_identity ~mode:Ocaml_protoc_plugin.Writer.Speed v_plugin in
let size_space, unused_space = verify_identity ~mode:Ocaml_protoc_plugin.Writer.Space v_plugin in
let data_plugin = Plugin.M.to_proto' (Ocaml_protoc_plugin.Writer.init ()) v_plugin |> Ocaml_protoc_plugin.Writer.contents in
let v_plugin' = Plugin.M.from_proto_exn (Ocaml_protoc_plugin.Reader.create data_plugin) in
assert (Poly.equal v_plugin v_plugin');
let v_protoc = Protoc.decode_pb_m (Pbrt.Decoder.of_string data_plugin) in
let protoc_encoder = Pbrt.Encoder.create () in
let () = Protoc.encode_pb_m v_protoc protoc_encoder in
let data_protoc = Pbrt.Encoder.to_string protoc_encoder in
let v_plugin'' = Plugin.M.from_proto_exn (Ocaml_protoc_plugin.Reader.create data_protoc) in
let () = match Plugin.M.equal v_plugin v_plugin'' with
| true -> ()
| false ->
eprintf "Orig: %s\n" (Plugin.M.show v_plugin);
eprintf "New: %s\n" (Plugin.M.show v_plugin');
failwith "Data not the same"
in
printf "%-16s: %5d+%-5d(B) / %5d+%-5d(S) / %5d+%-5d(Sp) - %5d\n%!" (Plugin.M.name' ())
size_normal unused_normal size_speed unused_speed size_space unused_space (String.length data_protoc);


let open Bechamel in
let test_encode =
Test.make_grouped ~name:"Encode"
[
Test.make ~name:"Plugin" (Staged.stage @@ fun () -> Plugin.M.to_proto' Ocaml_protoc_plugin.Writer.(init ()) v_plugin);
Test.make ~name:"Protoc" (Staged.stage @@ fun () -> let encoder = Pbrt.Encoder.create () in Protoc.encode_pb_m v_protoc encoder; Pbrt.Encoder.to_string encoder)
]
in
let test_decode =
Test.make_grouped ~name:"Decode"
[
Test.make ~name:"Plugin" (Staged.stage @@ fun () -> Plugin.M.from_proto_exn (Ocaml_protoc_plugin.Reader.create data_plugin));
Test.make ~name:"Protoc" (Staged.stage @@ fun () -> Protoc.decode_pb_m (Pbrt.Decoder.of_string data_protoc))
]
in
Test.make_grouped ~name:(Plugin.M.name' ()) [test_encode; test_decode]

let _ =
Random.init 0;
let module Gc = Stdlib.Gc in
Gc.full_major ();
let control = Gc.get () in
Gc.set { control with minor_heap_size=4000_1000; space_overhead=500 }


let random_list ~len ~f () =
List.init len ~f:(fun _ -> f ())

let random_string ~len () =
String.init len ~f:(fun _ -> Random.char ())

let create_test_data ~depth () =
let module M = Plugin.Bench.M in
let module Data = Plugin.Bench.Data in
let module Enum = Plugin.Bench.Enum in
let optional ~f () =
match (Random.int 4 = 0) with
| true -> None
| false -> Some (f ())
in
let create_data () =

let random_enum () =
Array.random_element_exn [| Enum.EA; Enum.EB; Enum.EC; Enum.ED; Enum.EE; |]
in
let s1 = random_string ~len:20 () in
let n1 = random_list ~len:100 ~f:(fun () -> Random.int 1_000) () in
let n2 = random_list ~len:100 ~f:(fun () -> Random.int 1_000) () in
let d1 = random_list ~len:100 ~f:(fun () -> Random.float 1_000.) () in
let n3 = Random.int 10 in
let b1 = Random.bool () in
let e = random_list ~len:100 ~f:random_enum () in

Data.make ~s1 ~n1 ~n2 ~d1 ~n3 ~b1 (* ~e *) ()
in

let rec create_btree n () =
match n with
| 0 -> None
| n ->
let data = random_list ~len:2 ~f:create_data () in
let children =
random_list ~len:2 ~f:(create_btree (n - 1)) () |> List.filter_opt
in
M.make ~children ~data () |> Option.some
in
create_btree depth ()

let benchmark tests =
let open Bechamel in
let instances = [ meassure ] in
let cfg = Benchmark.cfg ~compaction:false ~kde:(Some 1) ~quota:(Time.second 1.0) () in
Benchmark.all cfg instances tests

let analyze results =
let open Bechamel in
let ols = Analyze.ols ~bootstrap:5 ~r_square:true
~predictors:[| Measure.run |] in
let results = Analyze.all ols meassure results in
Analyze.merge ols [ meassure ] [ results ]

let print_bench_results results =
let open Bechamel in
let () = Bechamel_notty.Unit.add
meassure
(Measure.unit meassure)
in

let img (window, results) =
Bechamel_notty.Multiple.image_of_ols_results ~rect:window
~predictor:Measure.run results
in

let open Notty_unix in

let window =
match winsize Unix.stdout with
| Some (w, h) -> { Bechamel_notty.w; h }
| None -> { Bechamel_notty.w= 80; h= 1; } in
img (window, results) |> eol |> output_image


let _ =
let v_plugin = create_test_data ~depth:4 () |> Option.value_exn in
[
make_tests (module Protoc.Bench) (module Plugin.Bench) v_plugin;
make_tests (module Protoc.Int64) (module Plugin.Int64) 27;
make_tests (module Protoc.Float) (module Plugin.Float) 27.0001;
make_tests (module Protoc.String) (module Plugin.String) "Benchmark";
make_tests (module Protoc.Enum) (module Plugin.Enum) Plugin.Enum.Enum.ED;

List.init 1000 ~f:(fun i -> i) |> make_tests (module Protoc.Int64_list) (module Plugin.Int64_list);
List.init 1000 ~f:(fun i -> Float.of_int i) |> make_tests (module Protoc.Float_list) (module Plugin.Float_list);
List.init 1000 ~f:(fun _ -> random_string ~len:20 ()) |> make_tests (module Protoc.String_list) (module Plugin.String_list);
(* random_list ~len:100 ~f:(fun () -> Plugin.Enum_list.Enum.ED) () |> make_tests (module Protoc.Enum_list) (module Plugin.Enum_list); *)
]
|> List.rev |> List.iter ~f:(fun test ->
test
|> benchmark
|> analyze
|> print_bench_results
)
24 changes: 24 additions & 0 deletions bench/bench.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
syntax = "proto3";

enum Enum {
EA = 0;
EB = 1;
EC = 2;
ED = 3;
EE = 4;
}

message data {
optional string s1 = 1;
repeated int64 n1 = 2 [packed = true];
repeated int64 n2 = 3 [packed = true];
repeated double d1 = 4 [packed = true];
optional int64 n3 = 5;
bool b1 = 6;
//repeated Enum e = 7;
}

message M {
repeated M children = 1;
repeated data data = 2;
}
3 changes: 3 additions & 0 deletions bench/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(executable
(name bench)
(libraries protoc plugin bechamel bechamel-notty notty.unix bechamel-perf base stdio))
13 changes: 13 additions & 0 deletions bench/enum.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";

enum Enum {
EA = 0;
EB = 1;
EC = 2;
ED = 3;
EE = 4;
}

message M {
Enum i = 1;
}
13 changes: 13 additions & 0 deletions bench/enum_list.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
syntax = "proto3";

enum Enum {
EA = 0;
EB = 1;
EC = 2;
ED = 3;
EE = 4;
}

message M {
repeated Enum i = 1;
}
5 changes: 5 additions & 0 deletions bench/float.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
double i = 1;
}
5 changes: 5 additions & 0 deletions bench/float_list.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
repeated float i = 1;
}
5 changes: 5 additions & 0 deletions bench/int64.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
int64 i = 1;
}
5 changes: 5 additions & 0 deletions bench/int64_list.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
repeated int64 i = 1;
}
Empty file added bench/perf.data
Empty file.
22 changes: 22 additions & 0 deletions bench/plugin/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(rule
(targets
bench.ml
int64.ml string.ml float.ml enum.ml
int64_list.ml string_list.ml float_list.ml enum_list.ml
)
(deps
(:proto
../bench.proto
../int64.proto ../string.proto ../float.proto ../enum.proto
../int64_list.proto ../string_list.proto ../float_list.proto ../enum_list.proto)
(:plugin ../../src/plugin/protoc_gen_ocaml.exe)
)
(action
(bash "for p in %{proto}; do protoc -I .. --plugin=protoc-gen-ocaml=%{plugin} \"--ocaml_out=annot=[@@deriving show { with_path = false },eq]:.\" $p; done")))

(library
(name plugin)
(libraries ocaml_protoc_plugin)
(preprocess
(pps ppx_deriving.show ppx_deriving.eq ppx_deriving.ord))
)
20 changes: 20 additions & 0 deletions bench/protoc/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
(rule
(targets
bench.ml bench.mli
int64.ml string.ml float.ml enum.ml
int64.mli string.mli float.mli enum.mli
int64_list.ml string_list.ml float_list.ml enum_list.ml
int64_list.mli string_list.mli float_list.mli enum_list.mli
)
(deps
(:proto
../bench.proto
../int64.proto ../string.proto ../float.proto ../enum.proto
../int64_list.proto ../string_list.proto ../float_list.proto ../enum_list.proto))
(action
(bash "for p in %{proto}; do ocaml-protoc -I .. --binary --int32_type int_t --int64_type int_t --ml_out . $p; done")))

(library
(name protoc)
(ocamlopt_flags :standard \ -unboxed-types)
(libraries pbrt))
5 changes: 5 additions & 0 deletions bench/string.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
string i = 1;
}
5 changes: 5 additions & 0 deletions bench/string_list.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
syntax = "proto3";

message M {
repeated string i = 1;
}
4 changes: 4 additions & 0 deletions dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(alias
(name default)
(deps (alias_rec install))
)
Loading

0 comments on commit 509e5da

Please sign in to comment.