如何解释这些ruby代码的性能差异?

时间:2018-04-29 06:18:18

标签: ruby optimization time-complexity

我正在为以下问题执行一些速度测试:

  

给定2个字符串s1和s2,它们只包含小写字母,如果可以重新排列s1的字母使得s2成为s1的子字符串,则输出。

我已经在ruby中获得了2个解决方案:

版本1:

def scramble(s1,s2)
  if s1.length < s2.length
    return false
  end

  a = Array.new(26) { 0 }
  b = Array.new(26) { 0 }

  t1 = Time.now

  (0 ... s1.length).each do |x|
    a[s1[x].ord - 97] += 1
  end

  (0 ... s2.length).each do |x|
    b[s2[x].ord - 97] += 1
  end

  t2 = Time.now

  (0 ... 26).each do |x|
    if a[x] < b[x]
      return false
    end
  end

  puts t2 - t1

  return true
end

此版本将s1和s2中的字符数保存在直接寻址表中,并比较每个字符的计数。应该清楚的是,该代码执行大约2 *(N + M)次操作,其中N是s1的长度,M是s2的长度。

第2版:

def scramble(s1,s2)
  t1 = Time.now

  c = s2.chars
  c.uniq!
  t = c.all?{|x| s2.count(x)<=s1.count(x)}

  t2 = Time.now

  puts t2 - t1

  return t
end

此版本还使用s1和s2中的字符数,但不使用直接寻址表。根据我的理解,这个版本应该执行大约26 *(N + M)次操作,因为count()方法的复杂性是字符串中字符数的线性,并且为字符串中的每个不同字符调用它。

当我表演时

scramble('abcdefghijklmnopqrstuvwxyz'*500000, 'abcdefghijklmnopqrstuvwxyz'*500000)

第一个版本需要4.424207,而第二个版本只需2.574269。我用不同长度的s1和s2运行了几次测试,结果是一致的(版本2比版本1快得多)。由于它们的不同常数,我真的很困惑。为什么版本2中的代码运行速度比版本1快得多,尽管有更高的常量?

有人可以告诉我吗?

1 个答案:

答案 0 :(得分:4)

我认为这是因为像String#count这样的标准库方法是用C实现的,这比在循环中执行复杂的Ruby表达式a[s1[x].ord - 97] += 1 500000次要少。

要了解我的意思,请尝试更换这些循环:

(0 ... s1.length).each do |x|
  a[s1[x].ord - 97] += 1
end

(0 ... s2.length).each do |x|
  b[s2[x].ord - 97] += 1
end

调用String#count

(0 ... 26).each do |x|
  a[x] = s1.count((x + 97).chr)
  b[x] = s2.count((x + 97).chr)
end

通过此更改,它在我的机器上运行0.4秒(相比之前的6.3秒)!