amq-protocol 2.3.4 → 2.5.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/AGENTS.md +23 -0
- data/CLAUDE.md +1 -0
- data/ChangeLog.md +30 -3
- data/GEMINI.md +1 -0
- 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/client.rb +30 -37
- 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 +9 -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 +14 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3f43ce885cd75341d0b5b784857aada66eab3990174d7b145b572a04286f41f
|
|
4
|
+
data.tar.gz: 7ec8798e7d34ec8d4218b209e67d75fc45fe254e80997d761f8c7c2cf0521366
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 378697156e2adf582c060b1be25c374f68cf7764008cebf926a07a3c1f02f7669680e2d934a4cec94a18f3e779889a45b4998113a076844d4298e230914683c6
|
|
7
|
+
data.tar.gz: 8ff53d0b35d42dbfa6a515391b26593340446ceb985740bd1ec6d5e0cb043beef4d46e7c262ee918ac753a4a96db746e1dceaba4fc8b479228340045080650ee
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Instructions for AI Agents
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This is a pure Ruby implementation of a AMQP 0-9-1 protocol parser
|
|
6
|
+
(more specifically% serialization, deserialization, framing) used by
|
|
7
|
+
[Bunny](https://github.com/ruby-amqp/bunny), a Ruby AMQP 0-9-1 client for RabbitMQ.
|
|
8
|
+
|
|
9
|
+
## Target Ruby Version
|
|
10
|
+
|
|
11
|
+
This library targets Ruby 3.0 and later versions.
|
|
12
|
+
|
|
13
|
+
## Comments
|
|
14
|
+
|
|
15
|
+
* Only add very important comments, both in tests and in the implementation
|
|
16
|
+
|
|
17
|
+
## Git Instructions
|
|
18
|
+
|
|
19
|
+
* Never add yourself to the list of commit co-authors
|
|
20
|
+
|
|
21
|
+
## Style Guide
|
|
22
|
+
|
|
23
|
+
* Never add full stops to Markdown list items
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
data/ChangeLog.md
CHANGED
|
@@ -1,8 +1,35 @@
|
|
|
1
|
-
## Changes between 2.
|
|
1
|
+
## Changes between 2.5.0 and 2.6.0 (in development)
|
|
2
2
|
|
|
3
3
|
No changes yet.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## Changes between 2.4.0 and 2.5.0 (Dec 31, 2025)
|
|
7
|
+
|
|
8
|
+
### Additional Consumer Hot Path Optimizations
|
|
9
|
+
|
|
10
|
+
A few more decode method optimizations for consumer delivery code paths.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Changes between 2.3.4 and 2.4.0 (Dec 30, 2025)
|
|
14
|
+
|
|
15
|
+
### Performance Improvements
|
|
16
|
+
|
|
17
|
+
Optimized encoding and decoding hot paths:
|
|
18
|
+
|
|
19
|
+
* Built-in `Q>`/`q>` packing/unpacking directives are 6-7x faster than the original implementation (that originally targeted Ruby 1.8.x)
|
|
20
|
+
* Switched to `unpack1` instead of `unpack().first` throughout
|
|
21
|
+
* Use `byteslice` instead of `slice` for binary string operations
|
|
22
|
+
* Use `getbyte` for single byte access (4x faster than alternatives)
|
|
23
|
+
* Adopted `frozen_string_literal` pragma
|
|
24
|
+
|
|
25
|
+
The improvements on Ruby 3.4 are very meaningful:
|
|
26
|
+
|
|
27
|
+
* `AMQ::Pack.pack_uint64_big_endian`: about 6.6x faster
|
|
28
|
+
* `AMQ::Pack.unpack_uint64_big_endian`: about 7.2x faster
|
|
29
|
+
* `Basic.Deliver.decode`: about 1.7x faster
|
|
30
|
+
* `Basic.Ack/Nack/Reject.encode`: about 2.5x faster
|
|
31
|
+
|
|
32
|
+
|
|
6
33
|
## Changes between 2.3.3 and 2.3.4 (May 12, 2025)
|
|
7
34
|
|
|
8
35
|
### (Forward) Compatibility with Ruby 3.5
|
|
@@ -16,13 +43,13 @@ GitHub issue: [#80](https://github.com/ruby-amqp/amq-protocol/pull/80)
|
|
|
16
43
|
|
|
17
44
|
### Improved Compatibility with Ruby 3.4
|
|
18
45
|
|
|
19
|
-
|
|
46
|
+
Contributed by @BenTalagan.
|
|
20
47
|
|
|
21
48
|
GitHub issue: [#79](https://github.com/ruby-amqp/amq-protocol/pull/79)
|
|
22
49
|
|
|
23
50
|
### Support Binary-Encoded Frozen Strings for Payloads
|
|
24
51
|
|
|
25
|
-
|
|
52
|
+
Contributed by @djrodgerspryor.
|
|
26
53
|
|
|
27
54
|
GitHub issue: [#78](https://github.com/ruby-amqp/amq-protocol/pull/78)
|
|
28
55
|
|
data/GEMINI.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
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
|