Оптимизация Ruby NArray для даунсэмпла и условных изменений
Я создаю информацию для ruby-fann, делаю как можно больше манипуляций в narray по соображениям производительности. Обычно я манипулирую двумерными массивами с плавающей запятой 200x200, и мне нужно повторять обработку много тысяч раз.
Используя только NArray, я получаю приемлемую производительность. Тем не менее, я совершил пару манипуляций, которые я хотел бы сделать, когда я не могу заставить NArray делать что-то массово - насколько я вижу. Это означает, что я в конечном итоге использую элементы управления цикла Ruby для работы с отдельными записями NArray. Это оказывает непосредственное и пагубное влияние на производительность моего кода, и мне было интересно, какие обходные пути или подходы у меня есть. Я могу, но не хочу, раскошелиться на NArray и добавить некоторые функции для моей работы. Это меня не устраивает, потому что нужные мне функции недостаточно универсальны, чтобы войти в эту библиотеку.
Я мог бы рассмотреть возможность написания собственного расширения, которое каким-то образом напрямую использует NArray - указатели на то, как это сделать, приветствуются, я не уверен, как ссылаться на один изначально расширенный гем из другого.
Я также был бы признателен за любую информацию или отзывы о том, как можно по-разному структурировать код, чтобы ускорить либо разработку Ruby, либо воспользоваться любой функцией NArray или связанной библиотекой.
Мои два медленных фрагмента кода очень похожи, поэтому я пишу один вопрос.
1) Предельная матрица чисел с плавающей точкой
Что я сейчас делаю (упрощенно):
# In reality, nn_input contains real-world data, and I want to
# re-normalise it, clipping high values to a maximum of 1.0
nn_input = NArray.float(200,200).random
nn_input *= 1.1
# The test for "anything needs clipping" is fast, the 200x200 loop is somewhat slower!
if (nn_input.gt 1.0).sum > 0
(0...200).each do |x|
(0...200).each do |y|
nn_input[x, y] = 1.0 if nn_input[x, y] > 1.0
end
end
end
2) Уменьшите выборку большой матрицы до более мелкой на основе средних значений (подумайте "изменение размера изображения")
Что я сейчас делаю (упрощенно):
# In reality, nn_input contains real-world data, and I want to
# downsize it, re-sampling a 200x200 array to a 20x20 one
large_input = NArray.float(200,200).random
small_output = NArray.float(20,20)
(0...20).each do |x|
(0...20).each do |y|
small_output[x, y] = large_input[x*10..x*10+9,y*10..y*10+9].mean
end
end
Я использую NArray mean
метод во втором примере, и это менее важно, чем в первом примере, где я заканчиваю выполнение небольшого цикла Ruby 40000 раз для каждого элемента (таким образом, более 200 миллионов раз для всего набора данных!)
После ответа masa16 приведен очень быстрый тест irb, показывающий разницу в скорости:
irb
1.9.3-p327 :001 > require 'narray'
=> true
1.9.3-p327 :002 > t0 = Time.now; 250.times { nn_input = NArray.float(200,200).random() * 1.1; (0...200).each {|x| (0...200).each { |y| nn_input[x,y]=1.0 if nn_input[x,y]> 1.0 }} }; Time.now - t0
=> 9.329647
1.9.3-p327 :003 > t0 = Time.now; 250.times { nn_input = NArray.float(200,200).random() * 1.1; nn_input[nn_input.gt 1.0] = 1.0; }; Time.now - t0
=> 0.764973
Таким образом, для этого небольшого сегмента кода это в 10 раз быстрее, и, поскольку я обычно выполняю не 250 раз, а 50000 раз, я сэкономил где-то от 30 минут до часа времени на то, что раньше занимало 3-4 часа.
2 ответа
1)
nn_input[nn_input.gt 1.0] = 1.0
2)
small_output = large_input.reshape(10,20,10,20).mean(0,2)
Вы тестировали свой код, чтобы определить наличие узких мест?
Можно ли как-нибудь распараллелить вычисления, чтобы использовать многоядерную среду? Если да, задумывались ли вы об использовании JRuby для обеспечения превосходной поддержки многопоточности JVM?
Вот некоторые из вещей, которые я могу придумать с моей головы. Может случиться так, что вы имеете дело с таким большим количеством данных, что вам нужно найти альтернативный способ решения этой проблемы.