Ruby concurrency: неблокиращ I/O срещу нишки

Играя си с паралелността в Ruby (1.9.3-p0) и създадох много проста, I/O-тежка прокси задача. Първо опитах неблокиращия подход:

require 'rack'
require 'rack/fiber_pool'
require 'em-http'
require 'em-synchrony'
require 'em-synchrony/em-http'

proxy = lambda {|*|
  result = EM::Synchrony.sync EventMachine::HttpRequest.new('http://google.com').get
  [200, {}, [result.response]]
}

use Rack::FiberPool, :size => 1000
run proxy

=begin
$ thin -p 3000 -e production -R rack-synchrony.ru start
>> Thin web server (v1.3.1 codename Triple Espresso)

$ ab -c100 -n100 http://localhost:3000/
Concurrency Level:      100
Time taken for tests:   5.602 seconds
HTML transferred:       21900 bytes
Requests per second:    17.85 [#/sec] (mean)
Time per request:       5602.174 [ms] (mean)
=end

Хм, помислих си, че трябва да правя нещо нередно. Средно време за заявка от 5,6 s за задача, при която най-вече чакаме I/O? Пробвах друг:

require 'sinatra'
require 'sinatra/synchrony'
require 'em-synchrony/em-http'

get '/' do
  EM::HttpRequest.new("http://google.com").get.response
end

=begin
$ ruby sinatra-synchrony.rb -p 3000 -e production
== Sinatra/1.3.1 has taken the stage on 3000 for production with backup from Thin
>> Thin web server (v1.3.1 codename Triple Espresso)

$ ab -c100 -n100 http://localhost:3000/
Concurrency Level:      100
Time taken for tests:   5.476 seconds
HTML transferred:       21900 bytes
Requests per second:    18.26 [#/sec] (mean)
Time per request:       5475.756 [ms] (mean)
=end

Хм, малко по-добре, но не и това, което бих нарекъл успех. Накрая опитах резбова реализация:

require 'rack'
require 'excon'

proxy = lambda {|*|
  result = Excon.get('http://google.com')
  [200, {}, [result.body]]
}    
run proxy

=begin
$ thin -p 3000 -e production -R rack-threaded.ru --threaded --no-epoll start
>> Thin web server (v1.3.1 codename Triple Espresso)

$ ab -c100 -n100 http://localhost:3000/
Concurrency Level:      100
Time taken for tests:   2.014 seconds
HTML transferred:       21900 bytes
Requests per second:    49.65 [#/sec] (mean)
Time per request:       2014.005 [ms] (mean)
=end

Това беше наистина, наистина изненадващо. Пропускам ли нещо тук? Защо EM се представя толкова зле тук? Има ли някаква настройка, която трябва да направя? Опитах различни комбинации (Unicorn, няколко Rainbows конфигурации и т.н.), но нито една от тях не се доближи дори до простото, старо I/O-блокиране на нишки.

Идеи, коментари и - очевидно - предложения за по-добри реализации са много добре дошли.


person BSM    schedule 12.02.2012    source източник
comment
Не трябва да използвате отдалечени сървъри за тестове, забавянето може да варира. Трябва да опитате отново асинхронния тест с по-малко влакна, с 20 влакна получавам 300ms/заявка срещу 1s/заявка с 1000 влакна, използвайки вашата точна ab линия. Вашият сървър с нишки използва набора от нишки по подразбиране на eventmachine, който по подразбиране е 20 нишки.   -  person Schmurfy    schedule 13.02.2012
comment
Не съм сигурен, задаването на размера на пула от влакна на 20 всъщност намалява производителността на моята кутия.   -  person BSM    schedule 13.02.2012
comment
Може би не 20, но 1000 е наистина много, тествах с локален сървър, така че времето за реакция беше наистина ниско.   -  person Schmurfy    schedule 14.02.2012


Отговори (1)


Вижте как вашето „Време за заявка“ се равнява точно на общото „Време, необходимо за тестове“? Това е аритметичен артефакт за отчитане, тъй като броят на вашите заявки (-n) е равен на вашето ниво на едновременност (-c). Средното време е общото време*едновременност/брой заявки. Така че отчетената средна стойност, когато -n == -c ще бъде времето на най-дългата заявка. Трябва да провеждате корема си с -n > -c по няколко фактора, за да получите разумни мерки.

Изглежда, че използвате стара версия на ab, тъй като сравнително текущата отчита много по-подробни резултати по подразбиране. Работейки директно срещу google, показвам подобно общо време == средно време, когато -n == -c, и получавам по-разумни числа, когато -n > -c. Наистина искате да разгледате req/sec, средната стойност за всички едновременни заявки и крайната разбивка на нивото на услугата, за да получите по-добро разбиране.

$ ab -c50 -n50 http://google.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking google.com (be patient).....done


Server Software:        gws
Server Hostname:        google.com
Server Port:            80

Document Path:          /
Document Length:        219 bytes

Concurrency Level:      50
Time taken for tests:   0.023 seconds           <<== note same as below
Complete requests:      50
Failed requests:        0
Write errors:           0
Non-2xx responses:      50
Total transferred:      27000 bytes
HTML transferred:       10950 bytes
Requests per second:    2220.05 [#/sec] (mean)
Time per request:       22.522 [ms] (mean)      <<== note same as above
Time per request:       0.450 [ms] (mean, across all concurrent requests)
Transfer rate:          1170.73 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    2   0.6      3       3
Processing:     8    9   2.1      9      19
Waiting:        8    9   2.1      9      19
Total:         11   12   2.1     11      22
WARNING: The median and mean for the initial connection time are not within a normal deviation
        These results are probably not that reliable.

Percentage of the requests served within a certain time (ms)
  50%     11
  66%     12
  75%     12
  80%     12
  90%     12
  95%     12
  98%     22
  99%     22
 100%     22 (longest request)        <<== note same as total and mean above


$ ab -c50 -n500 http://google.com/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking google.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Finished 500 requests


Server Software:        gws
Server Hostname:        google.com
Server Port:            80

Document Path:          /
Document Length:        219 bytes

Concurrency Level:      50
Time taken for tests:   0.110 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Non-2xx responses:      500
Total transferred:      270000 bytes
HTML transferred:       109500 bytes
Requests per second:    4554.31 [#/sec] (mean)
Time per request:       10.979 [ms] (mean)
Time per request:       0.220 [ms] (mean, across all concurrent requests)
Transfer rate:          2401.69 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        1    1   0.7      1       3
Processing:     8    9   0.7      9      13
Waiting:        8    9   0.7      9      13
Total:          9   10   1.3     10      16

Percentage of the requests served within a certain time (ms)
  50%     10
  66%     11
  75%     11
  80%     12
  90%     12
  95%     13
  98%     14
  99%     15
 100%     16 (longest request)
person dbenhur    schedule 11.03.2012