Причины такого неравенства в скорости исполнения? - PullRequest
7 голосов
/ 08 декабря 2009

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

Реализация Python занимает несколько секунд, а реализация Ruby - около 4 минут.

У меня такое ощущение, что это, скорее всего, из-за недостатка знаний Ruby, какие-либо идеи о том, что я делаю неправильно?

Среда - Windows XP x64, Python 2.6, Ruby 1.8.6

Python

f = open('c:\\file1.txt', 'r')

hashes = dict()

for line in f.readlines():
    if not line in hashes:
        hashes[line] = 1
    else:
        hashes[line] += 1


print "Done file 1"

f.close()

f = open('c:\\file2.txt', 'r')

for line in f.readlines():
    if not line in hashes:
        print "Hash not found!"
    else:
        hashes[line] -= 1

f.close()

print "Done file 2"

num_errors = 0

for key in hashes.keys():
    if hashes[key] != 0:
        print "Uneven hash count: %s" % key
        num_errors += 1

print "Total of %d mismatches found" % num_errors

рубин

file = File.open("c:\\file1.txt", "r")
hashes = {}

file.each_line { |line|
  if hashes.has_key?(line)
    hashes[line] += 1
  else
    hashes[line] = 1
  end
}

file.close()

puts "Done file 1"

file = File.open("c:\\file2.txt", "r")

file.each_line { |line|
  if hashes.has_key?(line)
    hashes[line] -= 1
  else
    puts "Hash not found!"
  end
}

file.close()

puts "Done file 2"

num_errors = 0
hashes.each_key{ |key|
  if hashes[key] != 0
    num_errors += 1
  end
}

puts "Total of #{num_errors} mismatches found"

РЕДАКТИРОВАТЬ Чтобы получить представление о масштабе, каждый файл имеет довольно большой размер, более 900 000 хешей.

ПРОГРЕСС

Используя рекомендации nathanvda, вот оптимизированный скрипт ruby:

f1 = "c:\\file1.txt"
f2 = "c:\\file2.txt"

hashes = Hash.new(0)

File.open(f1, "r") do |f|
  while line = f.gets
    hashes[line] += 1
  end
end  

not_founds = 0

File.open(f2, "r") do |f|
  while line = f.gets
    if hashes.has_key?(line)
      hashes[line] -= 1
    else
      not_founds += 1
    end
  end
end

num_errors = hashes.values.to_a.select { |z| z != 0}.size   

puts "Total of #{not_founds} lines not found in file2"
puts "Total of #{num_errors} mismatches found"

В Windows с Ruby 1.8.7 оригинальная версия занимала 250 секунд, а оптимизированная версия - 223.

На виртуальной машине Linux! работает под управлением ruby ​​1.9.1, оригинальная версия работала за 81 секунду, примерно в 1/3 времени, как windows 1.8.7. Интересно, что оптимизированная версия заняла больше времени на 89 секунд. Обратите внимание, что хотя строка = ... была необходима из-за ограничений памяти.

В Windows с Ruby 1.9.1 оригинал занял 457 секунд, а оптимизированная версия - 543.

В Windows с jRuby оригинал занял 45 секунд, а оптимизированная версия - 43.

Я несколько удивлен результатами, я ожидал, что 1.9.1 будет улучшением по сравнению с 1.8.7.

Ответы [ 8 ]

5 голосов
/ 08 декабря 2009

Я обнаружил, что эталонная реализация Ruby (ну, Ruby) является (с научной точки зрения) медленной.

Если у вас есть такая возможность, попробуйте запустить программу под JRuby! Чарльз Наттер и другие люди из Sun утверждают, что резко ускорили Руби.

Меня, например, больше всего интересовали бы ваши результаты.

5 голосов
/ 08 декабря 2009

Это может быть потому, что в Python DICT намного быстрее, чем хэши в Ruby

.

Я только что провел быстрый тест, сборка хэша 12345678 элемента в Ruby1.8.7 заняла в 3 раза больше времени, чем Python. Ruby1.9 был примерно вдвое длиннее Python.

Вот как я тестировал
питон

$ time python -c "d={}
for i in xrange(12345678):d[i]=1"

рубин

$ time ruby -e "d={};12345678.times{|i|d[i]=1}"

Не достаточно, чтобы учесть ваше расхождение.

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

Вот еще одна версия в Python, использующая defaultdict и менеджеры контекста

from collections import defaultdict
hashes = defaultdict(int)

with open('c:\\file1.txt', 'r') as f:
    for line in f:
        hashes[line] += 1

print "Done file 1"

with open('c:\\file2.txt', 'r') as f:
    for line in f:
        if line in hashes:
            hashes[line] -= 1
        else:
            print "Hash not found!"

print "Done file 2"

num_errors = 0
for key,value in hashes.items():  # hashes.iteritems() might be better here
    if value != 0:
        print "Uneven hash count: %s" % key
        num_errors += 1

print "Total of %d mismatches found" % num_errors
2 голосов
/ 08 декабря 2009

На стороне Python вы можете перебирать элементы словаря следующим образом:

for key, value in hashes.iteritems():
    if value != 0:
        print "Uneven hash count: %s" % key
        num_errors += 1

Также:

for line in f.readlines():
    hashes[line] = hashes.setdefault(line, 0) + 1

... но я не могу помочь вам с Ruby, кроме как предложить вам выследить профилировщика.

0 голосов
/ 07 марта 2011

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

def convert_to_hash(file)
  values_hash = file.each_line.group_by {|line| line}
  # Hash.[] converts an array of pairs into a hash
  count_hash = Hash[ values_hash.map{|line, lines| [line, lines.length]}]
  count_hash
end

hash1 = convert_to_hash(file)
hash2 = convert_to_hash(file2)
# compare if the two hashes are equal
0 голосов
/ 04 марта 2011

Могу поспорить, что результаты Ruby 1.9.x, который быстрее или наравне с Python в большинстве областей, вызваны дополнительными издержками, необходимыми для реализации хэшей / словарей, поскольку упорядочено в Ruby вопреки Python.

0 голосов
/ 08 декабря 2009

Мне удалось немного ускорить ваш код рубина следующим образом:

require 'benchmark'

Benchmark.bm(10) do |x|

  x.report("original version") do
    file = File.open("c:\\file1.txt", "r")
    hashes = {}

    file.each_line { |line|
      if hashes.has_key?(line)
        hashes[line] += 1
      else
        hashes[line] = 1
      end
    }

    file.close()

    #puts "Done file 1"

    not_founds = 0

    file = File.open("c:\\file2.txt", "r")

    file.each_line { |line|
      if hashes.has_key?(line)
        hashes[line] -= 1
      else
        not_founds += 1        
      end
    }

    file.close()

    #puts "Done file 2"

    num_errors = 0
    hashes.each_key{ |key|
      if hashes[key] != 0
        num_errors += 1
      end
    }

    puts "Total of #{not_founds} lines not found in file2"
    puts "Total of #{num_errors} mismatches found"

  end


  x.report("my speedup") do
    hashes = {}
    File.open("c:\\file1.txt", "r") do |f|
      lines = f.readlines
      lines.each { |line|
        if hashes.has_key?(line)
          hashes[line] += 1
        else
          hashes[line] = 1
        end
      }
    end  

    not_founds = 0

    File.open("c:\\file2.txt", "r") do |f|
      lines = f.readlines
      lines.each { |line|
        if hashes.has_key?(line)
          hashes[line] -= 1
        else
          not_founds += 1
        end
      }
    end

    num_errors = hashes.values.to_a.select { |z| z != 0}.size   

    puts "Total of #{not_founds} lines not found in file2"
    puts "Total of #{num_errors} mismatches found"

  end

end

, поэтому я читаю файлы в одном баге, в моем случае это немного быстрее (я тестировал на Windows XP, ruby ​​1.8.6 и файле из 100000 строк). Я тестировал различные способы чтения файлов (я мог подумать), и это был самый быстрый способ. Также я немного ускорил подсчет значений в хэше, но это видно, только если вы сделали это для очень больших чисел:)

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

                user     system      total        real
original versionTotal of 16 lines not found in file2
Total of 4 mismatches found
   1.000000   0.015000   1.015000 (  1.016000)
my speedup v1Total of 16 lines not found in file2
Total of 4 mismatches found
   0.812000   0.047000   0.859000 (  0.859000)

У кого есть идеи по дальнейшему улучшению?

Если f.readlines идет медленнее, из-за размера я обнаружил, что

File.open("c:\\file2.txt", "r") do |f|
  while (line=f.gets)
    if hashes.has_key?(line)
      hashes[line] -= 1
    else
      not_founds += 1
    end
  end
end

немного быстрее для меня.

Я думал о том, как улучшить

if hashes.has_key?(line) ...

немного кодирую, но не мог придумать ничего.

Вы пробовали использовать Ruby 1.9?

У меня есть виртуальная машина Windows 7 с Ruby 1.9.1, и там f.readlines был медленнее, и мне нужно было использовать while (line=f.gets) из-за ограничений памяти:)

Поскольку многие пользователи Ruby тестируют в основном на платформах, связанных с Unix, я думаю, это может объяснить, почему код неоптимален в Windows. Кто-нибудь сравнивал вышеупомянутую производительность на Unix? Это проблема ruby ​​против python или Ruby-windows против Ruby-Unix?

0 голосов
/ 08 декабря 2009

словарь Python очень быстр. См. Как реализованы встроенные словари Python Возможно, в Ruby не так круто.

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

Возможно, Ruby 1.8 не спешит динамически изменять размеры больших хеш-таблиц? Как ваша проблема масштабируется с меньшими файлами?

0 голосов
/ 08 декабря 2009

Я не эксперт по Ruby, поэтому, пожалуйста, поправьте меня, если я ошибаюсь:

Я вижу небольшой потенциал оптимизации.

Если вы скажете

hashes = hash.new(0)

тогда ссылка на неопределенный хеш вернет 0 и сохранит ключ; и вы можете сделать

hashes[line] += 1

каждый раз, без ограждающих if и else.

Предупреждение: Не проверено!

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

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