首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Fixnum#to_s性能问题

Fixnum#to_s性能问题
EN

Stack Overflow用户
提问于 2014-12-03 07:11:27
回答 3查看 137关注 0票数 2

我有以下算法(它搜索二进制表示也是回文的回文):

代码语言:javascript
复制
#!/usr/bin/env ruby
class Runner
  attr_accessor :min, :max, :sum

  def initialize(min, max)
    @sum = 0
    @min, @max = min, max
  end

  def run
    current = @min
    while current <= @max
      string = current.to_s
      if string == string.reverse && current.to_s(2) == current.to_s(2).reverse
        @sum += current
      end
      current += 1
    end
    puts @sum
  end
end

t1 = Time.now
Runner.new(*ARGV.first.split(/\.+/).map(&:to_i)).run
puts "Time spent: #{(Time.now - t1).round(2)} s."

我运行它的10..10_000_000范围。我提供的上述版本的结果如下:

代码语言:javascript
复制
Time spent: 2.55 s.

但如果我改变了

代码语言:javascript
复制
    while current <= @max
      string = current.to_s
      if string == string.reverse && current.to_s(2) == current.to_s(2).reverse

代码语言:javascript
复制
    while current <= @max
      string = current.to_s
      string_b = current.to_s(2)
      if string == string.reverse && string_b == string_b.reverse

然后我得到

代码语言:javascript
复制
Time spent: 6.25 s.

虽然我预计它会更快(更少的计算量),但速度会慢两倍。同时,没有参数的#to_s按预期工作,如果我移除变量并执行两次计算:

代码语言:javascript
复制
    while current <= @max
      if current.to_s == current.to_s.reverse && current.to_s(2) == current.to_s(2).reverse

那么它的工作速度就慢了

代码语言:javascript
复制
Time spent: 4.17 s.

查看S无助于理解它,除非问题与条件的else分支有关。但我在参数NUM2INT上也找不到问题。有人能解释为什么它是这样工作的(我使用MRI 2.1.1p76)吗?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-12-03 07:48:08

在Ruby中,当您有如下表达式时

代码语言:javascript
复制
a && b

首先计算a,如果为false,则不计算b

Base10回文很少,因此对于循环中的大部分循环,原始代码只检查string == string.reverse是假的,然后继续循环的下一次迭代。因此,您只执行一次to_s,而对to_s(2)几乎从不执行。

其他一些更改都更频繁地调用to_s,从而完成了更多的工作,因此速度更慢。对我来说不太奇怪。

票数 3
EN

Stack Overflow用户

发布于 2014-12-03 07:48:24

在您的原始版本中,current.to_s(2)只在string == string.reverse时计算,因为&&短路.相当于:

代码语言:javascript
复制
while current <= @max
  string = current.to_s
  if string == string.reverse
    string_b = current.to_s(2)
    if string_b == string_b.reverse
票数 3
EN

Stack Overflow用户

发布于 2014-12-03 09:38:14

在分析器中分析这段代码,似乎大部分时间都花在fix_to_s() C调用中。

跳到fix_to_s()的汇编代码中,我们可以看到,它的大部分时间都花在了单个指令movslq %edx, %rdx上。显然,movslq是将一个长(32位)移动到一个四位(64位)。

在你的string2回忆录版本中,我们有3027个movslq样本,而在非回忆录版本中,我们只有700个样本!怎么回事?

在回忆录版本中,似乎分配了更多的字符串对象,因此由ruby解释器释放。注意创建和释放字符串对象所花费的时间。

我使用allocation_stats gem对对象分配进行了一些统计,它们表明在非回忆录版本中分配的字符串确实更少。大约2/3的数量。

test.rb String 20032954

test.rb String 30010967

当您显式地设置string2 = current.to_s(2)时,它们必须为您分配内存,因为您可能需要它。但是,在您的代码中,它不一定要执行&&语句的第2部分,所以您可以完全避免这种分配。

显然,增加分配量会以多种方式减慢速度,包括使缓存丢失的可能性更大,垃圾收集更多,以及更多的free()ing和malloc()ing,这解释了为什么分配/时间之间的关系不是线性的。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/27265723

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档