Производительность: итерация по массиву за исключением элемента - PullRequest
3 голосов
/ 22 июня 2011

Я использую Ruby on Rails 3.0.7, и я хотел бы перебирать массив объектов (классов), за исключением элемента с id, равным 1 (id относится к массиву [ 1] индекс).

Я знаю, что могу использовать оператор if "внутренне" для оператора each, и при этом проверяю каждый "текущий" \ "рассматриваемый" элемент if id == 1. Однако, так как массив заполнен большим количеством данных, я хотел бы найти другой способ, чтобы выполнить те же самые вещи более производительным способом (избегая запуска if каждый раз).

Как я могу это сделать?

Ответы [ 4 ]

7 голосов
/ 22 июня 2011
  1. Заставить программу работать
  2. Профиль
  3. Оптимизация

Дональд Кнут сказал:

Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла.

Теперь вы могли бы сделать что-то вроде:

def f
  do_something
end

f 0
for i in 2..n
  f i
end

Или даже:

def f
  yield 0
  for i in 2..@n
    yield i
  end
end

f do |i|
  do_something
end

Но вы, вероятно, не хотите делать что-либо подобное, и если бы вы это сделали, это было бы только после выяснениячто это имеет значение.

И наконец, предположим, что этот уродливый трюк на самом деле заставляет ваш сервер работать немного быстрее.Стоило ли это того?

1 голос
/ 22 июня 2011

Проверка фактического цикла for может стоить пяти минут, которые потребуются. В кругах Ruby это может вызывать недовольство, но это не значит, что его никогда не стоит использовать. Когда вы вызываете каждый или карту или что-то еще, эти методы используют циклы для любого способа. Избегайте абсолютов.

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

Если вам не нужен конкретный элемент, возможно, вам не нужно хранить эту строку данных в базе данных. В чем разница между строкой 1 и остальными строками, другими словами, почему вы ее пропускаете? Всегда ли в строке с id = 1 будут одни и те же данные? Если это так, то хранить его как константу, скорее всего, будет лучше, и ваш вопрос станет спорным. Производительность почти всегда стоит больше памяти.

Если Rails 3 не работает по-другому, и вы извлекаете данные и используете id в качестве ключа поиска, id = 1 будет в элементе 0.

К сожалению, цитата Кнута часто неверно истолковывается и используется для оправдания дрянного, неэффективного кода, который не был бы написан, если программист был достаточно образован, и думал об этом в течение 5 секунд. Конечно, потратить неделю, пытаясь ускорить код, который вы не знаете, является проблемой или незначительной проблемой, но это больше, чем говорил Кнут. Производительность - одно из самых неправильно понимаемых и злоупотребляемых понятий в информатике.

1 голос
/ 22 июня 2011
a = ['a', 'b', 'c']
a.each_with_index.reject {|el,i| i == 1}.each do |el,i|
  # do whatever with the element
  puts el
end

ИМХО - лучший способ сделать выбор вместо использования своего собственного явного if выражения. Я полагаю, однако, что это приведет к примерно той же производительности, что и при использовании if, возможно, даже немного ниже.

Если после сравнительного анализа, как другие предположили, вы знаете, что время, которое требуется, определенно медленнее, чем то, что вы можете себе позволить, и это - выбор, вызывающий медлительность, то это можно легко изменить, чтобы удалить выбор несколькими способами. :

a = ['a', 'b', 'c']
n = 1
(a.first(n) + a.drop(n + 1)).each do |el|
  # do whatever with the element
  puts el
end

К сожалению, я считаю, что это также будет медленнее, чем запуск простого if. Я полагаю, что у скорости есть потенциал:

a = ['a', 'b', 'c']
n = 1
((0...n).to_a+((n+1)...a.size).to_a).map{|i| a[i]}.each do |el|
  # do whatever with the element
  puts el
end

Но опять же есть большая вероятность быть медленнее.

EDIT

Бенчмарк находится в этом гисте . Эти результаты на самом деле меня удивили, отклонение - самый медленный вариант, за которым следуют диапазоны. Самая высокая эффективность после полного удаления элемента - использование first и drop для выбора всех элементов вокруг него.

Результаты в процентах, без выбора в качестве базовой линии:

with if             146%
with first and drop 104%
without if          100%

Очевидно, что это сильно зависит от того, что вы делаете с элементами, это было тестирование, вероятно, с самой быстрой операцией, которую может выполнить Ruby. Чем медленнее операция, тем меньше будет разница. Как всегда: Benchmark, Benchmark, Benchmark

1 голос
/ 22 июня 2011

if оператор очень дешевая операция.Вы можете проверить это с помощью стандартных инструментов тестирования производительности.

require "benchmark"

array = [1] * 100_000

Benchmark.bm do |bm|
  bm.report "with if" do
    array.each_with_index do |element, i|
      next if i == 1
      element - 1
    end
  end

  bm.report "without if" do
    array.each do |element|
      element - 1
    end
  end
end

Результаты:

                user     system      total        real
with if     0.020000   0.000000   0.020000 (  0.018115)
without if  0.010000   0.000000   0.010000 (  0.012248)

Разница составляет около 0,006 секунды для массива 100 000 элементов.Вы не должны заботиться об этом, если это не станет узким местом, и я сомневаюсь, что это будет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...