【问题标题】:How much slower are strings containing numbers compared to numbers?与数字相比,包含数字的字符串要慢多少?
【发布时间】:2011-09-20 16:33:57
【问题描述】:

假设我想获取一个数字并将其数字作为 Ruby 中的数组返回。
对于这个特定目的或一般的字符串函数和数字函数,哪个更快?

这些是我认为最常用的算法:

使用字符串:n.to_s.split(//).map {|x| x.to_i}

使用数字:

array = []
until n = 0
    m = n % 10
    array.unshift(m)
    n /= 10 
end

【问题讨论】:

  • String#to_i 不是从字符串到整数的唯一方法。您是否尝试过对 Integer() 进行基准测试?
  • 请注意,任何答案都取决于特定的 ruby​​ 实现和版本。

标签: ruby string performance integer


【解决方案1】:

史蒂文的回答令人印象深刻,但我看了几分钟,无法将其提炼成一个简单的答案,所以这是我的。

对于 Fixnums

使用我在下面提供的数字方法是最快的。使用num.to_s.each_char.map(&:to_i) 也非常快(而且容易得多)。


对于大人物

使用num.to_s.each_char.map(&:to_i)最快。


解决方案

如果速度是老实说您使用什么代码的决定因素(意思是don't be evil),那么此代码是该工作的最佳选择

class Integer
  def digits
    working_int, digits = self, Array.new
    until working_int.zero?
      digits.unshift working_int % 10
      working_int /= 10
    end
    digits
  end
end

class Bignum
  def digits
    to_s.each_char.map(&:to_i)
  end
end

Here 是我考虑得出这个结论的方法。

【讨论】:

  • 感谢您添加“简短答案”!我很想知道低级解决方案的答案是什么。据推测,计算每个数字是实际打印数字所需工作的一个子集,因此基于整数的方法会更快。了解 Ruby 对其 Integer 类的实现如何使其变慢会很酷。
  • 我看了一下内部结构,仍然不确定它们是如何在内部表示的(我的期望是 BCD),但似乎已经有一个宏可以完成大部分任务(可能转换数组它返回到一个 Ruby 数组,并为 bignums 提供了一个超快速的版本)github.com/ruby/ruby/blob/trunk/include/ruby/ruby.h#L881-884
【解决方案2】:

我使用 Steven Xu 的代码示例和 String#each_byte-version 制作了一个带有“基准”的解决方案。

require 'benchmark'
MAX = 10_000

#Solution based on http://stackoverflow.com/questions/6445496/how-much-slower-are-strings-containing-numbers-compared-to-numbers/6447254#6447254
class Integer
  def digits
    working_int, digits = self, Array.new
    until working_int.zero?
      digits.unshift working_int % 10
      working_int /= 10
    end
    digits
  end
end

class Bignum
  def digits
    to_s.each_char.map(&:to_i)
  end
end

[
  12345,
  1234567890,
  12345678901234567890,
  1234567890123456789012345678901234567890,
].each{|num|
puts "========="
puts "Benchmark #{num}"
Benchmark.bm do|b|

   b.report("Integer%        ") do
     MAX.times { 
        array = []
        n = num
        until n == 0
            m = n%10
            array.unshift(m)
            n /= 10
        end
        array     
     }
   end

   b.report("Integer% <<     ") do
     MAX.times { 
        array = []
        n = num
        until n == 0
            m = n%10
            array << m
            n /= 10
        end
        array.reverse
     }
   end

   b.report("Integer#divmod  ") do
     MAX.times { 
        array = []
        n = num
        until n == 0
          n, x = *n.divmod(10)
          array.unshift(x)
        end
        array     
     }
   end

   b.report("Integer#divmod<<") do
     MAX.times { 
        array = []
        n = num
        until n == 0
          n, x = *n.divmod(10)
          array << x
        end
        array.reverse
     }
   end

   b.report("String+split//  ") do
     MAX.times { num.to_s.split(//).map {|x| x.to_i} }
   end

   b.report("String#each_byte") do
     MAX.times { num.to_s.each_byte.map{|x| x.chr } }
   end

   b.report("String#each_char") do
     MAX.times { num.to_s.each_char.map{|x| x.to_i } }
   end

   #http://stackoverflow.com/questions/6445496/how-much-slower-are-strings-containing-numbers-compared-to-numbers/6447254#6447254
   b.report("Num#digit       ") do
     MAX.times { num.to_s.each_char.map{|x| x.to_i } }
   end
 end
}

我的结果:

Benchmark 12345
      user     system      total        real
Integer%          0.015000   0.000000   0.015000 (  0.015625)
Integer% <<       0.016000   0.000000   0.016000 (  0.015625)
Integer#divmod    0.047000   0.000000   0.047000 (  0.046875)
Integer#divmod<<  0.031000   0.000000   0.031000 (  0.031250)
String+split//    0.109000   0.000000   0.109000 (  0.109375)
String#each_byte  0.047000   0.000000   0.047000 (  0.046875)
String#each_char  0.047000   0.000000   0.047000 (  0.046875)
Num#digit         0.047000   0.000000   0.047000 (  0.046875)
=========
Benchmark 1234567890
      user     system      total        real
Integer%          0.047000   0.000000   0.047000 (  0.046875)
Integer% <<       0.046000   0.000000   0.046000 (  0.046875)
Integer#divmod    0.063000   0.000000   0.063000 (  0.062500)
Integer#divmod<<  0.062000   0.000000   0.062000 (  0.062500)
String+split//    0.188000   0.000000   0.188000 (  0.187500)
String#each_byte  0.063000   0.000000   0.063000 (  0.062500)
String#each_char  0.093000   0.000000   0.093000 (  0.093750)
Num#digit         0.079000   0.000000   0.079000 (  0.078125)
=========
Benchmark 12345678901234567890
      user     system      total        real
Integer%          0.234000   0.000000   0.234000 (  0.234375)
Integer% <<       0.234000   0.000000   0.234000 (  0.234375)
Integer#divmod    0.203000   0.000000   0.203000 (  0.203125)
Integer#divmod<<  0.172000   0.000000   0.172000 (  0.171875)
String+split//    0.266000   0.000000   0.266000 (  0.265625)
String#each_byte  0.125000   0.000000   0.125000 (  0.125000)
String#each_char  0.141000   0.000000   0.141000 (  0.140625)
Num#digit         0.141000   0.000000   0.141000 (  0.140625)
=========
Benchmark 1234567890123456789012345678901234567890
      user     system      total        real
Integer%          0.718000   0.000000   0.718000 (  0.718750)
Integer% <<       0.657000   0.000000   0.657000 (  0.656250)
Integer#divmod    0.562000   0.000000   0.562000 (  0.562500)
Integer#divmod<<  0.485000   0.000000   0.485000 (  0.484375)
String+split//    0.500000   0.000000   0.500000 (  0.500000)
String#each_byte  0.218000   0.000000   0.218000 (  0.218750)
String#each_char  0.282000   0.000000   0.282000 (  0.281250)
Num#digit         0.265000   0.000000   0.265000 (  0.265625)

String#each_byte/each_char 的拆分速度更快,对于较小的数字,整数版本更快。

【讨论】:

    【解决方案3】:

    差异似乎小于一个数量级,对于Fixnums,基于整数的方法更快。对于Bignums,相对性能开始时或多或少是平的,随着位数的增加,字符串方法明显胜出。

    作为字符串

    程序

    #!/usr/bin/env ruby
    
    require 'profile'
    
    $n = 1234567890
    10000.times do
        $n.to_s.split(//).map {|x| x.to_i}
    end
    

    输出

      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
     55.64     0.74      0.74    10000     0.07     0.10  Array#map
     21.05     1.02      0.28   100000     0.00     0.00  String#to_i
     10.53     1.16      0.14        1   140.00  1330.00  Integer#times
      7.52     1.26      0.10    10000     0.01     0.01  String#split
      5.26     1.33      0.07    10000     0.01     0.01  Fixnum#to_s
      0.00     1.33      0.00        1     0.00  1330.00  #toplevel
    

    作为整数

    程序

    #!/usr/bin/env ruby
    
    require 'profile'
    
    $n = 1234567890
    10000.times do
        array = []
        n = $n
        until n == 0
            m = n%10
            array.unshift(m)
            n /= 10
        end
        array
    end
    

    输出

      %   cumulative   self              self     total
     time   seconds   seconds    calls  ms/call  ms/call  name
     70.64     0.77      0.77        1   770.00  1090.00  Integer#times
     29.36     1.09      0.32   100000     0.00     0.00  Array#unshift
      0.00     1.09      0.00        1     0.00  1090.00  #toplevel
    

    附录

    这种模式似乎也适用于较小的数字。对于$n = 12345,基于​​字符串的方法大约需要 800 毫秒,而基于整数的方法大约需要 550 毫秒。

    当我越过边界进入Bignums 时,例如,使用$n = 12345678901234567890,两种方法的时间均为 2375 毫秒。看起来差异很好地平衡了,我认为这意味着内部本地供电Bignum 是类似字符串的。但是,documentation 似乎另有说明。

    出于学术目的,我再次将位数加倍为$n = 1234567890123456789012345678901234567890。我得到了大约 4450 毫秒的字符串方法和 9850 毫秒的整数方法,这是一个完全相反的结果,排除了我之前的假设。

    总结

    Number of digits | String program | Integer program | Difference
    ---------------------------------------------------------------------------
    5                | 800ms          | 550ms           | Integer wins by 250ms
    10               | 1330ms         | 1090ms          | Integer wins by 240ms
    20               | 2375ms         | 2375ms          | Tie
    40               | 4450ms         | 9850ms          | String wins by 4400ms
    

    【讨论】:

    • 使用 String#each_byte 和 each_char 您可以获得更快的结果。例子可见my benchmark-solution
    猜你喜欢
    • 2022-12-20
    • 1970-01-01
    • 1970-01-01
    • 2014-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-05
    • 1970-01-01
    相关资源
    最近更新 更多