From 68412f1d63ac1336210ee357b573f8308e2f5b97 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 25 Oct 2015 11:08:18 +0900 Subject: improve performance --- lib/plum/binary_string.rb | 10 ++--- lib/plum/connection.rb | 11 +++--- lib/plum/flow_control.rb | 4 +- lib/plum/frame.rb | 64 ++++++++++++++++++------------- lib/plum/frame_factory.rb | 10 ++--- lib/plum/hpack/context.rb | 7 ++-- lib/plum/hpack/decoder.rb | 96 ++++++++++++++++++++++++++--------------------- lib/plum/hpack/encoder.rb | 35 ++++++++--------- lib/plum/stream.rb | 15 ++++---- 9 files changed, 134 insertions(+), 118 deletions(-) (limited to 'lib') diff --git a/lib/plum/binary_string.rb b/lib/plum/binary_string.rb index 72b38b1..053be05 100644 --- a/lib/plum/binary_string.rb +++ b/lib/plum/binary_string.rb @@ -4,7 +4,7 @@ module Plum # Reads a 8-bit unsigned integer. # @param pos [Integer] The start position to read. def uint8(pos = 0) - byteslice(pos, 1).unpack("C")[0] + getbyte(pos) end # Reads a 16-bit unsigned integer. @@ -16,7 +16,8 @@ module Plum # Reads a 24-bit unsigned integer. # @param pos [Integer] The start position to read. def uint24(pos = 0) - (uint16(pos) << 8) | uint8(pos + 2) + a, b = byteslice(pos, 3).unpack("nC") + (a * 0x100) | b end # Reads a 32-bit unsigned integer. @@ -27,7 +28,7 @@ module Plum # Appends a 8-bit unsigned integer to this string. def push_uint8(val) - self << [val].pack("C") + self << val.chr end # Appends a 16-bit unsigned integer to this string. @@ -37,8 +38,7 @@ module Plum # Appends a 24-bit unsigned integer to this string. def push_uint24(val) - push_uint16(val >> 8) - push_uint8(val & ((1 << 8) - 1)) + self << [val / 0x100, val % 0x100].pack("nC") end # Appends a 32-bit unsigned integer to this string. diff --git a/lib/plum/connection.rb b/lib/plum/connection.rb index e8dc1e8..56e5607 100644 --- a/lib/plum/connection.rb +++ b/lib/plum/connection.rb @@ -84,6 +84,7 @@ module Plum private def send_immediately(frame) callback(:send_frame, frame) + #frame.assemble @io.write(frame.assemble) end @@ -107,8 +108,8 @@ module Plum end stream = Stream.new(self, stream_id, **args) - callback(:stream, stream) @streams[stream_id] = stream + callback(:stream, stream) stream end @@ -122,14 +123,14 @@ module Plum raise ConnectionError.new(:protocol_error) end - if frame.flags.include?(:end_headers) + if frame.end_headers? @state = :open @continuation_id = nil end end if [:headers].include?(frame.type) - if !frame.flags.include?(:end_headers) + if !frame.end_headers? @state = :waiting_continuation @continuation_id = frame.stream_id end @@ -180,7 +181,7 @@ module Plum end def receive_settings(frame, send_ack: true) - if frame.flags.include?(:ack) + if frame.ack? raise ConnectionError.new(:frame_size_error) if frame.length != 0 callback(:settings_ack) return @@ -210,7 +211,7 @@ module Plum def receive_ping(frame) raise Plum::ConnectionError.new(:frame_size_error) if frame.length != 8 - if frame.flags.include?(:ack) + if frame.ack? callback(:ping_ack) else opaque_data = frame.payload diff --git a/lib/plum/flow_control.rb b/lib/plum/flow_control.rb index f01905a..d1903a7 100644 --- a/lib/plum/flow_control.rb +++ b/lib/plum/flow_control.rb @@ -82,8 +82,8 @@ module Plum end r_wsi = frame.payload.uint32 - r = r_wsi >> 31 - wsi = r_wsi & ~(1 << 31) + # r = r_wsi >> 31 # currently not used + wsi = r_wsi # & ~(1 << 31) if wsi == 0 local_error = (Connection === self) ? ConnectionError : StreamError diff --git a/lib/plum/frame.rb b/lib/plum/frame.rb index 6520247..f54938a 100644 --- a/lib/plum/frame.rb +++ b/lib/plum/frame.rb @@ -17,6 +17,7 @@ module Plum window_update: 0x08, continuation: 0x09 }.freeze + FRAME_TYPES_INVERSE = FRAME_TYPES.invert.freeze FRAME_FLAGS = { data: { @@ -48,6 +49,8 @@ module Plum }.freeze }.freeze + FRAME_FLAGS_MAP = FRAME_FLAGS.values.inject(:merge).freeze + SETTINGS_TYPE = { header_table_size: 0x01, enable_push: 0x02, @@ -78,10 +81,10 @@ module Plum attr_accessor :payload def initialize(type: nil, type_value: nil, flags: nil, flags_value: nil, stream_id: nil, payload: nil) - self.payload = (payload || "") - self.type_value = type_value or self.type = type - self.flags_value = flags_value or self.flags = flags - self.stream_id = stream_id or raise ArgumentError.new("stream_id is necessary") + @payload = payload.to_s + @type_value = type_value or self.type = type + @flags_value = flags_value or self.flags = flags + @stream_id = stream_id or raise ArgumentError.new("stream_id is necessary") end # Returns the length of payload. @@ -93,13 +96,13 @@ module Plum # Returns the type of the frame in Symbol. # @return [Symbol] The type. def type - FRAME_TYPES.key(type_value) || ("unknown_%02x" % type_value).to_sym + FRAME_TYPES_INVERSE[@type_value] || ("unknown_%02x" % @type_value).to_sym end # Sets the frame type. # @param value [Symbol] The type. def type=(value) - self.type_value = FRAME_TYPES[value] or raise ArgumentError.new("unknown frame type: #{value}") + @type_value = FRAME_TYPES[value] or raise ArgumentError.new("unknown frame type: #{value}") end # Returns the set flags on the frame. @@ -107,26 +110,37 @@ module Plum def flags fs = FRAME_FLAGS[type] [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80] - .select {|v| flags_value & v > 0 } + .select {|v| @flags_value & v > 0 } .map {|val| fs && fs.key(val) || ("unknown_%02x" % val).to_sym } end # Sets the frame flags. - # @param value [Array] The flags. - def flags=(value) - self.flags_value = (value && value.map {|flag| FRAME_FLAGS[self.type][flag] }.inject(:|) || 0) + # @param values [Array] The flags. + def flags=(values) + val = 0 + FRAME_FLAGS_MAP.values_at(*values).each { |c| + val |= c if c + } + @flags_value = val end + # Frame#flag_name?() == Frame#flags().include?(:flag_name) + # TODO + FRAME_FLAGS_MAP.each { |name, value| + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def #{name}? + @flags_value & #{value} > 0 + end + EOS + } + # Assembles the frame into binary representation. # @return [String] Binary representation of this frame. def assemble - bytes = "".force_encoding(Encoding::BINARY) - bytes.push_uint24(length) - bytes.push_uint8(type_value) - bytes.push_uint8(flags_value) - bytes.push_uint32(stream_id & ~(1 << 31)) # first bit is reserved (MUST be 0) - bytes.push(payload.b) - bytes + [length / 0x100, length % 0x100, + @type_value, + @flags_value, + @stream_id].pack("nCCCN") << @payload end # @private @@ -136,23 +150,19 @@ module Plum # Parses a frame from given buffer. It changes given buffer. # - # @param buffer [String] The buffer stored the data received from peer. + # @param buffer [String] The buffer stored the data received from peer. Encoding must be Encoding::BINARY. # @return [Frame, nil] The parsed frame or nil if the buffer is imcomplete. def self.parse!(buffer) - buffer.force_encoding(Encoding::BINARY) - - return nil if buffer.size < 9 # header: 9 bytes + return nil if buffer.bytesize < 9 # header: 9 bytes length = buffer.uint24 - return nil if buffer.size < 9 + length + return nil if buffer.bytesize < 9 + length bhead = buffer.byteshift(9) payload = buffer.byteshift(length) - type_value = bhead.uint8(3) - flags_value = bhead.uint8(4) - r_sid = bhead.uint32(5) - r = r_sid >> 31 - stream_id = r_sid & ~(1 << 31) + type_value, flags_value, r_sid = bhead.byteslice(3, 6).unpack("CCN") + # r = r_sid >> 31 # currently not used + stream_id = r_sid # & ~(1 << 31) self.new(type_value: type_value, flags_value: flags_value, diff --git a/lib/plum/frame_factory.rb b/lib/plum/frame_factory.rb index b88cfab..3a9fa97 100644 --- a/lib/plum/frame_factory.rb +++ b/lib/plum/frame_factory.rb @@ -20,7 +20,7 @@ module Plum payload.push_uint16(id) payload.push_uint32(value) } - Frame.new(type: :settings, stream_id: 0, flags: [ack].compact, payload: payload) + Frame.new(type: :settings, stream_id: 0, flags: [ack], payload: payload) end def ping(arg1 = "plum\x00\x00\x00\x00", arg2 = nil) @@ -33,21 +33,21 @@ module Plum end def data(stream_id, payload, *flags) - Frame.new(type: :data, stream_id: stream_id, flags: flags.compact, payload: payload.to_s) + Frame.new(type: :data, stream_id: stream_id, flags: flags, payload: payload) end def headers(stream_id, encoded, *flags) - Frame.new(type: :headers, stream_id: stream_id, flags: flags.compact, payload: encoded) + Frame.new(type: :headers, stream_id: stream_id, flags: flags, payload: encoded) end def push_promise(stream_id, new_id, encoded, *flags) payload = "".push_uint32(0 << 31 | new_id) .push(encoded) - Frame.new(type: :push_promise, stream_id: stream_id, flags: flags.compact, payload: payload) + Frame.new(type: :push_promise, stream_id: stream_id, flags: flags, payload: payload) end def continuation(stream_id, payload, *flags) - Frame.new(type: :continuation, stream_id: stream_id, flags: flags.compact, payload: payload) + Frame.new(type: :continuation, stream_id: stream_id, flags: flags, payload: payload) end end end diff --git a/lib/plum/hpack/context.rb b/lib/plum/hpack/context.rb index 71f07cb..d15249a 100644 --- a/lib/plum/hpack/context.rb +++ b/lib/plum/hpack/context.rb @@ -16,8 +16,9 @@ module Plum end def store(name, value) + value = value.to_s @dynamic_table.unshift([name, value]) - @size += name.bytesize + value.to_s.bytesize + 32 + @size += name.bytesize + value.bytesize + 32 evict end @@ -34,7 +35,7 @@ module Plum end def search(name, value) - pr = proc {|n, v| + pr = proc { |n, v| n == name && (!value || v == value) } @@ -47,7 +48,7 @@ module Plum def evict while @limit && @size > @limit name, value = @dynamic_table.pop - @size -= name.bytesize + value.to_s.bytesize + 32 + @size -= name.bytesize + value.bytesize + 32 end end end diff --git a/lib/plum/hpack/decoder.rb b/lib/plum/hpack/decoder.rb index 13833c1..b536123 100644 --- a/lib/plum/hpack/decoder.rb +++ b/lib/plum/hpack/decoder.rb @@ -10,42 +10,47 @@ module Plum end def decode(str) - str = str.dup headers = [] - headers << parse!(str) while str.size > 0 - headers.compact + pos = 0 + lpos = str.bytesize + while pos < lpos + l, succ = parse(str, pos) + pos += succ + headers << l if l + end + headers end private - def parse!(str) - first_byte = str.uint8 - if first_byte >= 128 # 0b1XXXXXXX - parse_indexed!(str) - elsif first_byte >= 64 # 0b01XXXXXX - parse_indexing!(str) - elsif first_byte >= 32 # 0b001XXXXX - self.limit = read_integer!(str, 5) - nil + def parse(str, pos) + first_byte = str.uint8(pos) + if first_byte >= 0x80 # 0b1XXXXXXX + parse_indexed(str, pos) + elsif first_byte >= 0x40 # 0b01XXXXXX + parse_indexing(str, pos) + elsif first_byte >= 0x20 # 0b001XXXXX + self.limit, succ = read_integer(str, pos, 5) + [nil, succ] else # 0b0000XXXX (without indexing) or 0b0001XXXX (never indexing) - parse_no_indexing!(str) + parse_no_indexing(str, pos) end end - def read_integer!(str, prefix_length) - first_byte = str.byteshift(1).uint8 - raise HPACKError.new("integer: end of buffer") unless first_byte + def read_integer(str, pos, prefix_length) + raise HPACKError.new("integer: end of buffer") if str.empty? + first_byte = str.uint8(pos) mask = (1 << prefix_length) - 1 ret = first_byte & mask - return ret if ret < mask + return [ret, 1] if ret != mask octets = 0 - while next_value = str.byteshift(1).uint8 - ret += (next_value & 0b01111111) << (7 * octets) + while next_value = str.uint8(pos + octets + 1) + ret += (next_value % 0x80) << (7 * octets) octets += 1 - if next_value < 128 - return ret + if next_value < 0x80 + return [ret, 1 + octets] elsif octets == 4 # RFC 7541 5.1 tells us that we MUST have limitation. at least > 2 ** 28 raise HPACKError.new("integer: too large integer") end @@ -54,29 +59,32 @@ module Plum raise HPACKError.new("integer: end of buffer") end - def read_string!(str) - first_byte = str.uint8 - raise HPACKError.new("string: end of buffer") unless first_byte + def read_string(str, pos) + raise HPACKError.new("string: end of buffer") if str.empty? + first_byte = str.uint8(pos) - huffman = (first_byte >> 7) == 1 - length = read_integer!(str, 7) - bin = str.byteshift(length) + huffman = first_byte > 0x80 + length, ilen = read_integer(str, pos, 7) + raise HTTPError.new("string: end of buffer") if str.bytesize < length - raise HTTPError.new("string: end of buffer") if bin.bytesize < length - bin = Huffman.decode(bin) if huffman - bin + bin = str.byteslice(pos + ilen, length) + if huffman + [Huffman.decode(bin), ilen + length] + else + [bin, ilen + length] + end end - def parse_indexed!(str) + def parse_indexed(str, pos) # indexed # +---+---+---+---+---+---+---+---+ # | 1 | Index (7+) | # +---+---------------------------+ - index = read_integer!(str, 7) - fetch(index) + index, succ = read_integer(str, pos, 7) + [fetch(index), succ] end - def parse_indexing!(str) + def parse_indexing(str, pos) # +---+---+---+---+---+---+---+---+ # | 0 | 1 | Index (6+) | # +---+---+-----------------------+ @@ -96,20 +104,21 @@ module Plum # +---+---------------------------+ # | Value String (Length octets) | # +-------------------------------+ - index = read_integer!(str, 6) + index, ilen = read_integer(str, pos, 6) if index == 0 - name = read_string!(str) + name, nlen = read_string(str, pos + ilen) else name, = fetch(index) + nlen = 0 end - val = read_string!(str) + val, vlen = read_string(str, pos + ilen + nlen) store(name, val) - [name, val] + [[name, val], ilen + nlen + vlen] end - def parse_no_indexing!(str) + def parse_no_indexing(str, pos) # +---+---+---+---+---+---+---+---+ # | 0 | 0 | 0 |0,1| Index (4+) | # +---+---+-----------------------+ @@ -129,16 +138,17 @@ module Plum # +---+---------------------------+ # | Value String (Length octets) | # +-------------------------------+ - index = read_integer!(str, 4) + index, ilen = read_integer(str, pos, 4) if index == 0 - name = read_string!(str) + name, nlen = read_string(str, pos + ilen) else name, = fetch(index) + nlen = 0 end - val = read_string!(str) + val, vlen = read_string(str, pos + ilen + nlen) - [name, val] + [[name, val], ilen + nlen + vlen] end end end diff --git a/lib/plum/hpack/encoder.rb b/lib/plum/hpack/encoder.rb index 2dc2482..d78f96e 100644 --- a/lib/plum/hpack/encoder.rb +++ b/lib/plum/hpack/encoder.rb @@ -14,8 +14,8 @@ module Plum def encode(headers) out = "" headers.each do |name, value| - name = name.to_s.b - value = value.to_s.b + name = name.to_s + value = value.to_s if index = search(name, value) out << encode_indexed(index) elsif index = search(name, nil) @@ -59,10 +59,9 @@ module Plum def encode_half_indexed(index, value) if @indexing store(fetch(index)[0], value) - fb = encode_integer(index, 6) - fb.setbyte(0, fb.uint8 | 0b01000000) + fb = encode_integer(index, 6, 0b01000000) else - fb = encode_integer(index, 4) + fb = encode_integer(index, 4, 0b00000000) end fb << encode_string(value) end @@ -71,27 +70,24 @@ module Plum # | 1 | Index (7+) | # +---+---------------------------+ def encode_indexed(index) - s = encode_integer(index, 7) - s.setbyte(0, s.uint8 | 0b10000000) - s + encode_integer(index, 7, 0b10000000) end - def encode_integer(value, prefix_length) + def encode_integer(value, prefix_length, hmask) mask = (1 << prefix_length) - 1 - out = "" if value < mask - out.push_uint8(value) + (value + hmask).chr.force_encoding(Encoding::BINARY) else + vals = [mask + hmask] value -= mask - out.push_uint8(mask) while value >= mask - out.push_uint8((value % 0b10000000) + 0b10000000) - value >>= 7 + vals << (value % 0x80) + 0x80 + value /= 0x80 end - out.push_uint8(value) + vals << value + vals.pack("C*") end - out.force_encoding(Encoding::BINARY) end def encode_string(str) @@ -105,14 +101,13 @@ module Plum end def encode_string_plain(str) - encode_integer(str.bytesize, 7) << str.force_encoding(Encoding::BINARY) + encode_integer(str.bytesize, 7, 0b00000000) << str.force_encoding(Encoding::BINARY) end def encode_string_huffman(str) huffman_str = Huffman.encode(str) - lenstr = encode_integer(huffman_str.bytesize, 7) - lenstr.setbyte(0, lenstr.uint8(0) | 0b10000000) - lenstr.force_encoding(Encoding::BINARY) << huffman_str + lenstr = encode_integer(huffman_str.bytesize, 7, 0b10000000) + lenstr << huffman_str end end end diff --git a/lib/plum/stream.rb b/lib/plum/stream.rb index eee7cad..007d352 100644 --- a/lib/plum/stream.rb +++ b/lib/plum/stream.rb @@ -105,7 +105,7 @@ module Plum raise StreamError.new(:stream_closed) end - if frame.flags.include?(:padded) + if frame.padded? padding_length = frame.payload.uint8(0) if padding_length >= frame.length raise ConnectionError.new(:protocol_error, "padding is too long") @@ -116,18 +116,17 @@ module Plum end callback(:data, body) - receive_end_stream if frame.flags.include?(:end_stream) + receive_end_stream if frame.end_stream? end def receive_complete_headers(frames) - frames = frames.dup first = frames.shift payload = first.payload first_length = first.length padding_length = 0 - if first.flags.include?(:padded) + if first.padded? padding_length = payload.uint8 first_length -= 1 + padding_length payload = payload.byteslice(1, first_length) @@ -135,7 +134,7 @@ module Plum payload = payload.dup end - if first.flags.include?(:priority) + if first.priority? receive_priority_payload(payload.byteshift(5)) first_length -= 5 end @@ -156,7 +155,7 @@ module Plum callback(:headers, decoded_headers) - receive_end_stream if first.flags.include?(:end_stream) + receive_end_stream if first.end_stream? end def receive_headers(frame) @@ -171,7 +170,7 @@ module Plum @state = :open callback(:open) - if frame.flags.include?(:end_headers) + if frame.end_headers? receive_complete_headers([frame]) else @continuation << frame @@ -182,7 +181,7 @@ module Plum # state error mustn't happen: server_connection validates @continuation << frame - if frame.flags.include?(:end_headers) + if frame.end_headers? receive_complete_headers(@continuation) @continuation.clear end -- cgit v1.2.3