2014-10-14 3 views
0

Допустим, у меня есть 4 папки с 25 папками в каждом. В каждой из этих 25 папок есть 20 папок с 1 очень длинным текстовым документом. Метод, который я использую сейчас, кажется, имеет место для улучшения и в каждом сценарии, в котором я реализую потоки ruby, результат медленнее, чем раньше. У меня есть массив из 54 имен папок. Я перебираю каждый из них и использую метод foreach для получения глубоко вложенных файлов. В цикле foreach я делаю 3 вещи. Я получаю содержимое сегодняшнего файла, я получаю содержимое вчерашнего файла, и я использую свой алгоритм diff, чтобы найти то, что изменилось со вчерашнего дня. Как бы вы делали это быстрее с помощью потоков.Ruby threading vs normal

def backup_differ_loop device_name 

    device_name.strip! 
    Dir.foreach("X:/Backups/#{device_name}/#{@today}").each do |backup| 

    if backup != "." and backup != ".." 
     @today_filename = "X:/Backups/#{device_name}/#{@today}/#{backup}" 
     @yesterday_filename = "X:/Backups/#{device_name}/#{@yesterday}/#{backup.gsub(@today, @yesterday)}" 

     if File.exists?(@yesterday_filename) 
     today_backup_content = File.open(@today_filename, "r").read 
     yesterday_backup_content = File.open(@yesterday_filename, "r").read 

     begin 
     Diffy::Diff.new(yesterday_backup_content, today_backup_content, :include_plus_and_minus_in_html => true, :context => 1).to_s(:html) 
     rescue 
     #do nothing just continue 
     end 

     end 

     else 
     #file not found 
     end 

    end 

    end 
+0

Простая версия потоков будет состоять в том, чтобы использовать пул для обработки каждого файла (или даже каталога/устройства, в зависимости от области видимости) в отдельном потоке в виде отдельного автономного процесса. Пул позволяет избежать небольшого количества накладных расходов, позволяя одному потоку (по одному на время) обрабатывать несколько элементов. Если нет измененных общих данных (т. Е. «Резервная копия» уникальна для каждого процесса потока), то нет проблем с параллелизмом, о которых нужно беспокоиться (кроме запуска).Кроме того, версия Ruby имеет значение, если это может даже быть полезным изменением. Теперь прочитайте и спроектируйте реализацию. Вернитесь с проблемой :) – user2864740

ответ

2

Первая часть вашей логики - поиск всех файлов в определенной папке. Вместо того, чтобы делать Dir.foreach, а затем проверять «.». и «..» вы можете сделать это в одной строке:

files = Dir.glob("X:/Backups/#{device_name}/#{@today}/*").select { |item| File.file?(item)} 

Обратите внимание на /* в конце? Это будет поиск уровня 1 уровня (внутри папки @today). Если вы хотите также искать внутри подпапок, замените его на /**/*, чтобы вы получили массив всех файлов во всех подпапках @today.

Так что я бы сначала метод, который даст мне двойной массив, содержащий кучу массивов соответствующих файлов:

def get_matching_files 
    matching_files = [] 

    Dir.glob("X:/Backups/#{device_name}/#{@today}/*").select { |item| File.file?(item)}.each do |backup| 
    today_filename = File.absolute_path(backup) # should get you X:/Backups...converts to an absolute path 
    yesterday_filename = "X:/Backups/#{device_name}/#{@yesterday}/#{backup.gsub(@today, @yesterday)}" 

    if File.exists?(yesterday_filename) 
     matching_files << [today_filename, yesterday_filename] 
    end 
    end 

    return matching_files 
end 

и назвать его:

matching_files = get_matching_files 

Теперь мы можем начать многопоточность, в которой вещи, вероятно, замедляются. Я сначала получить все файлы из matching_files массива в очереди, а затем начать 5 потоков, которые будут идти до тех пор, пока очередь не пуста:

queue = Queue.new 
matching_files.each { |file| queue << file } 

# 5 being the number of threads 
5.times.map do 
    Thread.new do 
    until queue.empty? 
     begin 
     today_file_content, yesterday_file_content = queue.pop 
     Diffy::Diff.new(yesterday_backup_content, today_backup_content, :include_plus_and_minus_in_html => true, :context => 1).to_s(:html) 
     rescue 
     #do nothing just continue 
     end 
    end 
    end 
end.each(&:join) 

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

И самое важное: стандартная реализация Ruby может работать только по 1 потоку за раз. Это означает, что даже если вы реализуете вышеприведенный код, вы не получите существенной разницы в производительности. Получайте Rubinius или JRuby, которые позволяют запускать более одного потока одновременно. Или, если вы предпочитаете использовать стандартный MRI Ruby, вам потребуется переструктурировать свой код (вы можете сохранить свою оригинальную версию) и запустить несколько процессов. Вам просто нужно что-то вроде общей базы данных, где вы можете хранить match_files (например, одну строку), и каждый раз, когда процесс «берет» что-то из этой базы данных, он будет отмечать эту строку как «использованную». SQLite - хороший db для этого, я думаю, потому что по умолчанию это потокобезопасно.

+0

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

+0

Вы сначала соединяете их в массив, чтобы получить двойной массив с кучей этих пар. Очередь - это двойной массив, каждый queue.pop получит один элемент массива с двумя элементами в нем, таким образом параллельное назначение. – daremkd

0

Большинство реализаций Ruby не имеют «истинных» многоядерных потоков, то есть нити не улучшат производительность, поскольку интерпретатор может запускать только один поток за раз. Для таких приложений, как ваш с большим количеством дисковых ввода-вывода, это особенно актуально. Фактически даже при реальной многопоточности ваши приложения могут быть привязаны к IO и все еще не видят значительной части улучшения.

Вы, скорее всего, получите результаты, найдя в своем коде неэффективный алгоритм и улучшаете его.