我有一个300MB的文本文件,我想计算文件中每10,000个子字符串的出现次数。我想知道如何快速完成。
现在,我使用以下代码:
content = IO.read("path/to/mytextfile")
Word.each do |w|
w.occurrence = content.scan(w.name).size
w.save
end
Word是一个ActiveRecord类。
我花了差不多1天才完成计算。反正有更快的做法吗?感谢。
EDIT1:
再次感谢你。我正在运行rails 2.3.9。单词表的name
字段包含我要搜索的内容,并且它仅包含唯一值。我没有使用Word.each
,而是使用批量(一次1000行)加载。它应该有所帮助。
我用bpaulon的想法重写了整个代码。现在只花了几个小时才完成计数。
我分析了新的版本代码,现在最大的时间成本计算方法是utf8编码支持的字符串截断代码
def truncate(n)
self.slice(/\A.{0,#{n}}/m)
end
和计算代码的字符
def utf8_length
self.unpack('U*').size
end
还有其他更快的方法来替换它们吗?
答案 0 :(得分:3)
您对scan
的使用会创建一个数组,计算它的大小,然后将其抛弃。如果你在大文件中出现很多子字符串,你会暂时创建一个大数组,可能会耗尽内存管理的CPU时间,但即使使用300MB也应该很快运行。
因为Word是ActiveRecord类,所以它依赖于数据库中的模式和任何索引,以及数据库服务器可能遇到的任何问题。如果数据库未优化或响应缓慢或用于检索数据的查询效率不高,则迭代将很慢。您可能会发现抓取Word
组的速度要快得多,因此它们位于RAM中,然后迭代它们。
而且,如果数据库和您的代码在同一台机器上运行,您可能会遇到资源限制,例如只有一个驱动器,没有足够的RAM等等。
在不了解您的环境和硬件的情况下,很难说。
编辑:
我可以首先将子串捕获到数组/哈希中,然后将计数结果添加到数组或哈希中,并在完成所有计数后将结果写回数据库。你认为它更快,对吧?
不,我怀疑这会有多大帮助,而且,如果不知道问题出在哪里,你可能会做的就是让问题变得更糟,因为你必须从数据库中加载10,000条记录作为对象,然后构建一个10,000元素散列或数组,它们也将与DB记录一起存储在内存中,然后将它们写出来。
Ruby目前只使用单个核心,但您可以通过使用Ruby 1.9+获得速度。我建议installing RVM并让它管理你的Ruby。请务必阅读该页面上的说明,然后运行rvm notes
并按照这些说明操作。
您的Word模型和底层架构和索引是什么样的?数据库是否在同一台机器上?
编辑:通过查看您的表架构,您没有除id
之外的索引,这对于正常查找实际上没有多大帮助。我建议在Stack Overflow的兄弟网站https://dba.stackexchange.com/上展示你的架构并解释你想做什么。我至少会在文本字段中添加一个键,以帮助避免对您执行的任何搜索进行全表扫描。
可能有帮助的更多内容是从“Active Record Query Interface”中读取:Retrieving Multiple Objects in Batches。
另外,查看Word.each
运行时发出的SQL。它是"select * from word"
吗?如果是这样,Rails将提取10,000条记录,逐个迭代它们。如果它类似于"select * from word where id=1"
,那么对于每个记录,您都有一个数据库读取,然后在更新计数时写入。这就是“批量检索多个对象”链接将有助于修复的情况。
另外,我猜你content
是你要搜索的文字,但我无法确定。您是否可能有重复的文本值导致您对同一文本进行多次扫描?如果是,请在该字段上使用unique
条件选择记录,然后一次更新所有匹配记录的计数。
您是否已分析过代码以了解Ruby本身是否可以帮助您查明问题?稍微修改您的代码以处理100或1000条记录。使用-r profile
标志启动应用。当应用程序退出探查器时,将输出一个表格,显示花费的时间。
你在运行什么版本的Rails?
答案 1 :(得分:1)
我认为你可以用不同的方式解决这个问题
您不需要多次扫描文件,您可以创建一个数据库,如mongo或mysql,对于您找到的每个单词,您可以为它获取数据库,然后增加一些“反”字段。
你可以问我“但是我必须经常扫描我的数据库,这可能会花费更多”。好吧,你肯定不会问这个,但是不会花费更多的时间,因为数据库集中在IO中,除了你总是可以index it。
编辑:根本没有办法划界?假设你拥有一个Word.name字符串,你真的拥有一个(不是简单的)正则表达式。正则表达式是否包含\ n?好吧,如果正则表达式可以包含任何值,您应该估计正则表达式可以获取的字符串的最大大小,加倍,并通过该字符数量扫描文件,但将光标移动该数字。
让我们说你对正则表达式可以获取的最大值的估计就像20个字符,你的文件有0到30000个字符。你将每个正则表达式从0到40个字符传递,然后再从20到60,从40到80,等等......
您还应该保留您在较小的正则表达式中找到的位置,这样就不会重复它。
最后,这个解决方案似乎不值得努力,你的问题可能有一个更好的解决方案,基于正则表达式是什么,但它会比调用扫描Words.count倍你的300Mb字符串更快。
答案 2 :(得分:0)
您可以将整个“Word”表格加载到Trie,然后进行回溯,因为您说文本中没有分隔符。
因此,对于文本中的每个字符,请记下单词。如果您单击一个单词,则增加其计数。 “走下去”涉及三个案例:
回溯跟踪只是在你用尽Trie的“搜索”之后跟踪你想要去的地方,当你用完节点访问时。这可能是您访问的每个角色,这是Trie的根。
完成此操作后,您可以访问您更改的所有节点,只需更新它们所代表的记录。
这需要一些时间来实施,但肯定会比每个&更快。扫描。