МРТ рубиновое нарезание резьбы и производительность

Мой первый вопрос по SO, но я долго скрывался, так что вам придется простить меня, если я нарушил какие-либо правила или отправил мусорный вопрос.

Я пытаюсь лучше понять многопоточность, и я решил проверить МРТ и посмотреть, как она работает в целом.

Учитывая следующий код (и вывод), почему многопоточные операции намного медленнее, чем непотоковый вариант?

код

class Benchmarker
  def self.go
    puts '----------Benchmark Start----------'
    start_t = Time.now
    yield
    end_t = Time.now
    puts "Operation Took: #{end_t - start_t} seconds"
    puts '----------Benchmark End------------'
  end
end

# using mutex
puts 'Benchmark 1 (threaded, mutex):'
Benchmarker.go do
  array = []
  mutex = Mutex.new
  5000.times.map do
    Thread.new do
      mutex.synchronize do
        1000.times do
          array << nil
        end
      end
    end
  end.each(&:join)
  puts array.size
end

# using threads
puts 'Benchmark 2 (threaded, no mutex):'
Benchmarker.go do
  array = []
  5000.times.map do
    Thread.new do
      1000.times do
        array << nil
      end
    end
  end.each(&:join)
  puts array.size
end

# no threads
puts 'Benchmark 3 (no threads):'
Benchmarker.go do
  array = []
  5000.times.map do
    1000.times do
      array << nil
    end
  end
  puts array.size
end

выход

Benchmark 1 (threaded, mutex):
----------Benchmark Start----------
5000000
Operation Took: 3.373886 seconds
----------Benchmark End------------
Benchmark 2 (threaded, no mutex):
----------Benchmark Start----------
5000000
Operation Took: 5.040501 seconds
----------Benchmark End------------
Benchmark 3 (no threads):
----------Benchmark Start----------
5000000
Operation Took: 0.454665 seconds
----------Benchmark End------------

Заранее спасибо.

1 ответ

Решение

Как только вы достигнете большого количества потоков (5000), объем накладных расходов на переключение между потоками планировщиком намного превышает объем работы, которую фактически выполняет каждый поток. Обычно вы хотите 30-50 нитей макс.

Попробуйте уменьшить количество потоков и пропорционально увеличить объем работы, которую выполняет каждый:

  20.times.map do
    Thread.new do
      250000.times do
        array << nil
      end
    end
  end.each(&:join)

и вы должны увидеть гораздо более сопоставимые результаты.

Обратите внимание, что вы, вероятно, увидите нижнюю границу Time(threaded) >= Time(non-threaded) - то есть время для многопоточной версии не может быть меньше однопоточной версии. Это происходит из-за GIL MRI, который позволяет одновременно выполнять только один поток (они никогда не могут работать параллельно). Некоторые реализации ruby, такие как JRuby, допускают параллельное выполнение потоков.

Другие вопросы по тегам