Нам нужны объективные измерения. Профилировщик нужен.
В комплект поставки Ruby входит профилировщик
profile
. Для его вызова достаточно включить библиотеку:
ruby -rprofile myprog.rb
Рассмотрим листинг 16.6. Эта программа открывает файл
/usr/share/dict/words
и ищет в нем анаграммы. Затем смотрит, у каких слов оказалось больше всего анаграмм, и распечатывает их.
Листинг 16.6. Поиск анаграмм в словаре
words = File.readlines("/usr/share/dict/words")
words.map! {|x| x.chomp }
hash = {}
words.each do |word|
key = word.split("").sort.join
hash[key] ||= []
hash [key] << word
end
sizes = hash.values.map {|v| v.size }
most = sizes.max
list = hash.find_all {|k,v| v.size == most }
puts "Ни
у одного слова нет более #{most-1} анаграмм."
list.each do |key,val|
anagrams = val.sort
first = anagrams.shift
puts "Слово #{first} имеет #{most-1) анаграмм:"
anagrams.each {|a| puts " #{a}" }
end
num = 0
hash.keys.each do |key|
n = hash[key].size
num += n if n > 1
end
puts
puts "Всего слов в словаре: #{words.size},"
puts "из них имеют анаграммы: #{num}."
Наверняка вам интересно, какие получились результаты. Вот какие:
Ни у одного слова нет более 14 анаграмм.
Слово alerts имеет 14 анаграмм:
alters
artels
estral
laster
lastre
rastle
ratels
relast
resalt
salter
slater
staler
stelar
talers
Всего слов в словаре: 483523,
из них имеют анаграммы: 79537.
На моем компьютере этот файл содержит более 483000 слов, и программа работала чуть меньше 18 секунд. Как вы думаете, на что ушло это время? Попробуем выяснить. Профилировщик выдал более 100 строк, отсортированных в порядке убывания времени. Мы покажем только первые 20:
что больше всего времени программа тратит в методе
Array#each
. Это понятно: ведь цикл выполняется для каждого слова и на каждой итерации делает довольно много. Среднее значение в данном случае сбивает с толку, поскольку почти все время уходит на первый вызов
each
, а остальные 14 (см.
anagrams.each
) выполняются очень быстро.
Мы также видим, что
Hash#[]
— дорогая операция (главным образом потому что часто выполняется); на 1.4 миллиона вызовов было потрачено почти 11 секунд.
Обратите внимание, что метод
readlines
оказался чуть ли не в самом конце списка. Эта программа тратит время не на ввод/вывод, а на вычисления. На чтение всего файла ушло всего-то четверть секунды.
Но этот пример не показывает, в чем истинная ценность профилирования. В программе нет ни методов, ни классов. На практике вы, скорее всего, увидите свои методы среди системных. И тогда будете точно знать, какие из ваших методов находятся в числе первых 20 «пожирателей времени».
Надо ясно понимать, что профилировщик Ruby (видно, по иронии судьбы) работает медленно. Он подключается к программе во многих местах и следит за ее выполнением на низком уровне (причем сам написан на чистом Ruby). Так что не удивляйтесь, если ваша программа в ходе сеанса профилирования будет работать на несколько порядков медленнее. В нашем примере она работала 7 минут 40 секунд (460 секунд), то есть в 25 раз медленнее обычного.
Помимо профилировщика, есть еще один низкоуровневый инструмент — стандартная библиотека
benchmark
, которая тоже полезна для измерения производительности.