Пакетное преобразование Rails одного объекта в другой объект

#ruby-on-rails #ruby #performance

Вопрос:

Я ищу наиболее эффективный способ (по скорости) преобразования огромного количества объектов (1 млн экземпляров) в другой тип объектов. К сожалению, у меня нет выбора того, что я получаю в качестве входных данных (объект «миллион»).

До сих пор я пробовал, each_slice но это не показывает большого улучшения, когда дело доходит до скорости!

Это выглядит так:

 expected_objects_of_type_2 = []
huge_array.each_slice(3000) do |batch|
  batch.each do |object_type_1|
    expected_objects_of_type_2 << NewType2.new(object_type_1)
  end  
end
 

Есть идеи?

Спасибо!

Комментарии:

1. Каков фактический сценарий для этого? Массовый импорт?

2. Партии обрабатываются последовательно. Вы должны обрабатывать каждый кусочек в потоке.

3. each_slice похоже, это не очень хорошая идея — у вас уже есть весь исходный массив в памяти, поэтому наиболее эффективный способ его перебора-просто повторить 🙂 each_slice создаст много дополнительных массивов, которые затем нужно будет собрать — это добавляет некоторые ненужные накладные расходы.

4. улучшение : Какого рода «улучшение» вы ищете? Скорость?

Ответ №1:

Я провел быстрый тест с несколькими различными методами зацикливания массива и измерил время:

 huge_array = Array.new(10000000){rand(1..1000)}
a = Time.now
string_array = huge_array.map{|x| x.to_s}
b = Time.now
puts b-a
 

То же самое с:

 sa = []
huge_array.each do |x|
    sa << x.to_s
end
 

и

 sa = []
huge_array.each_slice(3000) do |batch|
  batch.each do |x|
    sa << x.to_s
  end  
end 
 

Понятия не имею, что вы конвертируете, поэтому я сделал немного простого int в строку.

Тайминги

 Map: 1.7
Each: 2.3
Slice: 3.2
 

Так что, очевидно, ваши накладные расходы на срез замедляют процесс. Карта кажется самой быстрой (которая внутренне является просто циклом for, но с нединамическим массивом длины в качестве вывода). Это << , кажется, немного замедляет процесс.

Поэтому, если каждый объект нуждается в индивидуальном преобразовании, вы застряли с O(n) сложностью и не можете сильно ускорить процесс. Просто заплатил сверху.

В зависимости от ваших данных, сортировка и использование эффектов кэширования могут помочь или избежать дублирования, если у вас много идентичных данных, но у нас нет возможности узнать, не знаем ли мы ваши фактические конверсии.

Комментарии:

1. спасибо за ответ! На самом деле дубликатов нет, так что я не могу там много сэкономить… Интересно посмотреть, как карта работает здесь более эффективно. Я буду иметь это в виду!

Ответ №2:

Я бы рассматривал каждый срез в отдельной теме:

 huge_array.each_slice(3000) do |batch|
  Thread.new do 
    batch.each do |object_type_1|
      expected_objects_of_type_2 << NewType2.new(object_type_1)
    end  
  end
end
 

Затем вам нужно дождаться завершения использования потоков join . Они должны быть собраны в массив и объединены.

Комментарии:

1. Это может быть даже медленнее, чем первоначальная реализация: из-за GIL в любой конкретный момент будет выполняться только один из созданных потоков накладные расходы на машинное оборудование потоков (включая стоимость переключения контекста).

2. Ах да, я и забыл об этом. Тогда я бы попробовал ракторы Ruby 3.

3. @KonstantinStrukov : Будет ли это применимо даже к системе с несколькими более чем одним ядром?