Ответ 1
Я приглашаю вас прочитать серию Jesse Storimer Никто не понимает GIL Это может помочь вам лучше понять некоторые внутренние МРТ.
Я также нашел Pragmatic Concurrency с Ruby, который читает интересно. Он имеет несколько примеров тестирования одновременно.
EDIT: Кроме того, я могу порекомендовать статью Удаление config.threadsafe! Возможно, это не относится к Rails 4, но в нем объясняются параметры конфигурации, один из которых можно использовать для разрешения concurrency.
Давайте обсудим ответ на ваш вопрос.
У вас может быть несколько потоков (с использованием MRI), даже с Puma. GIL гарантирует, что за один раз активен только один поток, то есть ограничение, которое разработчики дублируют как ограничивающие (из-за отсутствия реального параллельного выполнения). Имейте в виду, что GIL не гарантирует безопасность резьбы. Это не означает, что другие потоки не работают, они ждут своей очереди. Они могут чередовать (статьи могут помочь лучше понять).
Позвольте мне прояснить некоторые условия: рабочий процесс, поток. Процесс выполняется в отдельном пространстве памяти и может обслуживать несколько потоков. Потоки одного и того же процесса выполняются в пространстве общей памяти, что и в их процессе. В потоках мы подразумеваем потоки Ruby в этом контексте, а не потоки ЦП.
Что касается конфигурации вашего вопроса и репо GitHub, которую вы поделили, я думаю, что подходящая конфигурация (я использовал Puma) - это настроить 4 рабочих и от 1 до 40 потоков. Идея состоит в том, что один рабочий обслуживает одну вкладку. Каждая вкладка отправляет до 10 запросов.
Итак, давайте начнем:
Я работаю над Ubuntu на виртуальной машине. Поэтому сначала я включил 4 ядра в настройках виртуальной машины (и некоторые другие настройки, о которых я думал, что это может помочь). Я мог проверить это на своей машине. Поэтому я пошел с этим.
Linux command --> lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 1
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 69
Stepping: 1
CPU MHz: 2306.141
BogoMIPS: 4612.28
L1d cache: 32K
L1d cache: 32K
L2d cache: 6144K
NUMA node0 CPU(s): 0-3
Я использовал ваш общий проект GitHub и немного изменил его. Я создал файл конфигурации Puma с именем puma.rb
(поместите его в каталог config
) со следующим содержимым:
workers Integer(ENV['WEB_CONCURRENCY'] || 1)
threads_count = Integer(ENV['MAX_THREADS'] || 1)
threads 1, threads_count
preload_app!
rackup DefaultRackup
port ENV['PORT'] || 3000
environment ENV['RACK_ENV'] || 'development'
on_worker_boot do
# Worker specific setup for Rails 4.1+
# See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
#ActiveRecord::Base.establish_connection
end
По умолчанию Puma запускается с 1 рабочим и 1 потоком. Вы можете использовать переменные среды для изменения этих параметров. Я сделал это:
export MAX_THREADS=40
export WEB_CONCURRENCY=4
Чтобы запустить Puma с этой конфигурацией, я набрал
bundle exec puma -C config/puma.rb
в каталоге приложений Rails.
Я открыл браузер с четырьмя вкладками, чтобы вызвать URL приложения.
Первый запрос начался около 15:45:05, и последний запрос был около 15: 49: 44. Это истекшее время 4 минуты и 39 секунд. Также вы можете увидеть идентификатор запроса в неупорядоченном порядке в файле журнала. (См. Ниже)
Каждый вызов API в проекте GitHub спит в течение 15 секунд. У нас есть четыре 4 вкладки, каждая из которых имеет 10 вызовов API. Это составляет максимальное время, равное 600 секундам, т.е. 10 минут (в строго последовательном режиме).
Идеальный результат в теории был бы параллельным, и прошедшее время не было бы за пределами 15 секунд, но я этого не ожидал. Я не был уверен, чего ожидать в результате точно, но я все еще был удивлен (учитывая, что я побежал на виртуальной машине, а МРТ сдерживается GIL и некоторыми другими факторами). Истекшее время этого теста было меньше половины максимального прошедшего времени (в строго последовательном режиме), мы сокращаем результат менее чем наполовину.
EDIT. Я читал далее о Rack:: Lock, который обертывает мьютекс вокруг каждого запроса (третья статья выше). Я нашел вариант
config.allow_concurrency = true
для сохранения времени. Небольшое предостережение было увеличение пула соединений (хотя запрос не делает запрос база данных должна была быть установлена соответственно); число максимальных потоков хороший дефолт. 40 в этом случае.Я тестировал приложение с помощью jRuby, и фактическое прошедшее время составляло 2 минуты, с allow_concurrency = true.
Я тестировал приложение с помощью МРТ, и фактическое прошедшее время составляло 1 мин47, с allow_concurrency = true. Это было большим сюрпризом для меня. Это меня действительно удивило, потому что я ожидал, что МРТ будет медленнее, чем JRuby. Не было. Это заставляет меня расспрашивать о широко распространенной дискуссии о различиях в скорости между МРТ и JRuby.
Наблюдение ответов на разных вкладках теперь "более случайное". Бывает, что вкладка 3 или 4 завершается перед первой закладкой, которую я запросил.
Я думаю, потому что у вас нет условий гонки, тест, похоже, ОК. Однако я не уверен в широких последствиях приложения, если вы устанавливаете config.allow_concurrency = true в приложении реального мира.
Не стесняйтесь проверить это и сообщить мне любую обратную связь, которую вы, возможно, читатели. У меня все еще есть клон на моей машине. Сообщите мне, если вы заинтересованы.
Чтобы ответить на ваши вопросы в порядке:
- Я думаю, что ваш пример действителен по результату. Однако для Concurrency лучше протестировать с использованием общих ресурсов (как, например, во второй статье).
- Что касается ваших заявлений, как упоминалось в начале этого ответ, MRI является многопоточным, но ограниченным GIL одним активным поток за раз. Это ставит вопрос: с МРТ не лучше тестировать с большим количеством процессов и меньше потоков? Я действительно не знаю, первая догадка была бы скорее неважной или не очень важной. Может быть, кто-то может пролить свет на это.
- Твой пример просто прекрасен, я думаю. Просто нужно было немного модификаций.
Приложение
Файл журнала Rails:
**config.allow_concurrency = false (by default)**
-> Ideally 1 worker per core, each worker servers up to 10 threads.
[3045] Puma starting in cluster mode...
[3045] * Version 2.11.2 (ruby 2.1.5-p273), codename: Intrepid Squirrel
[3045] * Min threads: 1, max threads: 40
[3045] * Environment: development
[3045] * Process workers: 4
[3045] * Preloading application
[3045] * Listening on tcp://0.0.0.0:3000
[3045] Use Ctrl-C to stop
[3045] - Worker 0 (pid: 3075) booted, phase: 0
[3045] - Worker 1 (pid: 3080) booted, phase: 0
[3045] - Worker 2 (pid: 3087) booted, phase: 0
[3045] - Worker 3 (pid: 3098) booted, phase: 0
Started GET "/assets/angular-ui-router/release/angular-ui-router.js?body=1" for 127.0.0.1 at 2015-05-11 15:45:05 +0800
...
...
...
Processing by ApplicationController#api_call as JSON
Parameters: {"t"=>"15?id=9"}
Completed 200 OK in 15002ms (Views: 0.2ms | ActiveRecord: 0.0ms)
[3075] 127.0.0.1 - - [11/May/2015:15:49:44 +0800] "GET /api_call.json?t=15?id=9 HTTP/1.1" 304 - 60.0230
**config.allow_concurrency = true**
-> Ideally 1 worker per core, each worker servers up to 10 threads.
[22802] Puma starting in cluster mode...
[22802] * Version 2.11.2 (ruby 2.2.0-p0), codename: Intrepid Squirrel
[22802] * Min threads: 1, max threads: 40
[22802] * Environment: development
[22802] * Process workers: 4
[22802] * Preloading application
[22802] * Listening on tcp://0.0.0.0:3000
[22802] Use Ctrl-C to stop
[22802] - Worker 0 (pid: 22832) booted, phase: 0
[22802] - Worker 1 (pid: 22835) booted, phase: 0
[22802] - Worker 3 (pid: 22852) booted, phase: 0
[22802] - Worker 2 (pid: 22843) booted, phase: 0
Started GET "/" for 127.0.0.1 at 2015-05-13 17:58:20 +0800
Processing by ApplicationController#index as HTML
Rendered application/index.html.erb within layouts/application (3.6ms)
Completed 200 OK in 216ms (Views: 200.0ms | ActiveRecord: 0.0ms)
[22832] 127.0.0.1 - - [13/May/2015:17:58:20 +0800] "GET / HTTP/1.1" 200 - 0.8190
...
...
...
Completed 200 OK in 15003ms (Views: 0.1ms | ActiveRecord: 0.0ms)
[22852] 127.0.0.1 - - [13/May/2015:18:00:07 +0800] "GET /api_call.json?t=15?id=10 HTTP/1.1" 304 - 15.0103
**config.allow_concurrency = true (by default)**
-> Ideally each thread serves a request.
Puma starting in single mode...
* Version 2.11.2 (jruby 2.2.2), codename: Intrepid Squirrel
* Min threads: 1, max threads: 40
* Environment: development
NOTE: ActiveRecord 4.2 is not (yet) fully supported by AR-JDBC, please help us finish 4.2 support - check http://bit.ly/jruby-42 for starters
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
Started GET "/" for 127.0.0.1 at 2015-05-13 18:23:04 +0800
Processing by ApplicationController#index as HTML
Rendered application/index.html.erb within layouts/application (35.0ms)
...
...
...
Completed 200 OK in 15020ms (Views: 0.7ms | ActiveRecord: 0.0ms)
127.0.0.1 - - [13/May/2015:18:25:19 +0800] "GET /api_call.json?t=15?id=9 HTTP/1.1" 304 - 15.0640