diff --git a/.formatter.exs b/.formatter.exs index 370c90a9..216876e2 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,3 +1,3 @@ [ - inputs: ["mix.exs", "{config,lib,test,conformance,benchmarks}/**/*.{ex,exs}"] + inputs: ["mix.exs", "{config,lib,test,conformance,benchmark}/**/*.{ex,exs}"] ] diff --git a/.gitignore b/.gitignore index b1864cd1..9324d4e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,20 @@ +*.beam +*.benchee +*.ez +.DS_Store +.lefthook-local.yml +.tool-versions /_build +/benchmark/output +/conformance-test-runner +/conformance_report /cover /deps /doc /docs -erl_crash.dump -*.ez -*.beam -.DS_Store -/protobuf-* -/conformance_report -/protox_conformance +/failing_tests.txt /priv/plts/*.plt /priv/plts/*.plt.hash -/conformance-test-runner -.tool-versions -/benchmarks/benchmarks/ -/benchmarks/*.benchee -/benchmarks/payloads.bin -/benchmarks/output -/failing_tests.txt -.lefthook-local.yml +/protobuf-* +/protox_conformance +erl_crash.dump diff --git a/README.md b/README.md index 8f24585e..3583f1ec 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ iex> {:ok, msg} = Msg.decode(binary) - [Files generation](#files-generation) - [Conformance](#conformance) - [Types mapping](#types-mapping) -- [Benchmarks](#benchmarks) +- [Benchmark](#benchmark) - [Contributing](#contributing) - [Credits](#credits) @@ -448,13 +448,11 @@ Protobuf | Elixir `enum` | `atom() \| integer()` `message` | `struct()` -## Benchmarks +## Benchmark You can launch benchmarks to see how `protox` perform: ``` -mix run ./benchmarks/generate_payloads.exs # first time only, generates random payloads -mix run ./benchmarks/run.exs --lib=./benchmarks/protox.exs -mix run ./benchmarks/load.exs +TODO ``` ## Contributing diff --git a/benchmark/benchmark_payloads.bin b/benchmark/benchmark_payloads.bin new file mode 100644 index 00000000..24fcaa11 Binary files /dev/null and b/benchmark/benchmark_payloads.bin differ diff --git a/benchmark/compile_benchmark_protos.ex b/benchmark/compile_benchmark_protos.ex new file mode 100644 index 00000000..fbb074ca --- /dev/null +++ b/benchmark/compile_benchmark_protos.ex @@ -0,0 +1,4 @@ +defmodule Protox.CompileBenchmarkProtos do + @moduledoc false + use Protox, files: Path.wildcard("./benchmark/protos/*.proto") +end diff --git a/benchmark/mix/tasks/protox/benchmark/generate/payloads.ex b/benchmark/mix/tasks/protox/benchmark/generate/payloads.ex new file mode 100644 index 00000000..702962e7 --- /dev/null +++ b/benchmark/mix/tasks/protox/benchmark/generate/payloads.ex @@ -0,0 +1,76 @@ +defmodule Mix.Tasks.Protox.Benchmark.Generate.Payloads do + @moduledoc false + + require Logger + + use Mix.Task + use PropCheck + + @nb_samples 1 + + @impl Mix.Task + @spec run(any) :: any + def run(_args) do + with {:ok, modules} <- get_benchmark_modules(), + {:ok, payloads} <- generate_payloads(modules), + {:ok, file} <- File.open("./benchmark/benchmark_payloads.bin", [:write]) do + IO.binwrite(file, :erlang.term_to_binary(payloads)) + File.close(file) + else + err -> + IO.puts(:stderr, "Error: #{inspect(err)}") + exit({:shutdown, 1}) + end + end + + defp get_benchmark_modules() do + case :application.get_key(:protox, :modules) do + {:ok, modules} -> + modules = + Enum.filter(modules, fn mod -> + match?(["Protox", "Benchmark", _, "Message"], Module.split(mod)) + end) + + modules = [ProtobufTestMessages.Proto3.TestAllTypesProto3 | modules] + + Logger.info("Modules: #{inspect(modules)}") + + {:ok, modules} + + :undefined -> + :error + end + end + + defp generate_payloads(modules) do + payloads_async = + for module <- modules, into: %{} do + {module, fn -> generate_payload(module) end} + end + + payloads = + payloads_async + |> Task.async_stream(fn {name, gen} -> {name, gen.()} end, timeout: :infinity) + |> Stream.map(fn {:ok, {name, payloads}} -> {name, payloads} end) + |> Map.new() + + {:ok, payloads} + end + + defp generate_payload(mod) do + Logger.info("Generating payload for #{mod}") + + gen = + let fields <- Protox.RandomInit.generate_fields(mod) do + Protox.RandomInit.generate_struct(mod, fields) + end + + Stream.repeatedly(fn -> :proper_gen.pick(gen, 5) end) + |> Stream.map(fn {:ok, msg} -> {msg, msg |> Protox.encode!() |> IO.iodata_to_binary()} end) + |> Stream.reject(fn {_msg, bytes} -> byte_size(bytes) == 0 end) + |> Stream.reject(fn {_msg, bytes} -> byte_size(bytes) > 16_384 * 16 end) + |> Stream.map(fn {msg, bytes} -> {msg, byte_size(bytes), bytes} end) + |> Stream.each(fn _ -> Logger.info("Payload generated for #{mod}") end) + |> Enum.take(@nb_samples) + end +end diff --git a/benchmark/mix/tasks/protox/benchmark/generate/protos.ex b/benchmark/mix/tasks/protox/benchmark/generate/protos.ex new file mode 100644 index 00000000..f9f75e7e --- /dev/null +++ b/benchmark/mix/tasks/protox/benchmark/generate/protos.ex @@ -0,0 +1,89 @@ +defmodule Mix.Tasks.Protox.Benchmark.Generate.Protos do + @moduledoc false + + require Logger + + use Mix.Task + + # Frequencies are taken from + # https://github.com/protocolbuffers/protobuf/blob/336d6f04e94efebcefb5574d0c8d487bcb0d187e/benchmarks/gen_synthetic_protos.py. + @field_freqs [ + {"bool", "", 8.321}, + {"bool", "repeated", 0.033}, + {"bytes", "", 0.809}, + {"bytes", "repeated", 0.065}, + {"double", "", 2.845}, + {"double", "repeated", 0.143}, + {"fixed32", "", 0.084}, + {"fixed32", "repeated", 0.012}, + {"fixed64", "", 0.204}, + {"fixed64", "repeated", 0.027}, + {"float", "", 2.355}, + {"float", "repeated", 0.132}, + {"int32", "", 6.717}, + {"int32", "repeated", 0.366}, + {"int64", "", 9.678}, + {"int64", "repeated", 0.425}, + {"sfixed32", "", 0.018}, + {"sfixed32", "repeated", 0.005}, + {"sfixed64", "", 0.022}, + {"sfixed64", "repeated", 0.005}, + {"sint32", "", 0.026}, + {"sint32", "repeated", 0.009}, + {"sint64", "", 0.018}, + {"sint64", "repeated", 0.006}, + {"string", "", 25.461}, + {"string", "repeated", 2.606}, + {"Enum", "", 6.16}, + {"Enum", "repeated", 0.576}, + {"Message", "", 22.472}, + {"Message", "repeated", 7.766}, + {"uint32", "", 1.289}, + {"uint32", "repeated", 0.051}, + {"uint64", "", 1.044}, + {"uint64", "repeated", 0.079} + ] + + @message_template """ + syntax = "proto3"; + package protox.benchmark.synthetic_<%= count %>; + + enum Enum { + ZERO = 0; + } + + message Message { + <%= for {type, label, counter} <- fields do %> + <%= label %> <%= type %> field_<%= counter %> = <%= counter %>;<% end %> + } + """ + + @impl Mix.Task + @spec run(any) :: any + def run(_args) do + for count <- [5, 10, 20, 50, 100] do + Logger.info("Generating synthetic proto with #{count} fields") + content = EEx.eval_string(@message_template, count: count, fields: random_choices(count)) + File.write!("./protos/benchmark/synthetic_#{count}.proto", content) + end + end + + defp random_choices(count) when count >= 0 do + total_weight = + @field_freqs + |> Stream.map(fn {_, _, freq} -> freq end) + |> Enum.sum() + + cumulative_weights = + @field_freqs + |> Stream.map(fn {_, _, freq} -> freq end) + |> Enum.scan(&(&1 + &2)) + + Enum.map(1..count, fn c -> + rand = :rand.uniform() * total_weight + index = Enum.find_index(cumulative_weights, &(rand <= &1)) + {type, label, _freq} = Enum.at(@field_freqs, index) + {type, label, c} + end) + end +end diff --git a/benchmark/mix/tasks/protox/benchmark/run.ex b/benchmark/mix/tasks/protox/benchmark/run.ex new file mode 100644 index 00000000..198cf3c1 --- /dev/null +++ b/benchmark/mix/tasks/protox/benchmark/run.ex @@ -0,0 +1,76 @@ +defmodule Mix.Tasks.Protox.Benchmark.Run do + @moduledoc false + + use Mix.Task + + @options [ + prefix: :string + ] + + @impl Mix.Task + @spec run(any) :: any + def run(args) do + with {opts, _argv, []} <- OptionParser.parse(args, strict: @options), + prefix <- Keyword.get(opts, :prefix, nil), + tag <- get_tag(prefix), + payloads <- get_payloads("./benchmark/benchmark_payloads.bin") do + IO.puts("tag=#{tag}\n") + + Benchee.run( + %{ + "decode" => fn input -> + Enum.map(input, fn {msg, _size, bytes} -> msg.__struct__.decode!(bytes) end) + end, + "encode" => fn input -> + Enum.map(input, fn {msg, _size, _bytes} -> msg.__struct__.encode!(msg) end) + end + }, + inputs: payloads, + save: [ + path: Path.join(["./benchmark", "#{tag}.benchee"]), + tag: "#{tag}" + ], + load: ["./benchmark/*.benchee"], + time: 10, + memory_time: 2, + formatters: [ + {Benchee.Formatters.HTML, file: "benchmark/output/#{tag}.html"}, + Benchee.Formatters.Console + ] + ) + else + err -> + IO.puts(:stderr, "Error: #{inspect(err)}") + exit({:shutdown, 1}) + end + end + + defp get_tag(prefix) do + {hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"]) + elixir_version = System.version() + + erlang_version = + [:code.root_dir(), "releases", :erlang.system_info(:otp_release), "OTP_VERSION"] + |> Path.join() + |> File.read!() + |> String.trim() + + tag = [elixir_version, erlang_version, hash] + + tag = + case prefix do + nil -> tag + prefix -> [prefix | tag] + end + + tag + |> Enum.map(&String.trim/1) + |> Enum.join("-") + end + + def get_payloads(path) do + path + |> File.read!() + |> :erlang.binary_to_term() + end +end diff --git a/benchmark/protos/synthetic_10.proto b/benchmark/protos/synthetic_10.proto new file mode 100644 index 00000000..e81e90c9 --- /dev/null +++ b/benchmark/protos/synthetic_10.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_10; + +enum Enum { + ZERO = 0; +} + +message Message { + repeated Message field_1 = 1; + Message field_2 = 2; + int64 field_3 = 3; + Enum field_4 = 4; + string field_5 = 5; + repeated Enum field_6 = 6; + int64 field_7 = 7; + Message field_8 = 8; + Message field_9 = 9; + Message field_10 = 10; +} diff --git a/benchmark/protos/synthetic_100.proto b/benchmark/protos/synthetic_100.proto new file mode 100644 index 00000000..9e0dc8be --- /dev/null +++ b/benchmark/protos/synthetic_100.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_100; + +enum Enum { + ZERO = 0; +} + +message Message { + bool field_1 = 1; + Message field_2 = 2; + string field_3 = 3; + Message field_4 = 4; + Message field_5 = 5; + Enum field_6 = 6; + uint64 field_7 = 7; + Message field_8 = 8; + string field_9 = 9; + repeated int32 field_10 = 10; + Message field_11 = 11; + repeated string field_12 = 12; + Message field_13 = 13; + bool field_14 = 14; + bool field_15 = 15; + Message field_16 = 16; + repeated Message field_17 = 17; + int32 field_18 = 18; + string field_19 = 19; + string field_20 = 20; + Message field_21 = 21; + float field_22 = 22; + repeated string field_23 = 23; + string field_24 = 24; + string field_25 = 25; + Enum field_26 = 26; + string field_27 = 27; + string field_28 = 28; + int32 field_29 = 29; + string field_30 = 30; + int32 field_31 = 31; + string field_32 = 32; + int64 field_33 = 33; + bool field_34 = 34; + int64 field_35 = 35; + Message field_36 = 36; + string field_37 = 37; + string field_38 = 38; + Enum field_39 = 39; + string field_40 = 40; + bool field_41 = 41; + Message field_42 = 42; + Message field_43 = 43; + string field_44 = 44; + repeated Message field_45 = 45; + Enum field_46 = 46; + Enum field_47 = 47; + Enum field_48 = 48; + Message field_49 = 49; + string field_50 = 50; + bool field_51 = 51; + Message field_52 = 52; + Message field_53 = 53; + repeated Message field_54 = 54; + bool field_55 = 55; + float field_56 = 56; + string field_57 = 57; + bool field_58 = 58; + Message field_59 = 59; + int32 field_60 = 60; + string field_61 = 61; + string field_62 = 62; + string field_63 = 63; + Message field_64 = 64; + bool field_65 = 65; + Message field_66 = 66; + Message field_67 = 67; + Message field_68 = 68; + bool field_69 = 69; + string field_70 = 70; + Message field_71 = 71; + Message field_72 = 72; + repeated string field_73 = 73; + string field_74 = 74; + bool field_75 = 75; + Message field_76 = 76; + uint32 field_77 = 77; + int32 field_78 = 78; + Message field_79 = 79; + Message field_80 = 80; + Message field_81 = 81; + bool field_82 = 82; + int32 field_83 = 83; + Enum field_84 = 84; + Message field_85 = 85; + repeated Message field_86 = 86; + repeated Message field_87 = 87; + Message field_88 = 88; + bool field_89 = 89; + Enum field_90 = 90; + Enum field_91 = 91; + string field_92 = 92; + Enum field_93 = 93; + int32 field_94 = 94; + repeated Message field_95 = 95; + Message field_96 = 96; + repeated Message field_97 = 97; + int32 field_98 = 98; + string field_99 = 99; + string field_100 = 100; +} diff --git a/benchmark/protos/synthetic_20.proto b/benchmark/protos/synthetic_20.proto new file mode 100644 index 00000000..e2982598 --- /dev/null +++ b/benchmark/protos/synthetic_20.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_20; + +enum Enum { + ZERO = 0; +} + +message Message { + + Message field_1 = 1; + double field_2 = 2; + Message field_3 = 3; + int64 field_4 = 4; + string field_5 = 5; + int64 field_6 = 6; + Message field_7 = 7; + repeated Message field_8 = 8; + fixed64 field_9 = 9; + repeated Message field_10 = 10; + bool field_11 = 11; + int64 field_12 = 12; + int64 field_13 = 13; + Message field_14 = 14; + bool field_15 = 15; + Message field_16 = 16; + Message field_17 = 17; + string field_18 = 18; + Message field_19 = 19; + Message field_20 = 20; +} diff --git a/benchmark/protos/synthetic_200.proto b/benchmark/protos/synthetic_200.proto new file mode 100644 index 00000000..22d10380 --- /dev/null +++ b/benchmark/protos/synthetic_200.proto @@ -0,0 +1,210 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_200; + +enum Enum { + ZERO = 0; +} + +message Message { + + int32 field_1 = 1; + Message field_2 = 2; + Message field_3 = 3; + string field_4 = 4; + Message field_5 = 5; + string field_6 = 6; + Message field_7 = 7; + Message field_8 = 8; + string field_9 = 9; + double field_10 = 10; + string field_11 = 11; + string field_12 = 12; + Message field_13 = 13; + string field_14 = 14; + int64 field_15 = 15; + int32 field_16 = 16; + string field_17 = 17; + string field_18 = 18; + Message field_19 = 19; + int64 field_20 = 20; + string field_21 = 21; + bool field_22 = 22; + double field_23 = 23; + repeated Message field_24 = 24; + double field_25 = 25; + double field_26 = 26; + int64 field_27 = 27; + bytes field_28 = 28; + string field_29 = 29; + Enum field_30 = 30; + string field_31 = 31; + Message field_32 = 32; + Message field_33 = 33; + bytes field_34 = 34; + string field_35 = 35; + string field_36 = 36; + Message field_37 = 37; + repeated string field_38 = 38; + Message field_39 = 39; + string field_40 = 40; + Enum field_41 = 41; + string field_42 = 42; + Message field_43 = 43; + string field_44 = 44; + int64 field_45 = 45; + bool field_46 = 46; + string field_47 = 47; + double field_48 = 48; + string field_49 = 49; + int64 field_50 = 50; + repeated Message field_51 = 51; + double field_52 = 52; + Enum field_53 = 53; + int64 field_54 = 54; + repeated string field_55 = 55; + repeated Message field_56 = 56; + Message field_57 = 57; + string field_58 = 58; + Message field_59 = 59; + Message field_60 = 60; + repeated Message field_61 = 61; + Message field_62 = 62; + double field_63 = 63; + int64 field_64 = 64; + Message field_65 = 65; + string field_66 = 66; + string field_67 = 67; + string field_68 = 68; + string field_69 = 69; + Enum field_70 = 70; + Message field_71 = 71; + bool field_72 = 72; + Message field_73 = 73; + repeated Message field_74 = 74; + double field_75 = 75; + Message field_76 = 76; + Message field_77 = 77; + string field_78 = 78; + Message field_79 = 79; + string field_80 = 80; + Message field_81 = 81; + int32 field_82 = 82; + Message field_83 = 83; + Enum field_84 = 84; + string field_85 = 85; + Message field_86 = 86; + int64 field_87 = 87; + Message field_88 = 88; + repeated string field_89 = 89; + Message field_90 = 90; + int32 field_91 = 91; + bool field_92 = 92; + Message field_93 = 93; + string field_94 = 94; + int64 field_95 = 95; + Message field_96 = 96; + Enum field_97 = 97; + string field_98 = 98; + Message field_99 = 99; + repeated Message field_100 = 100; + Message field_101 = 101; + uint32 field_102 = 102; + repeated Message field_103 = 103; + Message field_104 = 104; + Message field_105 = 105; + repeated Message field_106 = 106; + string field_107 = 107; + Message field_108 = 108; + Enum field_109 = 109; + repeated string field_110 = 110; + bool field_111 = 111; + string field_112 = 112; + repeated string field_113 = 113; + repeated Message field_114 = 114; + int32 field_115 = 115; + repeated string field_116 = 116; + string field_117 = 117; + int64 field_118 = 118; + Enum field_119 = 119; + int64 field_120 = 120; + repeated Message field_121 = 121; + Message field_122 = 122; + Message field_123 = 123; + string field_124 = 124; + repeated int64 field_125 = 125; + Message field_126 = 126; + string field_127 = 127; + float field_128 = 128; + uint64 field_129 = 129; + repeated Message field_130 = 130; + string field_131 = 131; + Message field_132 = 132; + string field_133 = 133; + uint32 field_134 = 134; + Message field_135 = 135; + int32 field_136 = 136; + bool field_137 = 137; + string field_138 = 138; + int64 field_139 = 139; + Message field_140 = 140; + string field_141 = 141; + Message field_142 = 142; + string field_143 = 143; + Message field_144 = 144; + Enum field_145 = 145; + string field_146 = 146; + bool field_147 = 147; + string field_148 = 148; + int32 field_149 = 149; + repeated string field_150 = 150; + fixed64 field_151 = 151; + Message field_152 = 152; + string field_153 = 153; + Message field_154 = 154; + int32 field_155 = 155; + uint32 field_156 = 156; + string field_157 = 157; + Message field_158 = 158; + Message field_159 = 159; + Enum field_160 = 160; + string field_161 = 161; + double field_162 = 162; + string field_163 = 163; + repeated Message field_164 = 164; + string field_165 = 165; + string field_166 = 166; + double field_167 = 167; + Message field_168 = 168; + fixed64 field_169 = 169; + string field_170 = 170; + int64 field_171 = 171; + float field_172 = 172; + string field_173 = 173; + Message field_174 = 174; + int32 field_175 = 175; + bytes field_176 = 176; + repeated Message field_177 = 177; + Enum field_178 = 178; + Enum field_179 = 179; + bool field_180 = 180; + int32 field_181 = 181; + string field_182 = 182; + string field_183 = 183; + int64 field_184 = 184; + repeated string field_185 = 185; + Message field_186 = 186; + string field_187 = 187; + Message field_188 = 188; + bool field_189 = 189; + Message field_190 = 190; + int64 field_191 = 191; + Enum field_192 = 192; + bool field_193 = 193; + bool field_194 = 194; + int64 field_195 = 195; + sint32 field_196 = 196; + int64 field_197 = 197; + Message field_198 = 198; + bool field_199 = 199; + Enum field_200 = 200; +} diff --git a/benchmark/protos/synthetic_5.proto b/benchmark/protos/synthetic_5.proto new file mode 100644 index 00000000..b1c5e65b --- /dev/null +++ b/benchmark/protos/synthetic_5.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_5; + +enum Enum { + ZERO = 0; +} + +message Message { + + int64 field_1 = 1; + bool field_2 = 2; + string field_3 = 3; + repeated Message field_4 = 4; + int32 field_5 = 5; +} diff --git a/benchmark/protos/synthetic_50.proto b/benchmark/protos/synthetic_50.proto new file mode 100644 index 00000000..5b8da95d --- /dev/null +++ b/benchmark/protos/synthetic_50.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; +package protox.benchmark.synthetic_50; + +enum Enum { + ZERO = 0; +} + +message Message { + + bool field_1 = 1; + Enum field_2 = 2; + bool field_3 = 3; + double field_4 = 4; + string field_5 = 5; + repeated Message field_6 = 6; + int32 field_7 = 7; + repeated Message field_8 = 8; + Message field_9 = 9; + bool field_10 = 10; + Message field_11 = 11; + string field_12 = 12; + Message field_13 = 13; + Message field_14 = 14; + Message field_15 = 15; + bool field_16 = 16; + int64 field_17 = 17; + repeated Message field_18 = 18; + Message field_19 = 19; + Enum field_20 = 20; + Message field_21 = 21; + int64 field_22 = 22; + repeated float field_23 = 23; + Message field_24 = 24; + string field_25 = 25; + Message field_26 = 26; + repeated float field_27 = 27; + Message field_28 = 28; + string field_29 = 29; + double field_30 = 30; + int32 field_31 = 31; + bool field_32 = 32; + string field_33 = 33; + float field_34 = 34; + float field_35 = 35; + int32 field_36 = 36; + string field_37 = 37; + Enum field_38 = 38; + Message field_39 = 39; + uint64 field_40 = 40; + repeated Message field_41 = 41; + string field_42 = 42; + string field_43 = 43; + Enum field_44 = 44; + int64 field_45 = 45; + double field_46 = 46; + string field_47 = 47; + string field_48 = 48; + string field_49 = 49; + int64 field_50 = 50; +} diff --git a/benchmarks/benchmarks.proto b/benchmarks/benchmarks.proto deleted file mode 100644 index 2cf29817..00000000 --- a/benchmarks/benchmarks.proto +++ /dev/null @@ -1,67 +0,0 @@ -syntax = "proto3"; - -message Sub { - int32 a = 1; - string b = 2; - double xxx = 3; - string zzz = 5; - int64 c = 6; - uint32 d = 7; - uint64 e = 8; - sint64 f = 9; - bytes bbb = 10; - int32 aaa = 11; - float fff = 12; - repeated fixed64 g = 13; - repeated sfixed32 h = 14; - repeated double i = 15; - fixed32 k = 17; - sfixed64 l = 18; - bytes m = 19; - repeated bool n = 20; - repeated E o = 21; - E r = 24; - repeated uint32 u = 27; - repeated sint32 w = 28; - repeated int64 x = 29; - repeated uint64 y = 30; - sint32 z = 10001; -} - -enum E { - FOO = 0; - BAR = 1; - BAZ = 2; -// :protobuf seems to not be compliant with the recommanded serialization -// and produces many warnings -// NEG = -1; -} - -message Msg { - E d = 1; - bool e = 2; - Sub f = 3; - repeated int32 g = 4; - double h = 5; - repeated float i = 6; - repeated Sub j = 7; - map k = 8; - map l = 9; - oneof m { - string n = 10; - Sub o = 11; - } - map p = 12; - repeated sint64 a = 27; - repeated fixed32 b = 28; - repeated sfixed64 c = 29; -} - -message Upper { - Msg msg = 1; - map map = 2; - Empty empty = 3; -} - -message Empty { -} diff --git a/benchmarks/generate_payloads.exs b/benchmarks/generate_payloads.exs deleted file mode 100644 index 203a6c34..00000000 --- a/benchmarks/generate_payloads.exs +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Protox.Benchmarks do - use Protox, files: ["./benchmarks/benchmarks.proto"], namespace: Protox.Benchmarks -end - -Code.compile_file("./test/support/random_init.ex") -Code.compile_file("./benchmarks/handmade_payloads.exs") - -defmodule Protox.GeneratePayloads do - use PropCheck - - def generate(mod, size, min_sz, max_sz, nb \\ 100) do - IO.puts("Generating for size=#{size}, min_sz=#{min_sz}, max_sz=#{max_sz}") - - gen = - let fields <- Protox.RandomInit.generate_fields(mod) do - Protox.RandomInit.generate_struct(mod, fields) - end - - Stream.repeatedly(fn -> - {:ok, msg} = :proper_gen.pick(gen, size) - msg |> Protox.encode!() |> :binary.list_to_bin() - end) - |> Stream.reject(fn bytes -> - byte_size(bytes) == 0 or byte_size(bytes) < min_sz or byte_size(bytes) >= max_sz - end) - |> Stream.map(fn payload -> - {mod |> Module.split() |> List.last(), payload} - end) - |> Enum.take(nb) - end - - def handmade() do - Enum.map(Protox.Benchmarks.HandmadePayloads.payloads(), fn msg -> - mod = msg.__struct__ |> Module.split() |> List.last() - {mod, msg |> Protox.encode!() |> :binary.list_to_bin()} - end) - end -end - -payloads_async = %{ - upper_xsmall: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Upper, 1, 4, 128) end, - upper_small: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Upper, 2, 128, 512) end, - upper_medium: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Upper, 3, 512, 2048) end, - upper_large: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Upper, 5, 2048, 8192) end, - upper_xlarge: fn -> - Protox.GeneratePayloads.generate(Protox.Benchmarks.Upper, 10, 8192, 262_144) - end, - sub_xsmall: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Sub, 1, 4, 128) end, - sub_small: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Sub, 2, 128, 256) end, - sub_medium: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Sub, 10, 256, 512) end, - sub_large: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Sub, 20, 512, 1024) end, - sub_xlarge: fn -> Protox.GeneratePayloads.generate(Protox.Benchmarks.Sub, 50, 1024, 2048) end, - handmade: fn -> Protox.GeneratePayloads.handmade() end -} - -payloads = - payloads_async - |> Task.async_stream(fn {name, gen} -> {name, gen.()} end, timeout: :infinity) - |> Stream.map(fn {:ok, {name, payloads}} -> {name, payloads} end) - |> Map.new() - -"./benchmarks/payloads.bin" -|> File.open!([:write]) -|> IO.binwrite(:erlang.term_to_binary(payloads)) -|> File.close() diff --git a/benchmarks/handmade_payloads.exs b/benchmarks/handmade_payloads.exs deleted file mode 100644 index 4da5337e..00000000 --- a/benchmarks/handmade_payloads.exs +++ /dev/null @@ -1,319 +0,0 @@ -defmodule Protox.Benchmarks.HandmadePayloads do - def payloads() do - [ - %Protox.Benchmarks.Sub{ - a: 150 - }, - %Protox.Benchmarks.Sub{ - a: -150 - }, - %Protox.Benchmarks.Sub{ - b: "testing" - }, - %Protox.Benchmarks.Sub{ - a: 150, - b: "testing" - }, - %Protox.Benchmarks.Sub{ - a: 150, - b: "testing", - zzz: "" - }, - %Protox.Benchmarks.Sub{ - a: 42, - xxx: 42.42, - z: -42 - }, - %Protox.Benchmarks.Sub{ - a: 42, - z: -42 - }, - %Protox.Benchmarks.Sub{ - a: 3342, - aaa: 666, - bbb: "hey!", - fff: 33.33000183105469, - z: -10 - }, - %Protox.Benchmarks.Sub{ - a: 3342, - b: "dqsqsdcqsqddqsqsd qsdqs dqsd ", - c: -4_678_909_765, - d: 29_232, - e: 8_938_293, - f: -242_424, - bbb: <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15>>, - aaa: 666, - fff: 33.33000183105469, - z: -10, - xxx: 42.31, - zzz: "a string", - r: :BAZ, - o: [:BAZ, :FOO, :FOO, :FOO, :BAZ, :FOO, :BAZ, :FOO, :BAZ] - }, - %Protox.Benchmarks.Sub{ - g: [0], - h: [-1], - i: [33.2, -44.0] - }, - %Protox.Benchmarks.Sub{ - h: [-1, -2] - }, - %Protox.Benchmarks.Msg{ - g: [ - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3, - 270, - 86_942, - 13, - 22, - 3423, - 23, - 132_432, - 12, - 98, - 142_442, - 14_500, - 0, - 3 - ] - }, - %Protox.Benchmarks.Msg{ - g: [1, 2, 3] - }, - %Protox.Benchmarks.Msg{ - k: %{1 => "foo", 2 => "bar", 3 => "ddsq", 4 => "pjqsopjqs", 5 => "sdfqjz", 6 => "foqd"} - }, - %Protox.Benchmarks.Msg{ - l: %{"bar" => 1.0, "foo" => 43.2, "baz" => 33.2, "fiz" => -3.4} - }, - %Protox.Benchmarks.Upper{ - msg: %Protox.Benchmarks.Msg{ - f: %Protox.Benchmarks.Sub{ - a: 42, - zzz: "efqpodiqfjqjpiosfqsfjopqfsopqsfopopqsfjpjoqcsojp" - } - } - }, - %Protox.Benchmarks.Upper{ - empty: nil, - map: %{ - "baz" => %Protox.Benchmarks.Msg{ - e: true - }, - "foo" => %Protox.Benchmarks.Msg{ - d: :BAR - } - } - }, - %Protox.Benchmarks.Sub{a: 150, b: "testing", c: 300, r: :BAR}, - %Protox.Benchmarks.Msg{ - d: :FOO, - e: true, - f: %Protox.Benchmarks.Sub{ - a: 150, - b: "testing", - c: 300, - r: :BAR, - g: [1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], - n: [true, false, true, false, true, false] - } - }, - %Protox.Benchmarks.Msg{g: [1, 2, -3, 4, -5, 6, -7, 8, -9, 10]}, - %Protox.Benchmarks.Msg{ - j: [ - %Protox.Benchmarks.Sub{a: 42}, - %Protox.Benchmarks.Sub{b: "foo"}, - %Protox.Benchmarks.Sub{b: "foofoo"}, - %Protox.Benchmarks.Sub{b: "foobarfoobar"}, - %Protox.Benchmarks.Sub{b: "foobarbar"}, - %Protox.Benchmarks.Sub{b: "bartutfoo"}, - %Protox.Benchmarks.Sub{b: "bartutfoofiz"}, - %Protox.Benchmarks.Sub{b: "bartutfoo"} - ] - }, - %Protox.Benchmarks.Msg{ - l: %{ - "1" => 1.0, - "2" => 2.0, - "3" => 3.0, - "4" => 4.0, - "5" => 5.0, - "6" => 6.0, - "7" => 7.0, - "8" => 8.0, - "9" => 9.0, - "10" => 10.0, - "11" => 11.0, - "12" => 12.0, - "13" => 13.0, - "14" => 14.0, - "15" => 15.0, - "16" => 16.0, - "17" => 17.0, - "18" => 18.0, - "19" => 19.0, - "20" => 20.0, - "21" => 21.0, - "22" => 22.0, - "23" => 23.0, - "24" => 24.0, - "25" => 25.0, - "26" => 26.0, - "27" => 27.0, - "28" => 28.0, - "29" => 29.0, - "30" => 30.0, - "31" => 31.0, - "32" => 32.0, - "33" => 33.0, - "34" => 34.0, - "35" => 35.0, - "36" => 36.0, - "37" => 37.0, - "38" => 38.0, - "39" => 39.0, - "40" => 40.0, - "41" => 41.0, - "42" => 42.0, - "43" => 43.0, - "44" => 44.0, - "45" => 45.0, - "46" => 46.0, - "47" => 47.0, - "48" => 48.0, - "49" => 49.0, - "50" => 50.0, - "51" => 51.0, - "52" => 52.0, - "53" => 53.0, - "54" => 54.0, - "55" => 55.0, - "56" => 56.0, - "57" => 57.0, - "58" => 58.0, - "59" => 59.0, - "60" => 60.0, - "61" => 61.0, - "62" => 62.0, - "63" => 63.0, - "64" => 64.0, - "65" => 65.0, - "66" => 66.0, - "67" => 67.0, - "68" => 68.0, - "69" => 69.0, - "70" => 70.0, - "71" => 71.0, - "72" => 72.0, - "73" => 73.0, - "74" => 74.0, - "75" => 75.0, - "76" => 76.0, - "77" => 77.0, - "78" => 78.0, - "79" => 79.0, - "80" => 80.0, - "81" => 81.0, - "82" => 82.0, - "83" => 83.0, - "84" => 84.0, - "85" => 85.0, - "86" => 86.0, - "87" => 87.0, - "88" => 88.0, - "89" => 89.0, - "90" => 90.0, - "91" => 91.0, - "92" => 92.0, - "93" => 93.0, - "94" => 94.0, - "95" => 95.0, - "96" => 96.0, - "97" => 97.0, - "98" => 98.0, - "99" => 99.0, - "100" => 100.0 - }, - m: {:n, "foo"} - } - ] - end -end diff --git a/benchmarks/load.exs b/benchmarks/load.exs deleted file mode 100644 index e189ec41..00000000 --- a/benchmarks/load.exs +++ /dev/null @@ -1,17 +0,0 @@ -Benchee.run( - %{}, - formatters: [ - {Benchee.Formatters.HTML, file: "./benchmarks/output/decode.html"}, - {Benchee.Formatters.Markdown, file: "./benchmarks/output/decode.md"} - ], - load: ["./benchmarks/*decode*.benchee"] -) - -Benchee.run( - %{}, - formatters: [ - {Benchee.Formatters.HTML, file: "./benchmarks/output/encode.html"}, - {Benchee.Formatters.Markdown, file: "./benchmarks/output/encode.md"} - ], - load: ["./benchmarks/*encode*.benchee"] -) diff --git a/benchmarks/protox.exs b/benchmarks/protox.exs deleted file mode 100644 index 8f4e6629..00000000 --- a/benchmarks/protox.exs +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Protox.Benchmarks.Run do - def decode({mod, bytes}), do: Protox.decode!(bytes, namespace(mod)) - def encode(msg), do: Protox.encode(msg) - - def decode_name(), do: "decode_protox" - def decode_file_name(), do: "decode_protox.benchee" - - def encode_name(), do: "encode_protox" - def encode_file_name(), do: "encode_protox.benchee" - - defp namespace(mod), do: Module.safe_concat([Protox, Benchmarks, mod]) -end diff --git a/benchmarks/run.exs b/benchmarks/run.exs deleted file mode 100644 index e0286dbf..00000000 --- a/benchmarks/run.exs +++ /dev/null @@ -1,138 +0,0 @@ -# ----------------------------------------------------------------------------------------------- # - -opts = - with {options, _, []} <- - OptionParser.parse(System.argv(), - strict: [lib: :string, selector: :string, tag: :string] - ), - {:ok, lib} <- Keyword.fetch(options, :lib) do - Code.compile_file(lib) - - selector = - case Keyword.get(options, :selector) do - nil -> :all - selector -> selector |> String.split(",") |> Enum.map(&String.to_atom(&1)) - end - - prefix = - case Keyword.get(options, :tag) do - nil -> "" - prefix -> "#{prefix}-" - end - - %{selector: selector, prefix: prefix} - else - _ -> - IO.puts("Missing lib argument") - System.halt(0) - end - -# ----------------------------------------------------------------------------------------------- # - -defmodule Protox.Benchmarks do - use Protox, files: ["./benchmarks/benchmarks.proto"], namespace: Protox.Benchmarks -end - -# ----------------------------------------------------------------------------------------------- # - -defmodule Protox.Benchmarks.Data do - def inputs(path, selector) do - filter = - case selector do - :all -> fn _ -> true end - selector -> fn {size, _payloads} -> size in selector end - end - - decode = - path - |> File.read!() - |> :erlang.binary_to_term() - |> Enum.filter(&filter.(&1)) - |> Enum.into(%{}, fn {size, payloads} -> - payloads = - Enum.map(payloads, fn {mod, bytes} -> - {mod, bytes} - end) - - {"#{Atom.to_string(size)}", payloads} - end) - - encode = - path - |> File.read!() - |> :erlang.binary_to_term() - |> Enum.filter(&filter.(&1)) - |> Enum.into(%{}, fn {size, payloads} -> - payloads = - Enum.map(payloads, fn {mod, bytes} -> - Protox.Benchmarks.Run.decode({mod, bytes}) - end) - - {"#{Atom.to_string(size)}", payloads} - end) - - {decode, encode} - end -end - -# ----------------------------------------------------------------------------------------------- # - -IO.puts("Will run benchmarks: #{inspect(opts.selector)}") - -{decode_inputs, encode_inputs} = - Protox.Benchmarks.Data.inputs("./benchmarks/payloads.bin", opts.selector) - -# ----------------------------------------------------------------------------------------------- # - -{hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"]) -elixir_version = System.version() - -erlang_version = - [:code.root_dir(), "releases", :erlang.system_info(:otp_release), "OTP_VERSION"] - |> Path.join() - |> File.read!() - |> String.trim() - -tag = "#{opts.prefix}#{elixir_version}-#{erlang_version}-#{String.trim(hash)}" - -# ----------------------------------------------------------------------------------------------- # - -Benchee.run( - %{ - Protox.Benchmarks.Run.decode_name() => fn input -> - Enum.map(input, &Protox.Benchmarks.Run.decode(&1)) - end - }, - inputs: decode_inputs, - formatters: [ - Benchee.Formatters.Console - ], - save: [ - path: Path.join(["./benchmarks", "#{tag}_#{Protox.Benchmarks.Run.decode_file_name()}"]), - tag: "#{tag}" - ], - time: 10, - memory_time: 2 -) - -# ----------------------------------------------------------------------------------------------- # - -Benchee.run( - %{ - Protox.Benchmarks.Run.encode_name() => fn input -> - Enum.map(input, &Protox.Benchmarks.Run.encode(&1)) - end - }, - inputs: encode_inputs, - formatters: [ - Benchee.Formatters.Console - ], - save: [ - path: Path.join(["./benchmarks", "#{tag}_#{Protox.Benchmarks.Run.encode_file_name()}"]), - tag: "#{tag}" - ], - time: 10, - memory_time: 2 -) - -# ----------------------------------------------------------------------------------------------- # diff --git a/coveralls.json b/coveralls.json index 4228d57a..1f0f6867 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,6 +1,3 @@ { - "skip_files": [ - "conformance", - "benchmarks" - ] + "skip_files": ["conformance", "benchmark"] } diff --git a/mise.toml b/mise.toml index e56febc8..7e428129 100644 --- a/mise.toml +++ b/mise.toml @@ -2,4 +2,4 @@ PROTOX_PROTOBUF_VERSION = "29.3" [tools] -elixir = "1.18.1-otp-27" +elixir = "1.18.2-otp-27" diff --git a/mix.exs b/mix.exs index b61ac830..5975ac66 100644 --- a/mix.exs +++ b/mix.exs @@ -22,19 +22,19 @@ defmodule Protox.Mixfile do ] end - # Do not compile conformance and benchmarks related files when in production defp elixirc_paths(:prod), do: ["lib"] - defp elixirc_paths(_), do: ["lib", "conformance", "benchmarks", "test/support"] + defp elixirc_paths(_), do: ["lib", "conformance", "benchmark", "test/support"] def application do [ - extra_applications: [:mix] + extra_applications: [:benchee, :eex, :mix] ] end defp deps do [ - {:benchee, "~> 1.0", only: [:dev], runtime: false}, + {:benchee, "~> 1.0", only: [:test, :dev], runtime: false}, + {:benchee_html, "~> 1.0", only: [:test, :dev], runtime: false}, {:credo, "~> 1.4", only: [:test, :dev], runtime: false}, {:dialyxir, "~> 1.0", only: [:test, :dev], runtime: false}, {:excoveralls, "~> 0.13", only: [:test], runtime: false}, diff --git a/mix.lock b/mix.lock index 063f3981..07ed75f5 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ "benchee": {:hex, :benchee, "1.3.1", "c786e6a76321121a44229dde3988fc772bca73ea75170a73fd5f4ddf1af95ccf", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "76224c58ea1d0391c8309a8ecbfe27d71062878f59bd41a390266bf4ac1cc56d"}, + "benchee_html": {:hex, :benchee_html, "1.0.1", "1e247c0886c3fdb0d3f4b184b653a8d6fb96e4ad0d0389267fe4f36968772e24", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:benchee_json, "~> 1.0", [hex: :benchee_json, repo: "hexpm", optional: false]}], "hexpm", "b00a181af7152431901e08f3fc9f7197ed43ff50421a8347b0c80bf45d5b3fef"}, + "benchee_json": {:hex, :benchee_json, "1.0.0", "cc661f4454d5995c08fe10dd1f2f72f229c8f0fb1c96f6b327a8c8fc96a91fe5", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "da05d813f9123505f870344d68fb7c86a4f0f9074df7d7b7e2bb011a63ec231c"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, diff --git a/test/code_generation_test.exs b/test/code_generation_test.exs index 3c5f8f23..06d498f5 100644 --- a/test/code_generation_test.exs +++ b/test/code_generation_test.exs @@ -180,7 +180,7 @@ defmodule Protox.CodeGenerationTest do def application do [ - extra_applications: [:logger] + extra_applications: [:logger, :benchee] ] end @@ -188,7 +188,8 @@ defmodule Protox.CodeGenerationTest do [ {:protox, path: "#{File.cwd!()}"}, {:dialyxir, "~> 1.0", only: [:test, :dev], runtime: false}, - {:credo, "~> 1.4", only: [:test, :dev], runtime: false} + {:credo, "~> 1.4", only: [:test, :dev], runtime: false}, + {:benchee, "~> 1.0", only: [:test, :dev], runtime: false} ] end end