Целлулоидные фьючерсы не быстрее, чем синхронные вычисления?
Я пытаюсь использовать Celluloid для асинхронной обработки некоторых данных.csv. Я читал, что использование фьючерсов позволяет дождаться завершения пула актеров, прежде чем завершится основной поток. Я посмотрел на несколько примеров, которые демонстрируют это.
Однако, когда я реализую это в своем примере кода, оказывается, что использование фьючерсов не намного быстрее, чем синхронная обработка. Кто-нибудь может увидеть, что я делаю не так?
require 'smarter_csv'
require 'celluloid/current'
require 'benchmark'
class ImportActor
include Celluloid
def process_row(row)
100000.times {|n| n}
end
end
def do_all_the_things_with_futures
pool = ImportActor.pool(size: 10)
SmarterCSV.process("all_the_things.csv").map do |row|
pool.future(:process_row,row)
end.map(&:value)
end
def do_all_the_things_insync
pool = ImportActor.pool(size: 10)
SmarterCSV.process("all_the_things.csv") do |row|
pool.process_row(row)
end
end
puts Benchmark.measure { do_all_the_things_with_futures}
puts Benchmark.measure { do_all_the_things_insync }
2,100000 0,030000 2,130000 (2.123381)
2.060000 0.020000 2.080000 (2.069357)
[Закончено в 4.6s]
1 ответ
Используете ли вы стандартный переводчик рубинового МРТ?
Если это так, вы не получите никакого ускорения для задач, полностью связанных с процессором, то есть задач, которые не выполняют никаких операций ввода-вывода, но полностью выполняют вычисления в процессоре. Ваша тестовая задача 100000.times {|n| n}
действительно полностью зависит от процессора.
Причина, по которой вы не получите никакого ускорения благодаря многопоточности для задач, полностью связанных с ЦП, в MRI, заключается в том, что в интерпретаторе MRI имеется "глобальная блокировка интерпретатора" (GIL), которая предотвращает более одного ядра вашего ЦП от используется сразу же переводчиком ruby. Многопоточный параллелизм, как дает вам целлулоид, может ускорить работу ЦП, если одновременно запускать разные потоки на разных ядрах ЦП, в многоядерной системе, как это делают большинство систем в наши дни.
Но в МРТ это невозможно. Это ограничение рубинового МРТ-переводчика.
Если вы установите JRuby и запустите свой тест под JRuby, вы должны увидеть ускорение.
Если ваша задача включала некоторый ввод-вывод (например, выполнение запроса к базе данных, ожидание удаленного HTTP-API или выполнение значительного объема чтения или записи файла), вы также можете увидеть некоторое ускорение при использовании MRI. Чем более пропорциональное время тратит ваша задача на выполнение операций ввода-вывода, тем больше ускорение. Это связано с тем, что, хотя MRI не позволяет одновременно выполнять потоки на более чем одном ядре ЦП, поток, ожидающий ввода-вывода, все равно можно отключить, а другой поток - для работы. Принимая во внимание, что если бы вы не использовали потоки, программа просто сидела бы без дела в ожидании ввода-вывода, не делая никакой работы.
Если вы в поиске "ruby GIL", вы можете найти больше обсуждений этой проблемы.
Если вы действительно выполняете нагрузку на процессор, которая может извлечь выгоду из многопоточного параллелизма таким образом, который значительно поможет вашей программе, подумайте о переходе на Jruby.
И если вам действительно нужен многопоточный параллелизм, альтернативой использованию Celluloid является использование Futures или Promises из пакета concurrent-ruby. Concurrent-ruby, как правило, проще и легче, чем Celluloid. Тем не менее, написание многопоточного кода может быть сложным независимо от того, какой инструмент вы используете, и даже если вы используете Celluloid или ruby-concurrent, чтобы предоставить вам более качественные абстракции более высокого уровня, чем работа непосредственно с потоками, работа с многопоточным параллелизмом потребует знакомы с некоторыми методами для таких и требуют время от времени некоторых хитрой отладки.