Сомнения в дизайне Rails: Должен ли / я мог загрузить весь словарь / таблицу в память? - PullRequest
5 голосов
/ 24 января 2010

Я новичок, работающий в простом приложении на Rails, которое переводит документ (длинную строку) с языка на другой язык. Словарь представляет собой таблицу терминов (строковое регулярное выражение для поиска и замены и блок, который выводит замещающую строку). Таблица содержит 1 миллион записей.

Каждый запрос - это документ, который нужно перевести. При первом подходе brutish force мне нужно запустить весь словарь для каждого запроса / документа.

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

Я знаю, что это не самый эффективный, но словарь должен быть целым на этом этапе.

1.- Если эффективность не может быть достигнута путем реструктуризации документа и словаря (то есть невозможно создать меньшие подмножества словаря). Какой лучший дизайнерский подход?

2.- Знаете ли вы о подобных проектах, из которых я могу поучиться?

3.- Где мне искать, чтобы узнать, как загрузить такую ​​большую таблицу в память (кэш?) При запуске rails?

Любой ответ на любой из поставленных вопросов будет принят с благодарностью. Большое спасибо!

Ответы [ 4 ]

2 голосов
/ 25 января 2010

Не думаю, что ваш веб-хостер будет доволен таким решением. Этот скрипт

dict = {}
(0..1000_000).each do | num |
    dict[/#{num}/] = "#{num}_subst"
end

потребляет гигабайт оперативной памяти на моем MBP для хранения хеш-таблицы. Другим подходом будет сохранение ваших замен, маршалированных в memcached, чтобы вы могли (по крайней мере) хранить их на разных машинах.

require 'rubygems'
require 'memcached'
@table = Memcached.new("localhost:11211")

retained_keys = (0..1000_000).each do | num |
  stored_blob = Marshal.dump([/#{num}/, "#{num}_subst"])
  @table.set("p#{num}", stored_blob)
end

Вам нужно будет позаботиться о том, чтобы клавиши оставались «горячими», так как memcached истечет, если они не нужны.

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

Но это должно помочь вам начать:

  require "base64"

  File.open("./dict.marshal", "wb") do | file |
    (0..1000_000).each do | num |
      stored_blob = Base64.encode64(Marshal.dump([/#{num}/, "#{num}_subst"]))
      file.puts(stored_blob)
    end
  end

  puts "Table populated (should be a 35 meg file), now let's run substitutions"

  File.open("./dict.marshal", "r") do | f |
    until f.eof?
      pattern, replacement = Marshal.load(Base64.decode64(f.gets))
    end
  end

  puts "All replacements out"

Чтобы заполнить файл И загрузить каждую замену, мне понадобится:

 real    0m21.262s
 user    0m19.100s
 sys     0m0.502s

Просто загрузить регулярное выражение и строку из файла (все миллионы, по частям)

 real    0m7.855s
 user    0m7.645s
 sys     0m0.105s

Так что это 7 секунд на ввод-вывод, но вы не потеряете память (и есть огромные возможности для улучшения) - RSIZE составляет около 3 мегабайт. Вы легко сможете заставить его работать быстрее, если вы выполните массовый ввод-вывод или сделаете один файл для 10-50 замен и загрузите их целиком. Поместите файлы на SSD или RAID, и вы получите победителя, но вы сможете сохранить свою оперативную память.

1 голос
/ 25 января 2010

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

Ознакомьтесь с этим уроком: Как профилировать приложение Rails .

Мой опыт работы на ряде платформ (ANSI C, C #, Ruby) заключается в том, что с проблемами производительности очень трудно справиться заранее ; скорее, вам лучше реализовать что-то похожее на это может быть быстрым, а затем нагрузочно протестировать его через профилировщик.

Затем, как только вы узнаете , на что тратится ваше время, вы можете потратить некоторые усилия на оптимизацию.

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

1 голос
/ 24 января 2010

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

Вы можете сделать что-то вроде:

class Dictionary < ActiveRecord::Base
  @@cached = nil
  mattr_accessor :cached

  def self.cache_dict!
    @@cached = Dictionary.all
  end
end

А потом в производство.rb:

Dictionary.cache_dict!

По вашим конкретным вопросам:

  1. Возможно, напишите ту часть, которая неэффективна в C, Java или более быстром языке
  2. Нет, прости. Может быть, вы могли бы сделать алгоритм MapReduce для распределения нагрузки по серверам.
  3. См. Выше.
0 голосов
/ 24 января 2010

Если вы используете что-то вроде cache_fu , вы можете использовать что-то вроде memcache, не выполняя никакой работы самостоятельно. Если вы пытаетесь перенести 1 ММ строк в память, вероятно, будет полезна возможность использования распределенной природы memcache.

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