Last active
August 18, 2018 08:51
-
-
Save elct9620/e2eadcb8cf431f30a1b080bdee4077a1 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# frozen_string_literal: true | |
require 'net/http' | |
require 'fiber' | |
require 'socket' | |
require 'openssl' | |
require 'benchmark/ips' | |
require 'benchmark/memory' | |
require 'memory_profiler' | |
# :nodoc: | |
class Selector | |
def initialize | |
@readable = {} | |
end | |
def wait_readable(io) | |
Fiber.new do | |
@readable[io] = Fiber.current | |
Fiber.yield | |
yield | |
end.resume | |
end | |
def resume | |
readable, = IO.select(@readable.keys) | |
readable.each do |io| | |
fiber = @readable.delete(io) | |
fiber.resume | |
end | |
end | |
def run | |
resume until @readable.empty? | |
end | |
end | |
class Fiber | |
# :nodoc: | |
class HTTP | |
class Connection | |
attr_reader :hostname, :port, :ssl, :ready, :selector | |
def initialize(hostname, port, selector) | |
@selector = selector | |
@hostname = hostname | |
@port = port | |
@io = TCPSocket.new hostname, port | |
@socket = OpenSSL::SSL::SSLSocket.new(@io) | |
@ready = false | |
@response = StringIO.new | |
@buffer = Net::BufferedIO.new(@response) | |
@count = 0 | |
@requests = 0 | |
end | |
def connect | |
return if ready | |
@ready = true | |
@socket.sync_close = true | |
@socket.connect | |
end | |
def start(request) | |
@requests += 1 | |
connect | |
request.update_uri @hostname, @port, true | |
request['Host'] = "#{@hostname}:#{@port}" | |
request.exec @socket, '1.1', request.path | |
end | |
def wait_response(&block) | |
loop do | |
break if @requests == @count | |
@response.write(@socket.read_nonblock(4096)) | |
response(&block) | |
end | |
rescue IO::WaitReadable, EOFError | |
return if @requests == @count | |
@selector.wait_readable(@socket) do | |
wait_response(&block) | |
end | |
end | |
def response(&block) | |
@response.rewind | |
return if @response.eof? | |
response = Net::HTTPResponse.read_new(@buffer) | |
response.decode_content = true | |
response.reading_body(@buffer, true) do | |
yield response if block_given? | |
end | |
@count += 1 | |
end | |
def close | |
return unless ready | |
@socket.close | |
end | |
end | |
include Enumerable | |
def initialize | |
@selector = Selector.new | |
@pool = {} | |
end | |
def request(request) | |
conn = @pool[request.uri.hostname] ||= Connection.new(request.uri.hostname, request.uri.port, @selector) | |
conn.connect | |
conn.start(request) | |
end | |
def connect | |
return if @connected | |
@connected = true | |
@socket.connect | |
@socket.sync_close = true | |
end | |
def start(&block) | |
instance_exec(self, &block) | |
@pool.values.each(&:close) | |
end | |
def each(&block) | |
@pool.values.each do |conn| | |
conn.wait_response(&block) | |
end | |
@selector.run | |
end | |
end | |
end | |
# uri = URI('https://api.binance.com/api/v1/ticker/24hr?symbol=ETHUSDT') | |
# uri = URI('https://api.bitfinex.com/v1/pubticker/BTCUSD') | |
# uri2 = URI('https://api.bitfinex.com/v1/pubticker/ETHUSD') | |
uris = [ | |
'https://api.bitfinex.com/v1/pubticker/BTCUSD', | |
'https://api.bitfinex.com/v1/pubticker/ETHUSD', | |
'https://api.hitbtc.com/api/2/public/ticker/BTCUSD', | |
'https://api.hitbtc.com/api/2/public/ticker/ETHUSD', | |
'https://bittrex.com/api/v1.1/public/getmarketsummary?market=USD-BTC', | |
'https://bittrex.com/api/v1.1/public/getmarketsummary?market=USD-ETH', | |
'https://api.binance.com/api/v1/ticker/24hr?symbol=BTCUSDT', | |
'https://api.binance.com/api/v1/ticker/24hr?symbol=ETHUSDT' | |
] | |
uris5 = [ | |
URI('https://api.bitfinex.com/v1/pubticker/BTCUSD'), | |
URI('https://api.bitfinex.com/v1/pubticker/ETHUSD') | |
] | |
uris2 = [ | |
URI('https://api.hitbtc.com/api/2/public/ticker/BTCUSD'), | |
URI('https://api.hitbtc.com/api/2/public/ticker/ETHUSD') | |
] | |
uris3 = [ | |
URI('https://bittrex.com/api/v1.1/public/getmarketsummary?market=USD-BTC'), | |
URI('https://bittrex.com/api/v1.1/public/getmarketsummary?market=USD-ETH') | |
] | |
uris4 = [ | |
URI('https://api.binance.com/api/v1/ticker/24hr?symbol=BTCUSDT'), | |
URI('https://api.binance.com/api/v1/ticker/24hr?symbol=ETHUSDT') | |
] | |
# Fiber::HTTP.new.start do |http| | |
# uris.each do |uri| | |
# http.request(Net::HTTP::Get.new(URI(uri))) | |
# end | |
# | |
# http.each do |response| | |
# puts response.body | |
# end | |
# end | |
Benchmark.ips do |x| | |
x.report('Fiber') do |c| | |
c.times do | |
Fiber::HTTP.new.start do |http| | |
uris.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))) | |
end | |
http.each do |response| | |
# TODO | |
end | |
end | |
sleep 5 | |
end | |
end | |
x.report('Net::HTTP') do |c| | |
c.times do | |
uris.each do |uri| | |
Net::HTTP.get(URI(uri)) | |
end | |
sleep 5 | |
end | |
end | |
x.report('[K] Net::HTTP') do |c| | |
c.times do | |
Net::HTTP.start(uris5.first.hostname, uris5.first.port, use_ssl: true) do |http| | |
uris5.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris2.first.hostname, uris2.first.port, use_ssl: true) do |http| | |
uris2.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris3.first.hostname, uris3.first.port, use_ssl: true) do |http| | |
uris3.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris4.first.hostname, uris4.first.port, use_ssl: true) do |http| | |
uris4.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
sleep 5 | |
end | |
end | |
x.report('[P] Net::HTTP') do |c| | |
c.times do | |
uris.map do |uri| | |
Thread.new { Net::HTTP.get(URI(uri)) } | |
end.each(&:join) | |
sleep 5 | |
end | |
end | |
x.compare! | |
end | |
Benchmark.memory do |x| | |
x.report('Fiber') do | |
Fiber::HTTP.new.start do |http| | |
uris.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))) | |
end | |
http.each do |response| | |
# TODO | |
end | |
end | |
end | |
sleep 5 | |
x.report('Net::HTTP') do | |
uris.each do |uri| | |
Net::HTTP.get(URI(uri)) | |
end | |
end | |
sleep 5 | |
x.report('[K] Net::HTTP') do |c| | |
Net::HTTP.start(uris5.first.hostname, uris5.first.port, use_ssl: true) do |http| | |
uris5.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris2.first.hostname, uris2.first.port, use_ssl: true) do |http| | |
uris2.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris3.first.hostname, uris3.first.port, use_ssl: true) do |http| | |
uris3.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
Net::HTTP.start(uris4.first.hostname, uris4.first.port, use_ssl: true) do |http| | |
uris4.each do |uri| | |
http.request(Net::HTTP::Get.new(URI(uri))).body | |
end | |
end | |
end | |
sleep 5 | |
x.report('[P] Net::HTTP') do | |
uris.map do |uri| | |
Thread.new { Net::HTTP.get(URI(uri)) } | |
end.each(&:join) | |
end | |
x.compare! | |
end | |
# MemoryProfiler.report do | |
# Fiber::HTTP.new.start do |http| | |
# uris.each do |uri| | |
# http.request(Net::HTTP::Get.new(URI(uri))) | |
# end | |
# | |
# http.each do |response| | |
# puts response.body | |
# end | |
# end | |
# end.pretty_print |
Author
elct9620
commented
Aug 18, 2018
•
[elct9620] fiber-http % ruby fiber-extend-enumerable.rb
Warming up --------------------------------------
Fiber 1.000 i/100ms
Net::HTTP 1.000 i/100ms
[K] Net::HTTP 1.000 i/100ms
[P] Net::HTTP 1.000 i/100ms
Calculating -------------------------------------
Fiber 0.153 (± 0.0%) i/s - 1.000 in 6.553764s
Net::HTTP 0.104 (± 0.0%) i/s - 1.000 in 9.656557s
[K] Net::HTTP 0.109 (± 0.0%) i/s - 1.000 in 9.196089s
[P] Net::HTTP 0.145 (± 0.0%) i/s - 1.000 in 6.877551s
Comparison:
Fiber: 0.2 i/s
[P] Net::HTTP: 0.1 i/s - 1.05x slower
[K] Net::HTTP: 0.1 i/s - 1.40x slower
Net::HTTP: 0.1 i/s - 1.47x slower
Calculating -------------------------------------
Fiber 633.129k memsize ( 8.289k retained)
5.040k objects ( 82.000 retained)
50.000 strings ( 50.000 retained)
Net::HTTP 9.057M memsize ( 11.527k retained)
5.834k objects ( 116.000 retained)
50.000 strings ( 50.000 retained)
[K] Net::HTTP 4.777M memsize ( 11.078k retained)
4.794k objects ( 113.000 retained)
50.000 strings ( 50.000 retained)
[P] Net::HTTP 17.440M memsize ( 11.047k retained)
5.874k objects ( 107.000 retained)
50.000 strings ( 50.000 retained)
Comparison:
Fiber: 633129 allocated
[K] Net::HTTP: 4776761 allocated - 7.54x more
Net::HTTP: 9056946 allocated - 14.31x more
[P] Net::HTTP: 17440446 allocated - 27.55x more
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment