执行速度差异的原因是啥?

Posted

技术标签:

【中文标题】执行速度差异的原因是啥?【英文标题】:Reasons for this disparity in execution speed?执行速度差异的原因是什么? 【发布时间】:2009-12-07 23:57:48 【问题描述】:

我编写了一个快速的 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"

在使用 Ruby 1.8.7 的 Windows 上,原始版本耗时 250 秒,优化版本耗时 223 秒。

在 Linux 虚拟机上!运行 ruby​​ 1.9.1,原始版本运行时间为 81 秒,大约是 windows 1.8.7 时间的 1/3。有趣的是,优化后的版本耗时更长,为 89 秒。请注意,由于内存限制,而 line = ... 是必需的。

在使用 Ruby 1.9.1 的 Windows 上,原始版本耗时 457 秒,优化版本耗时 543 秒。

在带有 jRuby 的 windows 上,原始版本需要 45 秒,优化版本需要 43 秒。

我对结果有些惊讶,我预计 1.9.1 会比 1.8.7 有所改进。

【问题讨论】:

还有许多其他可用的 Ruby VM,如果不是全部的话,大多数都比 Windows 上的 Ruby 1.8.6 更快。 IronRuby 可能是您最好的选择。见antoniocangiano.com/2009/08/03/… 对文件使用 readlines() 会将整个文件读入内存并创建一个巨大的列表。正如我在答案中所示,您可以一次遍历文件一行。这实际上可能比使用 readlines 慢,但内存效率更高 发帖前你应该做的第一件事是简化问题。完全删除比较和第二个文件,您仍然会看到差异,这使每个人都更容易。从那里,很容易看出 Python 的文件读取和 dicts 都比 Ruby(至少 1.8 的)快得多。在这种情况下,Python 对我来说要快 2-3 倍;额外的代码只会改变我的比例,而不是因素。 不相关的警告:readlineseach_line 返回行,包括末尾的任何换行符。如果文件中的最后一个散列后面没有换行符终止符,它将不带\n,并且不会匹配来自另一行的先前散列具有\n @bobince: 行等尾随空格数量的变化等...取决于 OP 如何定义“相同”... line.rstrip() 或行。 rstrip('\n') 或根本没有带 【参考方案1】:

我发现 Ruby 的参考实现(嗯,Ruby)很慢(不科学地说)。

如果您有机会,请尝试在 JRuby 下运行您的程序! Charles Nutter 和其他 Sun 人声称已经大大加快了 Ruby 的速度。

我对你的结果最感兴趣。

【讨论】:

Ruby 1.8.6 的速度特别慢。有问题的 Python 版本要新得多。 Ruby 1.9 肯定会更好。 我尝试了 jRuby,虽然它比 1.8.6 解释器快得多,但在约 45 秒时它仍然比 Python 慢 3 倍(约 15 秒) 有趣。非常感谢!【参考方案2】:

这可能是因为 Python 中的 dicts 比 Ruby 中的哈希快得多

我刚刚运行了一个快速测试,在 Ruby1.8.7 中构建 12345678 项的哈希值是 Python 的 3 倍。 Ruby1.9 大约是 Python 的两倍。

这是我的测试方法python

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

红宝石

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

但不足以说明您的差异。

也许文件 I/O 值得研究 - 注释掉所有哈希码并查看空循环运行文件所需的时间。

这是另一个使用 defaultdict 和上下文管理器的 Python 版本

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

【讨论】:

我相信你的意思是for key, value in hashes.items()(或hashes.iteritems())而不是hashes.keys() 在他的例子中,他正在散列字符串。更改您的 python 测试以使用: d[str(i)]=1 使我的时间增加了四倍。我对 Ruby 了解的不够多,无法更改 Ruby 示例并进行比较。 @Mark,对于 ruby​​ 来说应该是 i.to_s @gnibbler,我的时代:Python(int):0m4.359s Python(str):0m16.202s Ruby(int):0m8.092s Ruby(str):0m36.648s 似乎它们可以缩放差不多。【参考方案3】:

在 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 方面为您提供帮助,只能建议您寻找分析器。

【讨论】:

如果字典有很多元素,你应该改用hashes.iteritems()【参考方案4】:

我不是 Ruby 专家,所以如果我错了,请有人纠正我:

我看到了一个小的优化潜力。

如果你说

hashes = hash.new(0)

然后对未定义哈希的引用将返回 0 并存储密钥;你可以这样做

hashes[line] += 1

每次,不包括 ifelse

警告:未经测试!

如果存储密钥不是自动发生的,还有另一个使用块的哈希构造函数,您可以在其中显式执行它。

【讨论】:

类似地,collections.defaultdict(lambda : 0) 用于 Python。 docs.python.org/library/collections.html#defaultdict-objects 我相信它更典型地写成collections.defaultdict(int)【参考方案5】:

Python 的字典速度非常快。请参阅How are Python's Built In Dictionaries Implemented Ruby 可能没有那么火爆。

我怀疑是哈希函数。 Ruby 开发人员不可能拥有比 Python 差一个数量级的哈希函数。

也许 Ruby 1.8 在动态调整大型散列表的大小方面很慢?您的问题如何随着较小的文件扩展?

【讨论】:

就规模而言,1 000 和 10 000 的输入之间的差异很小。 10 000 和 100 000 之间的差异几乎是线性的。 100 000 到接近 1 000 000 之间的差异是巨大的。这表明它正在呈指数级增长。 @t_scho:后者是否也表明它正在达到某种限制(例如 RAM)?【参考方案6】:

我能够加快您的 ruby​​ 代码速度,如下所示:

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?

我有一个带有 Ruby 1.9.1 的 Windows 7 虚拟机,那里的 f.readlines 速度较慢,由于内存限制,我需要使用 while (line=f.gets) :)

由于很多 uf Ruby 用户主要在 Unix 相关平台上进行测试,我想这可以解释为什么代码在 Windows 上不是最佳的。有没有人比较过上面提到的在 Unix 上的性能?这是 ruby​​ 与 python 的问题,还是 Ruby-windows 与 Ruby-Unix 的问题?

【讨论】:

【参考方案7】:

我敢打赌 Ruby 1.9.x 的结果(它在大多数领域都更快或与 Python 相当)是由哈希/字典实现所需的额外开销引起的,因为它们是 有序 Ruby 与 Python 不同。

【讨论】:

【参考方案8】:

我会在我的大量空闲时间尝试做一个基准测试,但请尝试使用group_by。它不仅更像函数式编程,而且我发现它比 MRI 中的程序版本快得多。

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

【讨论】:

以上是关于执行速度差异的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

java程序在不同平台下运行速度差异,求原因及解决办法!!!!!

下载速度很慢是啥原因?

php+mssql 访问速度慢是啥原因?

页面打开的速度慢是啥原因?怎么监测?

部分网页打开不开、速度慢是啥原因?

mongodb查询速度慢是啥原因