-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
10x performance improvements #178
Merged
Merged
Changes from all commits
Commits
Show all changes
38 commits
Select commit
Hold shift + click to select a range
86cf728
benchmark aimed at measuring http-2 overhead
HoneyryderChuck 1a4f879
improving the encoding context to use more efficient enumerable funct…
HoneyryderChuck 7444155
using ruby 3.4 String#append_as_bytes wherever possible
HoneyryderChuck 0defb72
connection: cache frame properties used multiple times in local varia…
HoneyryderChuck d538ae7
remove redundant parentheses
HoneyryderChuck 669b76f
using local var in framer
HoneyryderChuck e2e1f04
moved empty collection to main namespace
HoneyryderChuck ae55786
refactor frame generation routines to reduce number of string/array a…
HoneyryderChuck b44b092
remove needless array allocations during connection handshake
HoneyryderChuck b76f85e
remove needless check from #connection_settings (they're already done…
HoneyryderChuck 00c8dd3
initialize @h2c_upgrade (for object shape opt)
HoneyryderChuck c36ba67
rewrite of variable usage, avoid multiple Hash#[] calls on flow control
HoneyryderChuck 4a7e2fe
rewrite logic of #encode and #encode_headers, in order to emit frames…
HoneyryderChuck 9e592cb
fixed inconsistency of @last_activated_stream and @last_stream_id
HoneyryderChuck 4897d4f
improve handling of partial data frame generation
HoneyryderChuck 8f62ccc
forego recycling of @streams_recently_closed if first stream of the c…
HoneyryderChuck a571910
another instance of replacing a multi-value compare with Array#includ…
HoneyryderChuck 1bddbb1
header compression: eliminate two intermediate arrays (at least) by y…
HoneyryderChuck 6da27ad
inlined #add_to_table logic
HoneyryderChuck 19025a2
memoizing current table size in encoding context
HoneyryderChuck 00b2c69
using the offset parameter in the #header function
HoneyryderChuck a1804c5
turned huffman class into a module
HoneyryderChuck afa2514
Huffman.encode: reduce the number of intermediate strings
HoneyryderChuck db73b15
supporting buffering to existing string when compressing strings
HoneyryderChuck e148589
when buffer is empty, push-then-pop can be avoided if the remote wind…
HoneyryderChuck 590ed7c
Decompressor#header: respond with correct size hash instead of init-t…
HoneyryderChuck 8a93838
EncodingContext#process: avoid intermediate array generation
HoneyryderChuck 0b9c671
improving sig for header_command to use exact record types for each t…
HoneyryderChuck 41e1056
avoid array accessors by spreading the two elements of the huffman st…
HoneyryderChuck 8a27021
avoid slow string 1 byte drip, instead iterate over bytes, and slice …
HoneyryderChuck dce6852
use Hash#fetch_values to reduce hash lookup usage
HoneyryderChuck 9f36edf
Use #filter_map instead of custom #each_with_object injecting to an a…
HoneyryderChuck 0f57063
initializing ivars from mixins (previously memoized) in the class ini…
HoneyryderChuck 071d7f2
use Comparable#betwen? for integer range
HoneyryderChuck 6a8e330
test with ruby 3.4
HoneyryderChuck e77067e
prefer Integer#<< over Integer#**
HoneyryderChuck 49cca5a
fixup! rewrite of variable usage, avoid multiple Hash#[] calls on flo…
HoneyryderChuck 0e0c892
EncodincContext#dereference: bailout on index value without hash lookup
HoneyryderChuck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
inherit_from: .rubocop_todo.yml | ||
|
||
require: | ||
plugins: | ||
- rubocop-performance | ||
|
||
AllCops: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,3 +33,10 @@ group :types do | |
end | ||
end | ||
end | ||
|
||
group :benchmark do | ||
platform :mri do | ||
gem "memory_profiler" | ||
gem "singed" | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
# frozen_string_literal: true | ||
|
||
require "uri" | ||
require "http/2" | ||
|
||
DEBUG = ENV.key?("DEBUG") | ||
BENCHMARK = ENV.fetch("BENCH", "profile") | ||
ITERATIONS = 5000 | ||
|
||
METHOD = "GET" | ||
BODY = "bang" | ||
URL = URI.parse(ARGV[0] || "http://localhost:8080/") | ||
CLIENT = HTTP2::Client.new | ||
SERVER = HTTP2::Server.new | ||
|
||
CLIENT_BUFFER = "".b | ||
SERVER_BUFFER = "".b | ||
|
||
def log | ||
return unless DEBUG | ||
|
||
puts yield | ||
end | ||
|
||
log { "build client..." } | ||
CLIENT.on(:frame) do |bytes| | ||
log { "(client) sending bytes: #{bytes.size}" } | ||
CLIENT_BUFFER << bytes | ||
end | ||
CLIENT.on(:frame_sent) do |frame| | ||
log { "(client) Sent frame: #{frame.inspect}" } | ||
end | ||
CLIENT.on(:frame_received) do |frame| | ||
log { "(client) Received frame: #{frame.inspect}" } | ||
end | ||
|
||
CLIENT.on(:altsvc) do |f| | ||
log { "(client) received ALTSVC #{f}" } | ||
end | ||
|
||
log { "build server..." } | ||
SERVER.on(:frame) do |bytes| | ||
log { "(server) sending bytes: #{bytes.bytesize}" } | ||
SERVER_BUFFER << bytes | ||
end | ||
SERVER.on(:frame_sent) do |frame| | ||
log { "(server) Sent frame: #{frame.inspect}" } | ||
end | ||
SERVER.on(:frame_received) do |frame| | ||
log { "(server) Received frame: #{frame.inspect}" } | ||
end | ||
|
||
SERVER.on(:goaway) do | ||
log { "(server) goaway received" } | ||
end | ||
|
||
SERVER.on(:stream) do |stream| | ||
req = {} | ||
buffer = "".b | ||
|
||
stream.on(:active) { log { "(server stream:#{stream.id}) client opened new stream" } } | ||
stream.on(:close) { log { "(server stream:#{stream.id}) stream closed" } } | ||
|
||
stream.on(:headers) do |h| | ||
log { "(server stream:#{stream.id}) request headers: #{Hash[*h.flatten]}" } | ||
end | ||
|
||
stream.on(:data) do |d| | ||
log { "(server stream:#{stream.id}) payload chunk: <<#{d}>>" } | ||
buffer << d | ||
end | ||
|
||
stream.on(:half_close) do | ||
log { "(server stream:#{stream.id}) client closed its end of the stream" } | ||
|
||
response = nil | ||
if req[":method"] == "POST" | ||
log { "(server stream:#{stream.id}) Received POST request, payload: #{buffer}" } | ||
response = "(server stream:#{stream.id}) Hello HTTP 2.0! POST payload: #{buffer}" | ||
else | ||
log { "Received GET request" } | ||
response = "(server stream:#{stream.id}) Hello HTTP 2.0! GET request" | ||
end | ||
|
||
stream.headers( | ||
{ | ||
":status" => "200", | ||
"content-length" => response.bytesize.to_s, | ||
"content-type" => "text/plain", | ||
"x-stream-id" => "stream-#{stream.id}" | ||
}, end_stream: false | ||
) | ||
|
||
# split response into multiple DATA frames | ||
stream.data(response[0, 5], end_stream: false) | ||
stream.data(response[5, -1] || "") | ||
end | ||
end | ||
|
||
def send_request | ||
stream = CLIENT.new_stream | ||
|
||
stream.on(:close) do | ||
log { "(client stream:#{stream.id}) stream closed" } | ||
end | ||
|
||
stream.on(:half_close) do | ||
log { "(client stream:#{stream.id}) closing client-end of the stream" } | ||
end | ||
|
||
stream.on(:headers) do |h| | ||
log { "(client stream:#{stream.id}) response headers: #{h}" } | ||
end | ||
|
||
stream.on(:data) do |d| | ||
log { "(client stream:#{stream.id}) response data chunk: <<#{d}>>" } | ||
end | ||
|
||
stream.on(:altsvc) do |f| | ||
log { "(client stream:#{stream.id}) received ALTSVC #{f}" } | ||
end | ||
|
||
head = { | ||
":scheme" => URL.scheme, | ||
":method" => METHOD, | ||
":authority" => [URL.host, URL.port].join(":"), | ||
":path" => URL.path, | ||
"accept" => "*/*" | ||
} | ||
|
||
log { "Sending HTTP 2.0 request" } | ||
|
||
if head[":method"] == "GET" | ||
stream.headers(head, end_stream: true) | ||
else | ||
stream.headers(head, end_stream: false) | ||
stream.data(BODY) | ||
end | ||
|
||
until CLIENT_BUFFER.empty? && SERVER_BUFFER.empty? | ||
unless CLIENT_BUFFER.empty? | ||
SERVER << CLIENT_BUFFER | ||
CLIENT_BUFFER.clear | ||
end | ||
|
||
unless SERVER_BUFFER.empty? | ||
CLIENT << SERVER_BUFFER | ||
SERVER_BUFFER.clear | ||
end | ||
end | ||
end | ||
|
||
def benchmark(bench_type, &block) | ||
return yield if DEBUG | ||
|
||
case bench_type | ||
when "profile" | ||
require "singed" | ||
Singed.output_directory = "tmp/" | ||
|
||
flamegraph(&block) | ||
when "memory" | ||
require "memory_profiler" | ||
MemoryProfiler.report(allow_files: ["lib/http/2"], &block).pretty_print | ||
|
||
when "benchmark" | ||
require "benchmark" | ||
puts Benchmark.measure(&block) | ||
end | ||
end | ||
|
||
GC.start | ||
GC.disable | ||
|
||
puts "warmup..." | ||
ITERATIONS.times do | ||
# start client stream | ||
send_request | ||
end | ||
|
||
puts "bench!" | ||
# Benchmark.bmbm do |x| | ||
benchmark(BENCHMARK) do | ||
ITERATIONS.times do | ||
# start client stream | ||
send_request | ||
end | ||
|
||
CLIENT.goaway | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use standard arrow semantics (-> or <-) for client/server responses if you want.