THRIFT-5944: Corrected and expanded Ruby protocol benchmarks
diff --git a/lib/rb/lib/thrift/thrift_native.rb b/lib/rb/lib/thrift/thrift_native.rb
index 372f3a5..d6dc4c2 100644
--- a/lib/rb/lib/thrift/thrift_native.rb
+++ b/lib/rb/lib/thrift/thrift_native.rb
@@ -20,5 +20,5 @@
begin
require "thrift_native"
rescue LoadError
- puts "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
+ warn "Unable to load thrift_native extension. Defaulting to pure Ruby libraries."
end
diff --git a/test/rb/benchmarks/README.md b/test/rb/benchmarks/README.md
new file mode 100644
index 0000000..b1eff1c
--- /dev/null
+++ b/test/rb/benchmarks/README.md
@@ -0,0 +1,107 @@
+# Ruby Protocol Benchmarks
+
+This directory holds a small harness for quick Ruby protocol benchmarks.
+Use it to spot read and write regressions in tree.
+
+## Quick Start
+Run the script with plain `ruby` from the repo root.
+
+```sh
+ruby test/rb/benchmarks/protocol_benchmark.rb
+```
+
+This runs the full benchmark set:
+
+- Ruby binary
+- Ruby compact
+- Ruby JSON
+- Header binary
+- Header compact
+- Header zlib
+- C binary, if `thrift_native.so` loads
+
+## Options
+Use flags or env vars to tune a run.
+
+```sh
+ruby test/rb/benchmarks/protocol_benchmark.rb --large-runs 1 --small-runs 10000
+```
+
+- `--json`
+- `THRIFT_BENCHMARK_LARGE_RUNS`
+- `THRIFT_BENCHMARK_SMALL_RUNS`
+- `THRIFT_BENCHMARK_SKIP_NATIVE=1`
+- `THRIFT_BENCHMARK_SCENARIOS`
+
+```sh
+ruby test/rb/benchmarks/protocol_benchmark.rb --json > benchmark.json
+```
+
+> [!NOTE]
+> `--json` keeps the warm-up pass, but only prints measured results.
+
+> [!TIP]
+> Set `THRIFT_BENCHMARK_SKIP_NATIVE=1` to force a pure-Ruby run.
+
+## Scenario IDs
+Use `--scenarios` or `THRIFT_BENCHMARK_SCENARIOS` to run only part of the matrix.
+
+```sh
+ruby test/rb/benchmarks/protocol_benchmark.rb --scenarios rb-bin-write-large,rb-json-read-large,hdr-zlib-read-small
+```
+
+Each ID has four parts:
+
+- family: `rb`, `c`, or `hdr`
+- protocol: `bin`, `cmp`, `json`, or `zlib`
+- operation: `write` or `read`
+- size: `large` or `small`
+
+### Full Table
+
+| ID | Family | Protocol | Operation | Size |
+| --- | --- | --- | --- | --- |
+| `rb-bin-write-large` | Ruby | binary | write | large |
+| `rb-bin-read-large` | Ruby | binary | read | large |
+| `c-bin-write-large` | C native | binary | write | large |
+| `c-bin-read-large` | C native | binary | read | large |
+| `rb-cmp-write-large` | Ruby | compact | write | large |
+| `rb-cmp-read-large` | Ruby | compact | read | large |
+| `rb-json-write-large` | Ruby | JSON | write | large |
+| `rb-json-read-large` | Ruby | JSON | read | large |
+| `rb-bin-write-small` | Ruby | binary | write | small |
+| `rb-bin-read-small` | Ruby | binary | read | small |
+| `c-bin-write-small` | C native | binary | write | small |
+| `c-bin-read-small` | C native | binary | read | small |
+| `rb-cmp-write-small` | Ruby | compact | write | small |
+| `rb-cmp-read-small` | Ruby | compact | read | small |
+| `rb-json-write-small` | Ruby | JSON | write | small |
+| `rb-json-read-small` | Ruby | JSON | read | small |
+| `hdr-bin-write-small` | Header | binary | write | small |
+| `hdr-bin-read-small` | Header | binary | read | small |
+| `hdr-cmp-write-small` | Header | compact | write | small |
+| `hdr-cmp-read-small` | Header | compact | read | small |
+| `hdr-zlib-write-small` | Header | zlib | write | small |
+| `hdr-zlib-read-small` | Header | zlib | read | small |
+
+> [!NOTE]
+> Native-only IDs fail if `thrift_native.so` is not available.
+
+## Reference
+
+### What It Measures
+
+- Large jobs serialize and deserialize one nested `Nested4` payload by default.
+- Small jobs serialize and deserialize many `OneOfEach` payloads.
+- Read jobs use payloads built before timing so they measure read cost, not payload construction.
+- Header jobs flush after each struct so reads benchmark framed messages, not a buffered write that was never emitted.
+
+### Files
+
+- `protocol_benchmark.rb`: benchmark harness and scenario definitions
+- `../fixtures/structs.rb`: sample structs used by the benchmark
+
+### Notes
+This harness is for quick in-tree checks.
+Use `--json` if you want structured output for scripts, result diffs, or branch comparisons.
+Run it more than once if you want a wider sample.
diff --git a/test/rb/benchmarks/protocol_benchmark.rb b/test/rb/benchmarks/protocol_benchmark.rb
index 897743f..aa39e25 100644
--- a/test/rb/benchmarks/protocol_benchmark.rb
+++ b/test/rb/benchmarks/protocol_benchmark.rb
@@ -17,202 +17,379 @@
# under the License.
#
-$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb lib])
-$LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. .. .. lib rb ext])
+THRIFT_BENCHMARK_SKIP_NATIVE = ENV.fetch('THRIFT_BENCHMARK_SKIP_NATIVE', '').match?(/\A(?:1|true|yes|on)\z/i)
-require 'thrift'
+lib_path = File.expand_path('../../../lib/rb/lib', __dir__)
+ext_path = File.expand_path('../../../lib/rb/ext', __dir__)
+
+$LOAD_PATH.unshift lib_path unless $LOAD_PATH.include?(lib_path)
+
+if THRIFT_BENCHMARK_SKIP_NATIVE
+ $LOAD_PATH.delete(ext_path)
+else
+ $LOAD_PATH.unshift ext_path unless $LOAD_PATH.include?(ext_path)
+end
+
+if THRIFT_BENCHMARK_SKIP_NATIVE
+ File.open(File::NULL, 'w') do |null_stdout|
+ original_stdout = $stdout
+ $stdout = null_stdout
+ begin
+ require 'thrift'
+ ensure
+ $stdout = original_stdout
+ end
+ end
+else
+ require 'thrift'
+end
require 'benchmark'
-require 'rubygems'
-require 'set'
-require 'pp'
+require 'json'
+require 'optparse'
# require 'ruby-debug'
# require 'ruby-prof'
-require File.join(File.dirname(__FILE__), '../fixtures/structs')
+require File.expand_path('../fixtures/structs', __dir__)
-transport1 = Thrift::MemoryBuffer.new
-ruby_binary_protocol = Thrift::BinaryProtocol.new(transport1)
+module ProtocolBenchmark
+ DEFAULT_LARGE_RUNS = 1
+ DEFAULT_SMALL_RUNS = 10_000
+ ALL_SCENARIO_IDS = %w[
+ rb-bin-write-large rb-bin-read-large c-bin-write-large c-bin-read-large
+ rb-cmp-write-large rb-cmp-read-large rb-json-write-large rb-json-read-large
+ rb-bin-write-small rb-bin-read-small c-bin-write-small c-bin-read-small
+ rb-cmp-write-small rb-cmp-read-small rb-json-write-small rb-json-read-small
+ hdr-bin-write-small hdr-bin-read-small hdr-cmp-write-small hdr-cmp-read-small
+ hdr-zlib-write-small hdr-zlib-read-small
+ ].freeze
+ NATIVE_SCENARIO_IDS = %w[
+ c-bin-write-large c-bin-read-large c-bin-write-small c-bin-read-small
+ ].freeze
-transport2 = Thrift::MemoryBuffer.new
-c_fast_binary_protocol = Thrift::BinaryProtocolAccelerated.new(transport2)
+ module_function
-transport3 = Thrift::MemoryBuffer.new
-header_binary_protocol = Thrift::HeaderProtocol.new(transport3)
+ def parse_run_options(argv = ARGV, env: ENV)
+ options = {
+ large_runs: env.fetch('THRIFT_BENCHMARK_LARGE_RUNS', DEFAULT_LARGE_RUNS),
+ small_runs: env.fetch('THRIFT_BENCHMARK_SMALL_RUNS', DEFAULT_SMALL_RUNS),
+ scenarios: env['THRIFT_BENCHMARK_SCENARIOS'],
+ json: false
+ }
-transport4 = Thrift::MemoryBuffer.new
-header_compact_protocol = Thrift::HeaderProtocol.new(transport4, nil, Thrift::HeaderSubprotocolID::COMPACT)
+ OptionParser.new do |parser|
+ parser.on('--large-runs N', Integer) { |value| options[:large_runs] = value }
+ parser.on('--small-runs N', Integer) { |value| options[:small_runs] = value }
+ parser.on('--scenarios IDS', String) { |value| options[:scenarios] = value }
+ parser.on('--json') { options[:json] = true }
+ end.parse!(argv.dup)
-transport5 = Thrift::MemoryBuffer.new
-header_zlib_protocol = Thrift::HeaderProtocol.new(transport5)
-header_zlib_protocol.add_transform(Thrift::HeaderTransformID::ZLIB)
-
-ooe = Fixtures::Structs::OneOfEach.new
-ooe.im_true = true
-ooe.im_false = false
-ooe.a_bite = -42
-ooe.integer16 = 27000
-ooe.integer32 = 1<<24
-ooe.integer64 = 6000 * 1000 * 1000
-ooe.double_precision = Math::PI
-ooe.some_characters = "Debug THIS!"
-ooe.zomg_unicode = "\xd7\n\a\t"
-
-n1 = Fixtures::Structs::Nested1.new
-n1.a_list = []
-n1.a_list << ooe << ooe << ooe << ooe
-n1.i32_map = {}
-n1.i32_map[1234] = ooe
-n1.i32_map[46345] = ooe
-n1.i32_map[-34264] = ooe
-n1.i64_map = {}
-n1.i64_map[43534986783945] = ooe
-n1.i64_map[-32434639875122] = ooe
-n1.dbl_map = {}
-n1.dbl_map[324.65469834] = ooe
-n1.dbl_map[-9458672340.4986798345112] = ooe
-n1.str_map = {}
-n1.str_map['sdoperuix'] = ooe
-n1.str_map['pwoerxclmn'] = ooe
-
-n2 = Fixtures::Structs::Nested2.new
-n2.a_list = []
-n2.a_list << n1 << n1 << n1 << n1 << n1
-n2.i32_map = {}
-n2.i32_map[398345] = n1
-n2.i32_map[-2345] = n1
-n2.i32_map[12312] = n1
-n2.i64_map = {}
-n2.i64_map[2349843765934] = n1
-n2.i64_map[-123234985495] = n1
-n2.i64_map[0] = n1
-n2.dbl_map = {}
-n2.dbl_map[23345345.38927834] = n1
-n2.dbl_map[-1232349.5489345] = n1
-n2.dbl_map[-234984574.23498725] = n1
-n2.str_map = {}
-n2.str_map[''] = n1
-n2.str_map['sdflkertpioux'] = n1
-n2.str_map['sdfwepwdcjpoi'] = n1
-
-n3 = Fixtures::Structs::Nested3.new
-n3.a_list = []
-n3.a_list << n2 << n2 << n2 << n2 << n2
-n3.i32_map = {}
-n3.i32_map[398345] = n2
-n3.i32_map[-2345] = n2
-n3.i32_map[12312] = n2
-n3.i64_map = {}
-n3.i64_map[2349843765934] = n2
-n3.i64_map[-123234985495] = n2
-n3.i64_map[0] = n2
-n3.dbl_map = {}
-n3.dbl_map[23345345.38927834] = n2
-n3.dbl_map[-1232349.5489345] = n2
-n3.dbl_map[-234984574.23498725] = n2
-n3.str_map = {}
-n3.str_map[''] = n2
-n3.str_map['sdflkertpioux'] = n2
-n3.str_map['sdfwepwdcjpoi'] = n2
-
-n4 = Fixtures::Structs::Nested4.new
-n4.a_list = []
-n4.a_list << n3
-n4.i32_map = {}
-n4.i32_map[-2345] = n3
-n4.i64_map = {}
-n4.i64_map[2349843765934] = n3
-n4.dbl_map = {}
-n4.dbl_map[-1232349.5489345] = n3
-n4.str_map = {}
-n4.str_map[''] = n3
-
-# prof = RubyProf.profile do
-# n4.write(c_fast_binary_protocol)
-# Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
-# end
-#
-# printer = RubyProf::GraphHtmlPrinter.new(prof)
-# printer.print(STDOUT, :min_percent=>0)
-
-Benchmark.bmbm do |x|
- x.report("ruby write large (1MB) structure once") do
- n4.write(ruby_binary_protocol)
+ {
+ large_runs: normalize_run_count(options[:large_runs], 'large runs'),
+ small_runs: normalize_run_count(options[:small_runs], 'small runs'),
+ scenarios: normalize_scenarios(options[:scenarios]),
+ json: options[:json]
+ }
end
- x.report("ruby read large (1MB) structure once") do
- Fixtures::Structs::Nested4.new.read(ruby_binary_protocol)
+ def normalize_run_count(value, name)
+ count = value.is_a?(String) ? Integer(value, 10) : Integer(value)
+ raise ArgumentError, "#{name} must be >= 1" if count < 1
+
+ count
end
- x.report("c write large (1MB) structure once") do
- n4.write(c_fast_binary_protocol)
+ def large_run_label(count)
+ count == 1 ? 'once' : "#{count} times"
end
- x.report("c read large (1MB) structure once") do
- Fixtures::Structs::Nested4.new.read(c_fast_binary_protocol)
+ def normalize_scenarios(value)
+ return nil if value.nil?
+
+ scenario_ids = value.split(/[\s,]+/).filter_map do |scenario_id|
+ normalized = scenario_id.strip
+ normalized unless normalized.empty?
+ end
+
+ scenario_ids.empty? ? nil : scenario_ids.uniq
end
- x.report("ruby write 10_000 small structures") do
- 10_000.times do
- ooe.write(ruby_binary_protocol)
+ def binary_protocol_builder(accelerated: false)
+ protocol_class =
+ if accelerated && native_available?
+ Thrift::BinaryProtocolAccelerated
+ else
+ Thrift::BinaryProtocol
+ end
+
+ lambda do |buffer = nil|
+ transport = Thrift::MemoryBufferTransport.new(buffer)
+ [transport, protocol_class.new(transport)]
end
end
- x.report("ruby read 10_000 small structures") do
- 10_000.times do
- Fixtures::Structs::OneOfEach.new.read(ruby_binary_protocol)
+ def compact_protocol_builder
+ lambda do |buffer = nil|
+ transport = Thrift::MemoryBufferTransport.new(buffer)
+ [transport, Thrift::CompactProtocol.new(transport)]
end
end
- x.report("c write 10_000 small structures") do
- 10_000.times do
- ooe.write(c_fast_binary_protocol)
+ def json_protocol_builder
+ lambda do |buffer = nil|
+ transport = Thrift::MemoryBufferTransport.new(buffer)
+ [transport, Thrift::JsonProtocol.new(transport)]
end
end
- x.report("c read 10_000 small structures") do
- 10_000.times do
- Fixtures::Structs::OneOfEach.new.read(c_fast_binary_protocol)
+ def header_protocol_builder(default_protocol:, zlib: false)
+ lambda do |buffer = nil|
+ transport = Thrift::MemoryBufferTransport.new(buffer)
+ protocol = Thrift::HeaderProtocol.new(transport, nil, default_protocol)
+ protocol.add_transform(Thrift::HeaderTransformID::ZLIB) if zlib
+ [transport, protocol]
end
end
- x.report("header (binary) write 10_000 small structures") do
- 10_000.times do
- ooe.write(header_binary_protocol)
- header_binary_protocol.trans.flush
+ def serialize(builder, value, count: 1)
+ transport, protocol = builder.call
+
+ count.times do
+ value.write(protocol)
+ flush(protocol)
+ end
+
+ transport.read(transport.available)
+ end
+
+ def deserialize(builder, struct_class, payload, count: 1)
+ _transport, protocol = builder.call(payload.dup)
+ value = nil
+
+ count.times do
+ value = struct_class.new
+ value.read(protocol)
+ end
+
+ value
+ end
+
+ def write(builder, value, count: 1)
+ _transport, protocol = builder.call
+
+ count.times do
+ value.write(protocol)
+ flush(protocol)
end
end
- x.report("header (binary) read 10_000 small structures") do
- 10_000.times do
- Fixtures::Structs::OneOfEach.new.read(header_binary_protocol)
+ def flush(protocol)
+ protocol.trans.flush if protocol.trans.is_a?(Thrift::HeaderTransport)
+ end
+
+ def build_sample_structs
+ ooe = Fixtures::Structs::OneOfEach.new
+ ooe.im_true = true
+ ooe.im_false = false
+ ooe.a_bite = -42
+ ooe.integer16 = 27_000
+ ooe.integer32 = 1 << 24
+ ooe.integer64 = 6000 * 1000 * 1000
+ ooe.double_precision = Math::PI
+ ooe.some_characters = 'Debug THIS!'
+ ooe.zomg_unicode = "\u00D7\n\a\t"
+
+ n1 = Fixtures::Structs::Nested1.new
+ n1.a_list = [ooe, ooe, ooe, ooe]
+ n1.i32_map = {1234 => ooe, 46_345 => ooe, -34_264 => ooe}
+ n1.i64_map = {43_534_986_783_945 => ooe, -32_434_639_875_122 => ooe}
+ n1.dbl_map = {324.65469834 => ooe, -9_458_672_340.49868 => ooe}
+ n1.str_map = {'sdoperuix' => ooe, 'pwoerxclmn' => ooe}
+
+ n2 = Fixtures::Structs::Nested2.new
+ n2.a_list = [n1, n1, n1, n1, n1]
+ n2.i32_map = {398_345 => n1, -2345 => n1, 12_312 => n1}
+ n2.i64_map = {2_349_843_765_934 => n1, -123_234_985_495 => n1, 0 => n1}
+ n2.dbl_map = {23_345_345.38927834 => n1, -1_232_349.5489345 => n1, -234_984_574.23498726 => n1}
+ n2.str_map = {'' => n1, 'sdflkertpioux' => n1, 'sdfwepwdcjpoi' => n1}
+
+ n3 = Fixtures::Structs::Nested3.new
+ n3.a_list = [n2, n2, n2, n2, n2]
+ n3.i32_map = {398_345 => n2, -2345 => n2, 12_312 => n2}
+ n3.i64_map = {2_349_843_765_934 => n2, -123_234_985_495 => n2, 0 => n2}
+ n3.dbl_map = {23_345_345.38927834 => n2, -1_232_349.5489345 => n2, -234_984_574.23498726 => n2}
+ n3.str_map = {'' => n2, 'sdflkertpioux' => n2, 'sdfwepwdcjpoi' => n2}
+
+ n4 = Fixtures::Structs::Nested4.new
+ n4.a_list = [n3]
+ n4.i32_map = {-2345 => n3}
+ n4.i64_map = {2_349_843_765_934 => n3}
+ n4.dbl_map = {-1_232_349.5489345 => n3}
+ n4.str_map = {'' => n3}
+
+ [ooe, n4]
+ end
+
+ def scenario(id, label, &job)
+ {id: id, label: label, job: job}
+ end
+
+ def native_available?
+ Thrift.const_defined?(:BinaryProtocolAccelerated, false)
+ end
+
+ def with_scenario_selected(requested_ids, *ids)
+ selected = requested_ids.nil? || ids.any? { |id| requested_ids.include?(id) }
+ return false unless selected
+ return true unless block_given?
+
+ yield
+ end
+
+ def select_scenarios(scenarios, requested_ids, native_available:)
+ return scenarios if requested_ids.nil?
+
+ unknown_ids = requested_ids - ALL_SCENARIO_IDS
+ raise ArgumentError, "unknown scenarios: #{unknown_ids.join(', ')}" if unknown_ids.any?
+
+ unavailable_native_ids = requested_ids & NATIVE_SCENARIO_IDS unless native_available
+ if unavailable_native_ids&.any?
+ raise ArgumentError, "native-only scenarios unavailable without thrift_native: #{unavailable_native_ids.join(', ')}"
+ end
+
+ scenarios.select { |entry| requested_ids.include?(entry[:id]) }
+ end
+
+ def build_scenarios(large_runs:, small_runs:, scenario_ids: nil)
+ unknown_ids = scenario_ids ? scenario_ids - ALL_SCENARIO_IDS : []
+ raise ArgumentError, "unknown scenarios: #{unknown_ids.join(', ')}" if unknown_ids.any?
+
+ one_of_each, nested4 = build_sample_structs
+
+ ruby_binary = binary_protocol_builder
+ ruby_compact = compact_protocol_builder
+ ruby_json = json_protocol_builder
+ accelerated_binary = binary_protocol_builder(accelerated: true)
+ header_binary = header_protocol_builder(default_protocol: Thrift::HeaderSubprotocolID::BINARY)
+ header_compact = header_protocol_builder(default_protocol: Thrift::HeaderSubprotocolID::COMPACT)
+ header_zlib = header_protocol_builder(default_protocol: Thrift::HeaderSubprotocolID::BINARY, zlib: true)
+
+ native_available = native_available?
+ unavailable_native_ids = native_available ? [] : (scenario_ids || []) & NATIVE_SCENARIO_IDS
+ if unavailable_native_ids.any?
+ raise ArgumentError, "native-only scenarios unavailable without thrift_native: #{unavailable_native_ids.join(', ')}"
+ end
+
+ native_scenarios = []
+
+ ruby_large_payload = with_scenario_selected(scenario_ids, 'rb-bin-read-large') { serialize(ruby_binary, nested4, count: large_runs) }
+ ruby_small_payload = with_scenario_selected(scenario_ids, 'rb-bin-read-small') { serialize(ruby_binary, one_of_each, count: small_runs) }
+ compact_large_payload = with_scenario_selected(scenario_ids, 'rb-cmp-read-large') { serialize(ruby_compact, nested4, count: large_runs) }
+ compact_small_payload = with_scenario_selected(scenario_ids, 'rb-cmp-read-small') { serialize(ruby_compact, one_of_each, count: small_runs) }
+ json_large_payload = with_scenario_selected(scenario_ids, 'rb-json-read-large') { serialize(ruby_json, nested4, count: large_runs) }
+ json_small_payload = with_scenario_selected(scenario_ids, 'rb-json-read-small') { serialize(ruby_json, one_of_each, count: small_runs) }
+ header_binary_payload = with_scenario_selected(scenario_ids, 'hdr-bin-read-small') { serialize(header_binary, one_of_each, count: small_runs) }
+ header_compact_payload = with_scenario_selected(scenario_ids, 'hdr-cmp-read-small') { serialize(header_compact, one_of_each, count: small_runs) }
+ header_zlib_payload = with_scenario_selected(scenario_ids, 'hdr-zlib-read-small') { serialize(header_zlib, one_of_each, count: small_runs) }
+
+ if native_available
+ accelerated_large_payload = with_scenario_selected(scenario_ids, 'c-bin-read-large') { serialize(accelerated_binary, nested4, count: large_runs) }
+ accelerated_small_payload = with_scenario_selected(scenario_ids, 'c-bin-read-small') { serialize(accelerated_binary, one_of_each, count: small_runs) }
+
+ native_scenarios = [
+ scenario('c-bin-write-large', "c binary write large (1MB) structure #{large_run_label(large_runs)}") { write(accelerated_binary, nested4, count: large_runs) },
+ scenario('c-bin-read-large', "c binary read large (1MB) structure #{large_run_label(large_runs)}") { deserialize(accelerated_binary, Fixtures::Structs::Nested4, accelerated_large_payload, count: large_runs) },
+ scenario('c-bin-write-small', "c binary write #{small_runs} small structures") { write(accelerated_binary, one_of_each, count: small_runs) },
+ scenario('c-bin-read-small', "c binary read #{small_runs} small structures") { deserialize(accelerated_binary, Fixtures::Structs::OneOfEach, accelerated_small_payload, count: small_runs) }
+ ]
+ elsif !THRIFT_BENCHMARK_SKIP_NATIVE && with_scenario_selected(scenario_ids, *NATIVE_SCENARIO_IDS)
+ warn 'Skipping accelerated binary protocol benchmarks: thrift_native extension is unavailable.'
+ end
+
+ scenario_list = [
+ scenario('rb-bin-write-large', "ruby binary write large (1MB) structure #{large_run_label(large_runs)}") { write(ruby_binary, nested4, count: large_runs) },
+ scenario('rb-bin-read-large', "ruby binary read large (1MB) structure #{large_run_label(large_runs)}") { deserialize(ruby_binary, Fixtures::Structs::Nested4, ruby_large_payload, count: large_runs) },
+ *native_scenarios.first(2),
+ scenario('rb-cmp-write-large', "ruby compact write large (1MB) structure #{large_run_label(large_runs)}") { write(ruby_compact, nested4, count: large_runs) },
+ scenario('rb-cmp-read-large', "ruby compact read large (1MB) structure #{large_run_label(large_runs)}") { deserialize(ruby_compact, Fixtures::Structs::Nested4, compact_large_payload, count: large_runs) },
+ scenario('rb-json-write-large', "ruby json write large (1MB) structure #{large_run_label(large_runs)}") { write(ruby_json, nested4, count: large_runs) },
+ scenario('rb-json-read-large', "ruby json read large (1MB) structure #{large_run_label(large_runs)}") { deserialize(ruby_json, Fixtures::Structs::Nested4, json_large_payload, count: large_runs) },
+ scenario('rb-bin-write-small', "ruby binary write #{small_runs} small structures") { write(ruby_binary, one_of_each, count: small_runs) },
+ scenario('rb-bin-read-small', "ruby binary read #{small_runs} small structures") { deserialize(ruby_binary, Fixtures::Structs::OneOfEach, ruby_small_payload, count: small_runs) },
+ *native_scenarios.drop(2),
+ scenario('rb-cmp-write-small', "ruby compact write #{small_runs} small structures") { write(ruby_compact, one_of_each, count: small_runs) },
+ scenario('rb-cmp-read-small', "ruby compact read #{small_runs} small structures") { deserialize(ruby_compact, Fixtures::Structs::OneOfEach, compact_small_payload, count: small_runs) },
+ scenario('rb-json-write-small', "ruby json write #{small_runs} small structures") { write(ruby_json, one_of_each, count: small_runs) },
+ scenario('rb-json-read-small', "ruby json read #{small_runs} small structures") { deserialize(ruby_json, Fixtures::Structs::OneOfEach, json_small_payload, count: small_runs) },
+ scenario('hdr-bin-write-small', "header binary write #{small_runs} small structures") { write(header_binary, one_of_each, count: small_runs) },
+ scenario('hdr-bin-read-small', "header binary read #{small_runs} small structures") { deserialize(header_binary, Fixtures::Structs::OneOfEach, header_binary_payload, count: small_runs) },
+ scenario('hdr-cmp-write-small', "header compact write #{small_runs} small structures") { write(header_compact, one_of_each, count: small_runs) },
+ scenario('hdr-cmp-read-small', "header compact read #{small_runs} small structures") { deserialize(header_compact, Fixtures::Structs::OneOfEach, header_compact_payload, count: small_runs) },
+ scenario('hdr-zlib-write-small', "header zlib write #{small_runs} small structures") { write(header_zlib, one_of_each, count: small_runs) },
+ scenario('hdr-zlib-read-small', "header zlib read #{small_runs} small structures") { deserialize(header_zlib, Fixtures::Structs::OneOfEach, header_zlib_payload, count: small_runs) }
+ ]
+
+ select_scenarios(scenario_list, scenario_ids, native_available: native_available)
+ end
+
+ def measure_job(job, label: '')
+ result = Benchmark.measure(label, &job)
+ {
+ user: result.utime,
+ system: result.stime,
+ total: result.total,
+ real: result.real
+ }
+ end
+
+ def warm_up_scenarios(scenarios)
+ scenarios.each { |entry| measure_job(entry[:job]) }
+ end
+
+ def benchmark_scenarios(scenarios)
+ scenarios.map do |entry|
+ GC.start
+ {
+ id: entry[:id],
+ label: entry[:label],
+ benchmark: measure_job(entry[:job], label: entry[:label])
+ }
end
end
- x.report("header (compact) write 10_000 small structures") do
- 10_000.times do
- ooe.write(header_compact_protocol)
- header_compact_protocol.trans.flush
+ def run(large_runs: DEFAULT_LARGE_RUNS, small_runs: DEFAULT_SMALL_RUNS, scenarios: nil, json: false)
+ scenario_list = build_scenarios(large_runs: large_runs, small_runs: small_runs, scenario_ids: scenarios)
+
+ if json
+ warm_up_scenarios(scenario_list)
+
+ puts JSON.generate(
+ config: {
+ large_runs: large_runs,
+ small_runs: small_runs,
+ scenarios: scenario_list.map { |entry| entry[:id] },
+ skip_native: THRIFT_BENCHMARK_SKIP_NATIVE,
+ native_available: native_available?
+ },
+ results: benchmark_scenarios(scenario_list)
+ )
+ return
+ end
+
+ Benchmark.bmbm do |x|
+ scenario_list.each do |entry|
+ x.report(entry[:label], &entry[:job])
+ end
end
end
+end
- x.report("header (compact) read 10_000 small structures") do
- 10_000.times do
- Fixtures::Structs::OneOfEach.new.read(header_compact_protocol)
- end
- end
-
- x.report("header (zlib) write 10_000 small structures") do
- 10_000.times do
- ooe.write(header_zlib_protocol)
- header_zlib_protocol.trans.flush
- end
- end
-
- x.report("header (zlib) read 10_000 small structures") do
- 10_000.times do
- Fixtures::Structs::OneOfEach.new.read(header_zlib_protocol)
- end
+if $PROGRAM_NAME == __FILE__
+ begin
+ ProtocolBenchmark.run(**ProtocolBenchmark.parse_run_options)
+ rescue OptionParser::ParseError, ArgumentError => e
+ warn e.message
+ exit 1
end
end
diff --git a/test/rb/fixtures/structs.rb b/test/rb/fixtures/structs.rb
index 79f2997..d24e5d1 100644
--- a/test/rb/fixtures/structs.rb
+++ b/test/rb/fixtures/structs.rb
@@ -22,146 +22,198 @@
module Fixtures
module Structs
class OneBool
- include Thrift::Struct
- attr_accessor :bool
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::BOOL, :name => 'bool'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneByte
- include Thrift::Struct
- attr_accessor :byte
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::BYTE, :name => 'byte'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneI16
- include Thrift::Struct
- attr_accessor :i16
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::I16, :name => 'i16'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneI32
- include Thrift::Struct
- attr_accessor :i32
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::I32, :name => 'i32'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneI64
- include Thrift::Struct
- attr_accessor :i64
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::I64, :name => 'i64'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneDouble
- include Thrift::Struct
- attr_accessor :double
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::DOUBLE, :name => 'double'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneString
- include Thrift::Struct
- attr_accessor :string
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::STRING, :name => 'string'}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneMap
- include Thrift::Struct
- attr_accessor :map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRING}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class NestedMap
- include Thrift::Struct
- attr_accessor :map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
0 => {:type => Thrift::Types::MAP, :name => 'map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::MAP, :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::I32}}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneList
- include Thrift::Struct
- attr_accessor :list
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::STRING}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class NestedList
- include Thrift::Struct
- attr_accessor :list
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
0 => {:type => Thrift::Types::LIST, :name => 'list', :element => {:type => Thrift::Types::LIST, :element => { :type => Thrift::Types::I32 } } }
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class OneSet
- include Thrift::Struct
- attr_accessor :set
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::STRING}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
class NestedSet
- include Thrift::Struct
- attr_accessor :set
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::SET, :name => 'set', :element => {:type => Thrift::Types::SET, :element => { :type => Thrift::Types::STRING } }}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
# struct OneOfEach {
@@ -178,8 +230,8 @@
# 11: binary base64,
# }
class OneOfEach
- include Thrift::Struct
- attr_accessor :im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::BOOL, :name => 'im_true'},
2 => {:type => Thrift::Types::BOOL, :name => 'im_false'},
@@ -191,20 +243,15 @@
8 => {:type => Thrift::Types::STRING, :name => 'some_characters'},
9 => {:type => Thrift::Types::STRING, :name => 'zomg_unicode'},
10 => {:type => Thrift::Types::BOOL, :name => 'what_who'},
- 11 => {:type => Thrift::Types::STRING, :name => 'base64'}
+ 11 => {:type => Thrift::Types::STRING, :name => 'base64', :binary => true}
}
- # Added for assert_equal
- def ==(other)
- [:im_true, :im_false, :a_bite, :integer16, :integer32, :integer64, :double_precision, :some_characters, :zomg_unicode, :what_who, :base64].each do |f|
- var = "@#{f}"
- return false if instance_variable_get(var) != other.instance_variable_get(var)
- end
- true
- end
+ def struct_fields; FIELDS; end
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
# struct Nested1 {
@@ -215,8 +262,8 @@
# 5: map<string, OneOfEach> str_map
# }
class Nested1
- include Thrift::Struct
- attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}},
@@ -225,8 +272,12 @@
5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => OneOfEach}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
# struct Nested2 {
@@ -237,8 +288,8 @@
# 5: map<string, Nested1> str_map
# }
class Nested2
- include Thrift::Struct
- attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested1}},
2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}},
@@ -247,8 +298,12 @@
5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested1}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
# struct Nested3 {
@@ -259,8 +314,8 @@
# 5: map<string, Nested2> str_map
# }
class Nested3
- include Thrift::Struct
- attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested2}},
2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}},
@@ -269,8 +324,12 @@
5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested2}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
# struct Nested4 {
@@ -281,8 +340,8 @@
# 5: map<string, Nested3> str_map
# }
class Nested4
- include Thrift::Struct
- attr_accessor :a_list, :i32_map, :i64_map, :dbl_map, :str_map
+ include Thrift::Struct, Thrift::Struct_Union
+
FIELDS = {
1 => {:type => Thrift::Types::LIST, :name => 'a_list', :element => {:type => Thrift::Types::STRUCT, :class => Nested3}},
2 => {:type => Thrift::Types::MAP, :name => 'i32_map', :key => {:type => Thrift::Types::I32}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}},
@@ -291,8 +350,12 @@
5 => {:type => Thrift::Types::MAP, :name => 'str_map', :key => {:type => Thrift::Types::STRING}, :value => {:type => Thrift::Types::STRUCT, :class => Nested3}}
}
+ def struct_fields; FIELDS; end
+
def validate
end
+
+ Thrift::Struct.generate_accessors self
end
end
end