Skip to content

Instantly share code, notes, and snippets.

@elct9620
Last active August 18, 2018 08:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elct9620/e2eadcb8cf431f30a1b080bdee4077a1 to your computer and use it in GitHub Desktop.
Save elct9620/e2eadcb8cf431f30a1b080bdee4077a1 to your computer and use it in GitHub Desktop.
# 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
@elct9620
Copy link
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.152  (± 0.0%) i/s -      1.000  in   6.569510s
           Net::HTTP      0.098  (± 0.0%) i/s -      1.000  in  10.153025s
       [K] Net::HTTP      0.090  (± 0.0%) i/s -      1.000  in  11.058569s
       [P] Net::HTTP      0.156  (± 0.0%) i/s -      1.000  in   6.394142s

Comparison:
       [P] Net::HTTP:        0.2 i/s
               Fiber:        0.2 i/s - 1.03x  slower
           Net::HTTP:        0.1 i/s - 1.59x  slower
       [K] Net::HTTP:        0.1 i/s - 1.73x  slower

Calculating -------------------------------------
               Fiber   646.070k memsize (     8.155k retained)
                         5.058k objects (    80.000  retained)
                        50.000  strings (    50.000  retained)
           Net::HTTP     9.139M memsize (    12.324k retained)
                         5.841k objects (   122.000  retained)
                        50.000  strings (    50.000  retained)
       [K] Net::HTTP     4.776M memsize (    11.176k retained)
                         4.791k objects (   113.000  retained)
                        50.000  strings (    50.000  retained)
       [P] Net::HTTP    17.507M memsize (    11.276k retained)
                         5.881k objects (   108.000  retained)
                        50.000  strings (    50.000  retained)

Comparison:
               Fiber:     646070 allocated
       [K] Net::HTTP:    4776019 allocated - 7.39x more
           Net::HTTP:    9139121 allocated - 14.15x more
       [P] Net::HTTP:   17507373 allocated - 27.10x more
[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.133  (± 0.0%) i/s -      1.000  in   7.499627s
           Net::HTTP      0.113  (± 0.0%) i/s -      1.000  in   8.886591s
       [K] Net::HTTP      0.116  (± 0.0%) i/s -      1.000  in   8.608502s
       [P] Net::HTTP      0.158  (± 0.0%) i/s -      1.000  in   6.326223s

Comparison:
       [P] Net::HTTP:        0.2 i/s
               Fiber:        0.1 i/s - 1.19x  slower
       [K] Net::HTTP:        0.1 i/s - 1.36x  slower
           Net::HTTP:        0.1 i/s - 1.40x  slower

Calculating -------------------------------------
               Fiber   642.465k memsize (     8.129k retained)
                         5.040k objects (    82.000  retained)
                        50.000  strings (    50.000  retained)
           Net::HTTP     9.091M memsize (    11.924k retained)
                         5.836k objects (   115.000  retained)
                        50.000  strings (    50.000  retained)
       [K] Net::HTTP     4.776M memsize (    10.905k retained)
                         4.790k objects (   109.000  retained)
                        50.000  strings (    50.000  retained)
       [P] Net::HTTP    17.509M memsize (    11.933k retained)
                         5.907k objects (   111.000  retained)
                        50.000  strings (    50.000  retained)

Comparison:
               Fiber:     642465 allocated
       [K] Net::HTTP:    4776020 allocated - 7.43x more
           Net::HTTP:    9091160 allocated - 14.15x more
       [P] Net::HTTP:   17508674 allocated - 27.25x more
[elct9620] fiber-http %

@elct9620
Copy link
Author

[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