@@ -169,11 +169,57 @@ def eof?
169169 private
170170
171171 def read_from_socket ( length )
172- data = String . new
172+ # Just in case
173+ if length == 0
174+ return '' . force_encoding ( 'BINARY' )
175+ end
176+
173177 deadline = ( Time . now + timeout ) if timeout
178+
179+ # We want to have a fixed and reasonably small size buffer for reads
180+ # because, for example, OpenSSL reads in 16 kb chunks max.
181+ # Having a 16 mb buffer means there will be 1000 reads each allocating
182+ # 16 mb of memory and using 16 kb of it.
183+ buf_size = read_buffer_size
184+ data = nil
185+
186+ # If we want to read less than the buffer size, just allocate the
187+ # memory that is necessary
188+ if length < buf_size
189+ buf_size = length
190+ end
191+
192+ # The binary encoding is important, otherwise ruby performs encoding
193+ # conversions of some sort during the write into the buffer which
194+ # kills performance
195+ buf = allocate_string ( buf_size )
196+ retrieved = 0
174197 begin
175- while ( data . length < length )
176- data << @socket . read_nonblock ( length - data . length )
198+ while retrieved < length
199+ retrieve = length - retrieved
200+ if retrieve > buf_size
201+ retrieve = buf_size
202+ end
203+ chunk = @socket . read_nonblock ( retrieve , buf )
204+
205+ # If we read the entire wanted length in one operation,
206+ # return the data as is which saves one memory allocation and
207+ # one copy per read
208+ if retrieved == 0 && chunk . length == length
209+ return chunk
210+ end
211+
212+ # If we are here, we are reading the wanted length in
213+ # multiple operations. Allocate the total buffer here rather
214+ # than up front so that the special case above won't be
215+ # allocating twice
216+ if data . nil?
217+ data = allocate_string ( length )
218+ end
219+
220+ # ... and we need to copy the chunks at this point
221+ data [ retrieved , chunk . length ] = chunk
222+ retrieved += chunk . length
177223 end
178224 rescue IO ::WaitReadable
179225 select_timeout = ( deadline - Time . now ) if deadline
@@ -186,6 +232,20 @@ def read_from_socket(length)
186232 data
187233 end
188234
235+ def allocate_string ( capacity )
236+ if RUBY_VERSION >= '2.4.0'
237+ String . new ( '' , :capacity => capacity , :encoding => 'BINARY' )
238+ else
239+ ( 'x' *capacity ) . force_encoding ( 'BINARY' )
240+ end
241+ end
242+
243+ def read_buffer_size
244+ # Buffer size for non-SSL reads
245+ # 64kb
246+ 65536
247+ end
248+
189249 def unix_socket? ( sock )
190250 defined? ( UNIXSocket ) && sock . is_a? ( UNIXSocket )
191251 end
0 commit comments