amq-protocol 2.3.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +26 -3
- data/Gemfile +5 -0
- data/README.md +2 -7
- data/benchmarks/frame_encoding.rb +75 -0
- data/benchmarks/method_encoding.rb +198 -0
- data/benchmarks/pack_unpack.rb +158 -0
- data/benchmarks/run_all.rb +64 -0
- data/benchmarks/table_encoding.rb +110 -0
- data/lib/amq/bit_set.rb +1 -0
- data/lib/amq/endianness.rb +2 -0
- data/lib/amq/int_allocator.rb +1 -0
- data/lib/amq/pack.rb +33 -42
- data/lib/amq/protocol/constants.rb +2 -0
- data/lib/amq/protocol/exceptions.rb +3 -1
- data/lib/amq/protocol/float_32bit.rb +2 -0
- data/lib/amq/protocol/frame.rb +7 -3
- data/lib/amq/protocol/table.rb +20 -17
- data/lib/amq/protocol/table_value_decoder.rb +48 -52
- data/lib/amq/protocol/table_value_encoder.rb +1 -0
- data/lib/amq/protocol/type_constants.rb +1 -0
- data/lib/amq/protocol/version.rb +1 -1
- data/lib/amq/settings.rb +1 -0
- data/spec/amq/bit_set_spec.rb +22 -0
- data/spec/amq/endianness_spec.rb +23 -0
- data/spec/amq/int_allocator_spec.rb +26 -3
- data/spec/amq/pack_spec.rb +14 -24
- data/spec/amq/protocol/exceptions_spec.rb +70 -0
- data/spec/amq/protocol/float_32bit_spec.rb +27 -0
- data/spec/amq/protocol/frame_spec.rb +64 -0
- data/spec/amq/protocol/table_spec.rb +32 -0
- data/spec/amq/protocol/value_decoder_spec.rb +97 -0
- data/spec/amq/protocol/value_encoder_spec.rb +21 -0
- data/spec/amq/settings_spec.rb +37 -1
- data/spec/amq/uri_parsing_spec.rb +7 -0
- metadata +11 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d6619ef67f8b1e30048f898796d21854eae2a35dfdfe83286e0a5e894cbbf1c
|
|
4
|
+
data.tar.gz: 0d62824daba6da208b96b7681e4c1db4f5c32a93a7a8a7714b875b8ad8f603e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cac00e1594a26adcb0863b6505ba789e8fce9a1dee3a38584e58cf838479661ba857a37ac3d747c69e7751549e15877a6478dd51559a8003af70c68e856badb1
|
|
7
|
+
data.tar.gz: 040e923fd9dcf5931a157007835085ec30b7ac44897c5a76ee05545f14f76cf431a3611f73fddb562e6bf9230fb82151fc808bfed24bc2244f12c85164541b6e
|
data/ChangeLog.md
CHANGED
|
@@ -1,7 +1,30 @@
|
|
|
1
|
-
## Changes between 2.
|
|
1
|
+
## Changes between 2.4.0 and 2.5.0 (in development)
|
|
2
2
|
|
|
3
3
|
No changes yet.
|
|
4
4
|
|
|
5
|
+
## Changes between 2.3.4 and 2.4.0 (Dec 30, 2025)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
* Fixed `BadResponseError` constructor which referenced undefined variable `data` instead of `actual`
|
|
10
|
+
|
|
11
|
+
### Performance Improvements
|
|
12
|
+
|
|
13
|
+
Optimized encoding and decoding hot paths:
|
|
14
|
+
|
|
15
|
+
* Built-in `Q>`/`q>` packing/unpacking directives are 6-7x faster than the original implementation (that originally targeted Ruby 1.8.x)
|
|
16
|
+
* Switched to `unpack1` instead of `unpack().first` throughout
|
|
17
|
+
* Use `byteslice` instead of `slice` for binary string operations
|
|
18
|
+
* Use `getbyte` for single byte access (4x faster than alternatives)
|
|
19
|
+
* Adopted `frozen_string_literal` pragma
|
|
20
|
+
|
|
21
|
+
The improvements on Ruby 3.4 are very meaningful:
|
|
22
|
+
|
|
23
|
+
* `AMQ::Pack.pack_uint64_big_endian`: about 6.6x faster
|
|
24
|
+
* `AMQ::Pack.unpack_uint64_big_endian`: about 7.2x faster
|
|
25
|
+
* `Basic.Deliver.decode`: about 1.7x faster
|
|
26
|
+
* `Basic.Ack/Nack/Reject.encode`: about 2.5x faster
|
|
27
|
+
|
|
5
28
|
|
|
6
29
|
## Changes between 2.3.3 and 2.3.4 (May 12, 2025)
|
|
7
30
|
|
|
@@ -16,13 +39,13 @@ GitHub issue: [#80](https://github.com/ruby-amqp/amq-protocol/pull/80)
|
|
|
16
39
|
|
|
17
40
|
### Improved Compatibility with Ruby 3.4
|
|
18
41
|
|
|
19
|
-
|
|
42
|
+
Contributed by @BenTalagan.
|
|
20
43
|
|
|
21
44
|
GitHub issue: [#79](https://github.com/ruby-amqp/amq-protocol/pull/79)
|
|
22
45
|
|
|
23
46
|
### Support Binary-Encoded Frozen Strings for Payloads
|
|
24
47
|
|
|
25
|
-
|
|
48
|
+
Contributed by @djrodgerspryor.
|
|
26
49
|
|
|
27
50
|
GitHub issue: [#78](https://github.com/ruby-amqp/amq-protocol/pull/78)
|
|
28
51
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -9,6 +9,8 @@ needs for you, including RabbitMQ extensions to AMQP 0.9.1.
|
|
|
9
9
|
|
|
10
10
|
## Supported Ruby Versions
|
|
11
11
|
|
|
12
|
+
* amq-protocol `2.3.4` has fixes for Ruby 3.5 compatibility
|
|
13
|
+
* amq-protocol `2.3.3` has fixes for Ruby 3.4 compatibility
|
|
12
14
|
* amq-protocol `2.3.0` only supports Ruby 2.2+.
|
|
13
15
|
* amq-protocol `2.0.0` through `2.2.0` and later supports Ruby 2.0+.
|
|
14
16
|
* amq-protocol `1.9.2` was the last version to support Ruby 1.8 and 1.9.
|
|
@@ -45,21 +47,14 @@ To run tests, use
|
|
|
45
47
|
amq-protocol is maintained by [Michael Klishin](https://github.com/michaelklishin).
|
|
46
48
|
|
|
47
49
|
|
|
48
|
-
## CI Status
|
|
49
|
-
|
|
50
|
-
[](https://travis-ci.org/ruby-amqp/amq-protocol)
|
|
51
|
-
|
|
52
|
-
|
|
53
50
|
## Issues
|
|
54
51
|
|
|
55
52
|
Please report any issues you may find to our [Issue tracker](http://github.com/ruby-amqp/amq-protocol/issues) on GitHub.
|
|
56
53
|
|
|
57
|
-
|
|
58
54
|
## Mailing List
|
|
59
55
|
|
|
60
56
|
Any questions you may have should be sent to the [Ruby AMQP mailing list](http://groups.google.com/group/ruby-amqp).
|
|
61
57
|
|
|
62
|
-
|
|
63
58
|
## License
|
|
64
59
|
|
|
65
60
|
MIT (see LICENSE in the repository root).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
6
|
+
|
|
7
|
+
require "amq/protocol/client"
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
|
|
10
|
+
puts
|
|
11
|
+
puts "-" * 80
|
|
12
|
+
puts "Frame Encoding Benchmarks on #{RUBY_DESCRIPTION}"
|
|
13
|
+
puts "-" * 80
|
|
14
|
+
|
|
15
|
+
# Test data
|
|
16
|
+
SMALL_PAYLOAD = "x" * 100
|
|
17
|
+
MEDIUM_PAYLOAD = "x" * 1024
|
|
18
|
+
LARGE_PAYLOAD = "x" * 16384
|
|
19
|
+
|
|
20
|
+
CHANNEL = 1
|
|
21
|
+
|
|
22
|
+
Benchmark.ips do |x|
|
|
23
|
+
x.config(time: 5, warmup: 2)
|
|
24
|
+
|
|
25
|
+
x.report("Frame.encode(:method, small)") do
|
|
26
|
+
AMQ::Protocol::Frame.encode(:method, SMALL_PAYLOAD, CHANNEL)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
x.report("Frame.encode(:method, medium)") do
|
|
30
|
+
AMQ::Protocol::Frame.encode(:method, MEDIUM_PAYLOAD, CHANNEL)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
x.report("Frame.encode(:body, large)") do
|
|
34
|
+
AMQ::Protocol::Frame.encode(:body, LARGE_PAYLOAD, CHANNEL)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
x.report("MethodFrame.new + encode") do
|
|
38
|
+
frame = AMQ::Protocol::MethodFrame.new(SMALL_PAYLOAD, CHANNEL)
|
|
39
|
+
frame.encode
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
x.report("BodyFrame.new + encode") do
|
|
43
|
+
frame = AMQ::Protocol::BodyFrame.new(MEDIUM_PAYLOAD, CHANNEL)
|
|
44
|
+
frame.encode
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
x.report("HeartbeatFrame.encode") do
|
|
48
|
+
AMQ::Protocol::HeartbeatFrame.encode
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
x.compare!
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts
|
|
55
|
+
puts "-" * 80
|
|
56
|
+
puts "Frame Header Decoding"
|
|
57
|
+
puts "-" * 80
|
|
58
|
+
|
|
59
|
+
# Encoded frame headers
|
|
60
|
+
METHOD_HEADER = [1, 0, 1, 0, 0, 0, 100].pack("CnN") # type=1, channel=1, size=100
|
|
61
|
+
BODY_HEADER = [3, 0, 5, 0, 0, 16, 0].pack("CnN") # type=3, channel=5, size=4096
|
|
62
|
+
|
|
63
|
+
Benchmark.ips do |x|
|
|
64
|
+
x.config(time: 5, warmup: 2)
|
|
65
|
+
|
|
66
|
+
x.report("Frame.decode_header (method)") do
|
|
67
|
+
AMQ::Protocol::Frame.decode_header(METHOD_HEADER)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
x.report("Frame.decode_header (body)") do
|
|
71
|
+
AMQ::Protocol::Frame.decode_header(BODY_HEADER)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
x.compare!
|
|
75
|
+
end
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
6
|
+
|
|
7
|
+
require "amq/protocol/client"
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
|
|
10
|
+
puts
|
|
11
|
+
puts "-" * 80
|
|
12
|
+
puts "AMQP Method Encoding/Decoding Benchmarks on #{RUBY_DESCRIPTION}"
|
|
13
|
+
puts "-" * 80
|
|
14
|
+
|
|
15
|
+
FRAME_SIZE = 131072 # 128KB, typical default
|
|
16
|
+
|
|
17
|
+
# Common message properties
|
|
18
|
+
BASIC_PROPERTIES = {
|
|
19
|
+
content_type: "application/json",
|
|
20
|
+
delivery_mode: 2,
|
|
21
|
+
priority: 0,
|
|
22
|
+
headers: { "x-custom" => "value" }
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
MINIMAL_PROPERTIES = {
|
|
26
|
+
delivery_mode: 2
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
# Payloads
|
|
30
|
+
SMALL_BODY = '{"id":1}'.freeze
|
|
31
|
+
MEDIUM_BODY = ('x' * 1024).freeze
|
|
32
|
+
LARGE_BODY = ('x' * 65536).freeze
|
|
33
|
+
|
|
34
|
+
puts "=== Basic.Publish (Full Message Encoding) ==="
|
|
35
|
+
puts "This is the critical hot path for publishing messages"
|
|
36
|
+
puts
|
|
37
|
+
|
|
38
|
+
Benchmark.ips do |x|
|
|
39
|
+
x.config(time: 5, warmup: 2)
|
|
40
|
+
|
|
41
|
+
x.report("Publish small (8B) + minimal props") do
|
|
42
|
+
AMQ::Protocol::Basic::Publish.encode(
|
|
43
|
+
1, # channel
|
|
44
|
+
SMALL_BODY, # payload
|
|
45
|
+
MINIMAL_PROPERTIES,
|
|
46
|
+
"", # exchange
|
|
47
|
+
"test.queue", # routing_key
|
|
48
|
+
false, # mandatory
|
|
49
|
+
false, # immediate
|
|
50
|
+
FRAME_SIZE
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
x.report("Publish small (8B) + full props") do
|
|
55
|
+
AMQ::Protocol::Basic::Publish.encode(
|
|
56
|
+
1,
|
|
57
|
+
SMALL_BODY,
|
|
58
|
+
BASIC_PROPERTIES,
|
|
59
|
+
"",
|
|
60
|
+
"test.queue",
|
|
61
|
+
false,
|
|
62
|
+
false,
|
|
63
|
+
FRAME_SIZE
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
x.report("Publish medium (1KB) + full props") do
|
|
68
|
+
AMQ::Protocol::Basic::Publish.encode(
|
|
69
|
+
1,
|
|
70
|
+
MEDIUM_BODY,
|
|
71
|
+
BASIC_PROPERTIES,
|
|
72
|
+
"",
|
|
73
|
+
"test.queue",
|
|
74
|
+
false,
|
|
75
|
+
false,
|
|
76
|
+
FRAME_SIZE
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
x.report("Publish large (64KB) + full props") do
|
|
81
|
+
AMQ::Protocol::Basic::Publish.encode(
|
|
82
|
+
1,
|
|
83
|
+
LARGE_BODY,
|
|
84
|
+
BASIC_PROPERTIES,
|
|
85
|
+
"",
|
|
86
|
+
"test.queue",
|
|
87
|
+
false,
|
|
88
|
+
false,
|
|
89
|
+
FRAME_SIZE
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
x.compare!
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Create sample encoded methods for decoding benchmarks
|
|
97
|
+
puts
|
|
98
|
+
puts "=== Method Decoding ==="
|
|
99
|
+
|
|
100
|
+
# Simulate encoded Basic.Deliver frame payload (after class/method ID)
|
|
101
|
+
# Basic.Deliver: consumer_tag(shortstr), delivery_tag(longlong), redelivered(bit), exchange(shortstr), routing_key(shortstr)
|
|
102
|
+
def make_deliver_payload(consumer_tag, delivery_tag, exchange, routing_key)
|
|
103
|
+
buffer = String.new(encoding: 'BINARY')
|
|
104
|
+
buffer << consumer_tag.bytesize.chr
|
|
105
|
+
buffer << consumer_tag
|
|
106
|
+
buffer << AMQ::Pack.pack_uint64_big_endian(delivery_tag)
|
|
107
|
+
buffer << "\x00" # redelivered = false
|
|
108
|
+
buffer << exchange.bytesize.chr
|
|
109
|
+
buffer << exchange
|
|
110
|
+
buffer << routing_key.bytesize.chr
|
|
111
|
+
buffer << routing_key
|
|
112
|
+
buffer
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
DELIVER_PAYLOAD_SHORT = make_deliver_payload("ctag", 1, "", "q")
|
|
116
|
+
DELIVER_PAYLOAD_TYPICAL = make_deliver_payload("bunny-consumer-12345", 999999, "amq.topic", "events.user.created")
|
|
117
|
+
|
|
118
|
+
Benchmark.ips do |x|
|
|
119
|
+
x.config(time: 5, warmup: 2)
|
|
120
|
+
|
|
121
|
+
x.report("Basic.Deliver.decode (short)") do
|
|
122
|
+
AMQ::Protocol::Basic::Deliver.decode(DELIVER_PAYLOAD_SHORT)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
x.report("Basic.Deliver.decode (typical)") do
|
|
126
|
+
AMQ::Protocol::Basic::Deliver.decode(DELIVER_PAYLOAD_TYPICAL)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
x.compare!
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
puts
|
|
133
|
+
puts "=== Properties Encoding/Decoding ==="
|
|
134
|
+
|
|
135
|
+
Benchmark.ips do |x|
|
|
136
|
+
x.config(time: 5, warmup: 2)
|
|
137
|
+
|
|
138
|
+
x.report("encode_properties (minimal)") do
|
|
139
|
+
AMQ::Protocol::Basic.encode_properties(100, MINIMAL_PROPERTIES)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
x.report("encode_properties (full)") do
|
|
143
|
+
AMQ::Protocol::Basic.encode_properties(1024, BASIC_PROPERTIES)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
x.compare!
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Create encoded properties for decode benchmark
|
|
150
|
+
ENCODED_MINIMAL_PROPS = AMQ::Protocol::Basic.encode_properties(100, MINIMAL_PROPERTIES)
|
|
151
|
+
ENCODED_FULL_PROPS = AMQ::Protocol::Basic.encode_properties(1024, BASIC_PROPERTIES)
|
|
152
|
+
|
|
153
|
+
# Skip the first 12 bytes (class_id, weight, body_size)
|
|
154
|
+
PROPS_DATA_MINIMAL = ENCODED_MINIMAL_PROPS[12..-1]
|
|
155
|
+
PROPS_DATA_FULL = ENCODED_FULL_PROPS[12..-1]
|
|
156
|
+
|
|
157
|
+
Benchmark.ips do |x|
|
|
158
|
+
x.config(time: 5, warmup: 2)
|
|
159
|
+
|
|
160
|
+
x.report("decode_properties (minimal)") do
|
|
161
|
+
AMQ::Protocol::Basic.decode_properties(PROPS_DATA_MINIMAL)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
x.report("decode_properties (full)") do
|
|
165
|
+
AMQ::Protocol::Basic.decode_properties(PROPS_DATA_FULL)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
x.compare!
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
puts
|
|
172
|
+
puts "=== Other Common Methods ==="
|
|
173
|
+
|
|
174
|
+
Benchmark.ips do |x|
|
|
175
|
+
x.config(time: 5, warmup: 2)
|
|
176
|
+
|
|
177
|
+
x.report("Basic.Ack.encode") do
|
|
178
|
+
AMQ::Protocol::Basic::Ack.encode(1, 12345, false)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
x.report("Basic.Nack.encode") do
|
|
182
|
+
AMQ::Protocol::Basic::Nack.encode(1, 12345, false, true)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
x.report("Basic.Reject.encode") do
|
|
186
|
+
AMQ::Protocol::Basic::Reject.encode(1, 12345, true)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
x.report("Queue.Declare.encode") do
|
|
190
|
+
AMQ::Protocol::Queue::Declare.encode(1, "test.queue", false, true, false, false, false, {})
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
x.report("Exchange.Declare.encode") do
|
|
194
|
+
AMQ::Protocol::Exchange::Declare.encode(1, "test.exchange", "topic", false, true, false, false, false, {})
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
x.compare!
|
|
198
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
6
|
+
|
|
7
|
+
require "amq/protocol/client"
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
|
|
10
|
+
puts
|
|
11
|
+
puts "-" * 80
|
|
12
|
+
puts "Pack/Unpack Micro-benchmarks on #{RUBY_DESCRIPTION}"
|
|
13
|
+
puts "-" * 80
|
|
14
|
+
|
|
15
|
+
# Test data
|
|
16
|
+
UINT64_VALUE = 0x123456789ABCDEF0
|
|
17
|
+
UINT32_VALUE = 0x12345678
|
|
18
|
+
UINT16_VALUE = 0x1234
|
|
19
|
+
|
|
20
|
+
PACKED_UINT64_BE = (+"\x12\x34\x56\x78\x9A\xBC\xDE\xF0").force_encoding('BINARY').freeze
|
|
21
|
+
PACKED_UINT32_BE = (+"\x12\x34\x56\x78").force_encoding('BINARY').freeze
|
|
22
|
+
PACKED_UINT16_BE = (+"\x12\x34").force_encoding('BINARY').freeze
|
|
23
|
+
|
|
24
|
+
puts "=== Pack Operations ==="
|
|
25
|
+
|
|
26
|
+
Benchmark.ips do |x|
|
|
27
|
+
x.config(time: 5, warmup: 2)
|
|
28
|
+
|
|
29
|
+
x.report("AMQ::Pack.pack_uint64_big_endian") do
|
|
30
|
+
AMQ::Pack.pack_uint64_big_endian(UINT64_VALUE)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Alternative: direct pack with 'Q>' directive (Ruby 1.9.3+)
|
|
34
|
+
x.report("[val].pack('Q>')") do
|
|
35
|
+
[UINT64_VALUE].pack('Q>')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
x.report("[val].pack('N') uint32") do
|
|
39
|
+
[UINT32_VALUE].pack('N')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
x.report("[val].pack('n') uint16") do
|
|
43
|
+
[UINT16_VALUE].pack('n')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
x.compare!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts
|
|
50
|
+
puts "=== Unpack Operations ==="
|
|
51
|
+
|
|
52
|
+
Benchmark.ips do |x|
|
|
53
|
+
x.config(time: 5, warmup: 2)
|
|
54
|
+
|
|
55
|
+
x.report("AMQ::Pack.unpack_uint64_big_endian") do
|
|
56
|
+
AMQ::Pack.unpack_uint64_big_endian(PACKED_UINT64_BE)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Alternative: direct unpack with 'Q>' directive
|
|
60
|
+
x.report("data.unpack('Q>')") do
|
|
61
|
+
PACKED_UINT64_BE.unpack('Q>')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
x.report("data.unpack1('Q>')") do
|
|
65
|
+
PACKED_UINT64_BE.unpack1('Q>')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
x.report("data.unpack('N').first") do
|
|
69
|
+
PACKED_UINT32_BE.unpack('N').first
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
x.report("data.unpack1('N')") do
|
|
73
|
+
PACKED_UINT32_BE.unpack1('N')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
x.compare!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
puts
|
|
80
|
+
puts "=== String Slicing ==="
|
|
81
|
+
|
|
82
|
+
DATA = ("x" * 1000).force_encoding('BINARY').freeze
|
|
83
|
+
|
|
84
|
+
Benchmark.ips do |x|
|
|
85
|
+
x.config(time: 5, warmup: 2)
|
|
86
|
+
|
|
87
|
+
x.report("data[offset, length]") do
|
|
88
|
+
DATA[100, 50]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
x.report("data.byteslice(offset, length)") do
|
|
92
|
+
DATA.byteslice(100, 50)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
x.report("data.slice(offset, length)") do
|
|
96
|
+
DATA.slice(100, 50)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
x.compare!
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
puts
|
|
103
|
+
puts "=== Single Byte Access ==="
|
|
104
|
+
|
|
105
|
+
Benchmark.ips do |x|
|
|
106
|
+
x.config(time: 5, warmup: 2)
|
|
107
|
+
|
|
108
|
+
x.report("data[0, 1].unpack('C').first") do
|
|
109
|
+
DATA[0, 1].unpack('C').first
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
x.report("data.getbyte(0)") do
|
|
113
|
+
DATA.getbyte(0)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
x.report("data[0].ord") do
|
|
117
|
+
DATA[0].ord
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
x.report("data.unpack1('C')") do
|
|
121
|
+
DATA.unpack1('C')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
x.compare!
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
puts
|
|
128
|
+
puts "=== Buffer Building ==="
|
|
129
|
+
|
|
130
|
+
Benchmark.ips do |x|
|
|
131
|
+
x.config(time: 5, warmup: 2)
|
|
132
|
+
|
|
133
|
+
x.report("String.new + <<") do
|
|
134
|
+
buf = String.new
|
|
135
|
+
buf << "hello"
|
|
136
|
+
buf << "world"
|
|
137
|
+
buf << [1234].pack('N')
|
|
138
|
+
buf
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
x.report("+'' + <<") do
|
|
142
|
+
buf = +''
|
|
143
|
+
buf << "hello"
|
|
144
|
+
buf << "world"
|
|
145
|
+
buf << [1234].pack('N')
|
|
146
|
+
buf
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
x.report("Array#join") do
|
|
150
|
+
parts = []
|
|
151
|
+
parts << "hello"
|
|
152
|
+
parts << "world"
|
|
153
|
+
parts << [1234].pack('N')
|
|
154
|
+
parts.join
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
x.compare!
|
|
158
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Master benchmark runner
|
|
6
|
+
# Usage: ruby benchmarks/run_all.rb
|
|
7
|
+
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
|
|
10
|
+
BENCHMARK_DIR = File.dirname(__FILE__)
|
|
11
|
+
RESULTS_DIR = File.join(BENCHMARK_DIR, "results")
|
|
12
|
+
|
|
13
|
+
FileUtils.mkdir_p(RESULTS_DIR)
|
|
14
|
+
|
|
15
|
+
benchmarks = %w[
|
|
16
|
+
pack_unpack.rb
|
|
17
|
+
frame_encoding.rb
|
|
18
|
+
table_encoding.rb
|
|
19
|
+
method_encoding.rb
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
|
23
|
+
ruby_version = RUBY_VERSION.gsub('.', '_')
|
|
24
|
+
results_file = File.join(RESULTS_DIR, "benchmark_#{ruby_version}_#{timestamp}.txt")
|
|
25
|
+
|
|
26
|
+
puts "=" * 80
|
|
27
|
+
puts "AMQ-Protocol Benchmark Suite"
|
|
28
|
+
puts "=" * 80
|
|
29
|
+
puts "Ruby: #{RUBY_DESCRIPTION}"
|
|
30
|
+
puts "Time: #{Time.now}"
|
|
31
|
+
puts "Results will be saved to: #{results_file}"
|
|
32
|
+
puts "=" * 80
|
|
33
|
+
puts
|
|
34
|
+
|
|
35
|
+
File.open(results_file, 'w') do |f|
|
|
36
|
+
f.puts "AMQ-Protocol Benchmark Results"
|
|
37
|
+
f.puts "Ruby: #{RUBY_DESCRIPTION}"
|
|
38
|
+
f.puts "Time: #{Time.now}"
|
|
39
|
+
f.puts "=" * 80
|
|
40
|
+
f.puts
|
|
41
|
+
|
|
42
|
+
benchmarks.each do |benchmark|
|
|
43
|
+
benchmark_path = File.join(BENCHMARK_DIR, benchmark)
|
|
44
|
+
|
|
45
|
+
if File.exist?(benchmark_path)
|
|
46
|
+
puts "\n>>> Running #{benchmark}..."
|
|
47
|
+
puts
|
|
48
|
+
|
|
49
|
+
output = `ruby #{benchmark_path} 2>&1`
|
|
50
|
+
puts output
|
|
51
|
+
|
|
52
|
+
f.puts ">>> #{benchmark}"
|
|
53
|
+
f.puts output
|
|
54
|
+
f.puts
|
|
55
|
+
else
|
|
56
|
+
puts "Warning: #{benchmark_path} not found, skipping..."
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
puts
|
|
62
|
+
puts "=" * 80
|
|
63
|
+
puts "Benchmark complete! Results saved to: #{results_file}"
|
|
64
|
+
puts "=" * 80
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
|
|
6
|
+
|
|
7
|
+
require "amq/protocol/client"
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
|
|
10
|
+
puts
|
|
11
|
+
puts "-" * 80
|
|
12
|
+
puts "Table Encoding/Decoding Benchmarks on #{RUBY_DESCRIPTION}"
|
|
13
|
+
puts "-" * 80
|
|
14
|
+
|
|
15
|
+
# Test data - various table sizes and types
|
|
16
|
+
EMPTY_TABLE = {}
|
|
17
|
+
|
|
18
|
+
SIMPLE_TABLE = {
|
|
19
|
+
"key1" => "value1",
|
|
20
|
+
"key2" => 42,
|
|
21
|
+
"key3" => true
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
TYPICAL_HEADERS = {
|
|
25
|
+
"content_type" => "application/json",
|
|
26
|
+
"content_encoding" => "utf-8",
|
|
27
|
+
"x-custom-header" => "some-value",
|
|
28
|
+
"x-retry-count" => 3,
|
|
29
|
+
"x-timestamp" => Time.now.to_i
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
COMPLEX_TABLE = {
|
|
33
|
+
"string" => "hello world",
|
|
34
|
+
"integer" => 123456789,
|
|
35
|
+
"float" => 3.14159,
|
|
36
|
+
"boolean_true" => true,
|
|
37
|
+
"boolean_false" => false,
|
|
38
|
+
"nested" => {
|
|
39
|
+
"inner_key" => "inner_value",
|
|
40
|
+
"inner_number" => 999
|
|
41
|
+
},
|
|
42
|
+
"array" => [1, 2, 3, "four", true]
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
LARGE_TABLE = (1..50).to_h { |i| ["key_#{i}", "value_#{i}"] }.freeze
|
|
46
|
+
|
|
47
|
+
# Pre-encode tables for decode benchmarks
|
|
48
|
+
ENCODED_EMPTY = AMQ::Protocol::Table.encode(EMPTY_TABLE)
|
|
49
|
+
ENCODED_SIMPLE = AMQ::Protocol::Table.encode(SIMPLE_TABLE)
|
|
50
|
+
ENCODED_TYPICAL = AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
|
|
51
|
+
ENCODED_COMPLEX = AMQ::Protocol::Table.encode(COMPLEX_TABLE)
|
|
52
|
+
ENCODED_LARGE = AMQ::Protocol::Table.encode(LARGE_TABLE)
|
|
53
|
+
|
|
54
|
+
puts "Table sizes (bytes): empty=#{ENCODED_EMPTY.bytesize}, simple=#{ENCODED_SIMPLE.bytesize}, typical=#{ENCODED_TYPICAL.bytesize}, complex=#{ENCODED_COMPLEX.bytesize}, large=#{ENCODED_LARGE.bytesize}"
|
|
55
|
+
puts
|
|
56
|
+
|
|
57
|
+
puts "=== Table Encoding ==="
|
|
58
|
+
Benchmark.ips do |x|
|
|
59
|
+
x.config(time: 5, warmup: 2)
|
|
60
|
+
|
|
61
|
+
x.report("encode empty") do
|
|
62
|
+
AMQ::Protocol::Table.encode(EMPTY_TABLE)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
x.report("encode simple (3 keys)") do
|
|
66
|
+
AMQ::Protocol::Table.encode(SIMPLE_TABLE)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
x.report("encode typical headers (5 keys)") do
|
|
70
|
+
AMQ::Protocol::Table.encode(TYPICAL_HEADERS)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
x.report("encode complex (nested/array)") do
|
|
74
|
+
AMQ::Protocol::Table.encode(COMPLEX_TABLE)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
x.report("encode large (50 keys)") do
|
|
78
|
+
AMQ::Protocol::Table.encode(LARGE_TABLE)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
x.compare!
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
puts
|
|
85
|
+
puts "=== Table Decoding ==="
|
|
86
|
+
Benchmark.ips do |x|
|
|
87
|
+
x.config(time: 5, warmup: 2)
|
|
88
|
+
|
|
89
|
+
x.report("decode empty") do
|
|
90
|
+
AMQ::Protocol::Table.decode(ENCODED_EMPTY)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
x.report("decode simple (3 keys)") do
|
|
94
|
+
AMQ::Protocol::Table.decode(ENCODED_SIMPLE)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
x.report("decode typical headers (5 keys)") do
|
|
98
|
+
AMQ::Protocol::Table.decode(ENCODED_TYPICAL)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
x.report("decode complex (nested/array)") do
|
|
102
|
+
AMQ::Protocol::Table.decode(ENCODED_COMPLEX)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
x.report("decode large (50 keys)") do
|
|
106
|
+
AMQ::Protocol::Table.decode(ENCODED_LARGE)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
x.compare!
|
|
110
|
+
end
|