Какая из этих инициализаций массива лучше в Ruby? - PullRequest
1 голос
/ 15 июня 2010

Какая из этих двух форм инициализации массива лучше в Ruby?

Метод 1:

DAYS_IN_A_WEEK = (0..6).to_a
HOURS_IN_A_DAY = (0..23).to_a

@data = Array.new(DAYS_IN_A_WEEK.size).map!{ Array.new(HOURS_IN_A_DAY.size) }

DAYS_IN_A_WEEK.each do |day|
  HOURS_IN_A_DAY.each do |hour|
    @data[day][hour] = 'something'
  end
end

Метод 2:

DAYS_IN_A_WEEK = (0..6).to_a
HOURS_IN_A_DAY = (0..23).to_a

@data = {}

DAYS_IN_A_WEEK.each do |day|
  HOURS_IN_A_DAY.each do |hour|
    @data[day] ||= {}
    @data[day][hour] = 'something'
  end
end

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

Однако в Ruby не совсем понятно, что происходит. Так что, если кто-то может объяснить мне, что лучше, это было бы действительно здорово!

Спасибо

Ответы [ 3 ]

3 голосов
/ 15 июня 2010

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

В: Должен ли я сосредоточиться на том, чтобы сначала сделать мой код читабельным, или в первую очередь на производительности?

A: Сделайте ваш код читабельным и сначала исправьте , затем и только Если есть проблемы с производительностью, начните беспокоиться о производительности на измерение , где проблема производительности сначала и только , а затем внесение изменений в ваш код.

Теперь, чтобы ответить на вопрос, который вы задали, но не должны были:

method1.rb:

DAYS_IN_A_WEEK = (0..6).to_a
HOURS_IN_A_DAY = (0..23).to_a

10000.times do

  @data = Array.new(DAYS_IN_A_WEEK.size).map!{ Array.new(HOURS_IN_A_DAY.size) }

  DAYS_IN_A_WEEK.each do |day|
    HOURS_IN_A_DAY.each do |hour|
      @data[day][hour] = 'something'
    end
  end

end

method2.rb:

DAYS_IN_A_WEEK = (0..6).to_a
HOURS_IN_A_DAY = (0..23).to_a

10000.times do

  @data = {}

  DAYS_IN_A_WEEK.each do |day|
    HOURS_IN_A_DAY.each do |hour|
      @data[day] ||= {}
      @data[day][hour] = 'something'
    end
  end

end

Результаты эталонного теста:

$ time ruby method1.rb

real    0m1.189s
user    0m1.140s
sys 0m0.000s

$ time ruby method2.rb

real    0m1.879s
user    0m1.780s
sys 0m0.020s

Похоже, использование времени пользователем (важный фактор) намного быстрее вызывает method1.rb. Вы, конечно же, не должны доверять этому эталонному тесту и должны сами отражать фактическое использование кода. Однако это то, что вы должны делать только через после , когда вы определили, какой код является вашим узким местом в реальности. (Подсказка: 99,44% компьютерных программистов ошибаются на 100% , когда догадываются, где их узкие места без измерений!)

3 голосов
/ 15 июня 2010

Что не так с просто

@data = Array.new(7) { Array.new(24) { 'something' }}

Или, если у вас есть контент, имеющий везде одинаковый объект 1005 *:

@data = Array.new(7) { Array.new(24, 'something') }

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

user   system     total       real
method1  8.969000 0.000000  8.969000 ( 9.059570)
method2 16.547000 0.000000 16.547000 (16.799805)
method3  6.468000 0.000000  6.468000 ( 6.616211)
method4  0.969000 0.015000  0.984000 ( 1.021484)
В этой последней строке также показана другая интересная вещь: во время выполнения преобладает время, необходимое для создания 7 * 24 * 100000 = 16,8 миллиона 'something' строк.

И, конечно, есть еще одно важное замечание: ваши method1 и method2, которые вы сравниваете друг с другом, делают две совершенно разные вещи! Даже не имеет смысла сравнивать их друг с другом. method1 создает Array, method2 создает Hash.

Ваш method1 эквивалентен моему первому примеру выше:

@data = Array.new(7) { Array.new(24) { 'something' }}

Хотя method2 ( очень примерно) эквивалентно:

@data = Hash.new {|h, k| h[k] = Hash.new {|h, k| h[k] = 'something' }}

Хорошо, за исключением того, что ваш method2 инициализирует весь Hash с нетерпением, в то время как мой метод выполняет код инициализации только лениво в случае чтения неинициализированного ключа.

Другими словами, после запуска приведенного выше кода инициализации, Hash все еще пуст:

@data # => {}

Но всякий раз, когда вы пытаетесь получить доступ к ключу, он волшебным образом появляется:

@data[5][17] # => 'something'

И он останется там:

@data # => {5 => {17 => 'something'}}

Поскольку этот код на самом деле не инициализирует Hash, он, очевидно, намного быстрее:

user   system     total       real
method5  0.266000 0.000000  0.266000 ( 0.296875)
2 голосов
/ 15 июня 2010

Я обернул оба фрагмента кода в отдельные методы и провел некоторый сравнительный анализ.Вот результаты:

Benchmark.bm(7) do |x|
  x.report ("method1") { 100000.times { method1 } }
  x.report ("method2") { 100000.times { method2 } }
end

             user     system      total        real
method1 11.370000   0.010000  11.380000 ( 11.392233)
method2 17.920000   0.010000  17.930000 ( 18.328318)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...