【问题标题】:How do I optimize this bit of ruby code to go faster?如何优化这段 ruby​​ 代码以更快地运行?
【发布时间】:2009-04-20 15:37:28
【问题描述】:

这是Sieve of Eratosthenes的实现。

class PrimeGenerator
  def self.get_primes_between( x, y)
    sieve_array = Array.new(y) {|index|
      (index == 0 ? 0 : index+1)
    }

    position_when_we_can_stop_checking = Math.sqrt(y).to_i
    (2..position_when_we_can_stop_checking).each{|factor|
      sieve_array[(factor).. (y-1)].each{|number|
        sieve_array[number-1] = 0 if isMultipleOf(number, factor)
      }
    }

    sieve_array.select{|element| 
      ( (element != 0) && ( (x..y).include? element) )
    }
  end
  def self.isMultipleOf(x, y)
    return (x % y) == 0
  end
end

现在我这样做是为了“提交问题的解决方案,因为你有时间杀死”网站。我选择 ruby​​ 作为我的 impl 语言。但是我被宣布超时。 我做了一些基准测试

require 'benchmark'
Benchmark.bmbm do |x|
  x.report ("get primes") { PrimeGenerator.get_primes_between(10000, 100000)}
  end

ruby 1.9.1p0(2009-01-30 修订版 21907)[i386-mswin32]

L:\Gishu\Ruby>ruby prime_generator.rb
Rehearsal ----------------------------------------------
get primes  33.953000   0.047000  34.000000 ( 34.343750)
------------------------------------ total: 34.000000sec

                 user     system      total        real
get primes  33.735000   0.000000  33.735000 ( 33.843750)

ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]

Rehearsal ----------------------------------------------
get primes  65.922000   0.000000  65.922000 ( 66.110000)
------------------------------------ total: 65.922000sec

                 user     system      total        real
get primes  67.359000   0.016000  67.375000 ( 67.656000)

所以我在 C# 2.0 / VS 2008 中重新做了这件事 --> 722 毫秒

所以现在这促使我思考这是我的实现有问题还是语言之间的性能差异如此之大? (我对 1.9 Ruby VM 感到惊讶......直到我不得不将它与 C# 进行比较:)

更新: 毕竟是我的“put-eratosthenes-to-shame-adaptation” :) 消除不必要的循环迭代是主要的优化。如果有人对细节感兴趣..你可以阅读它here;反正这个问题太长了。

【问题讨论】:

    标签: ruby optimization


    【解决方案1】:

    我将从查看您的内部循环开始。 sieve_array[(factor).. (y-1)] 将在每次执行时创建一个新数组。相反,请尝试用正常的索引循环替换它。

    【讨论】:

    • 这将我的时间缩短了 8 秒。
    【解决方案2】:

    显然,每台计算机都会以不同的方式进行基准测试,但我能够通过使用 each 块删除数组上的循环,并通过导致内部循环检查更少的数字。

    factor=2
    while factor < position_when_we_can_stop_checking 
        number = factor
        while number < y-1
          sieve_array[number-1] = 0 if isMultipleOf(number, factor)
          number = number + factor; # Was incrementing by 1, causing too many checks
        end
      factor = factor +1
    end
    

    【讨论】:

    • 是的。逐级跳跃是另一个节省时间的方法。
    【解决方案3】:

    我不知道它的速度比较,但这是一个相当小且简单的 SoE 实现,对我来说效果很好:

    def sieve_to(n)
      s = (0..n).to_a
      s[0]=s[1]=nil
      s.each do |p|
        next unless p
        break if p * p > n
        (p*p).step(n, p) { |m| s[m] = nil }
      end
      s.compact
    end
    

    可能还有一些进一步的小加速,但我认为这非常好。

    它们并不完全等价,因此您的 10_000 到 1_000_000 将等同于

    sieve_to(1_000_000) - sieve_to(9_999)
    

    或近似的东西。

    无论如何,在 WinXP 上,使用 Ruby 1.8.6(和相当强大的 Xeon CPU)我明白了:

    require 'benchmark'
    Benchmark.bm(30) do |r|
      r.report("Mike") { a = sieve_to(10_000) - sieve_to(1_000) } 
      r.report("Gishu") { a = PrimeGenerator.get_primes_between( 1_000, 10_000) }
    end
    

    给了

                                        user     system      total        real
    Mike                            0.016000   0.000000   0.016000 (  0.016000)
    Gishu                           1.641000   0.000000   1.641000 (  1.672000)
    

    (我停止运行一百万个案例,因为我厌倦了等待)。

    所以我会说这是你的算法。 ;-)

    C# 解决方案几乎可以保证速度快几个数量级。

    【讨论】:

    • 谢谢。了解你的 sn-p 很有趣……再次感受到成为程序员的乐趣。它的简洁性也符合 Ruby 方式
    • 另外..您选择 sieve_to(y)-sieve_to(x-1) 来获取结果而不是 sieve_to(y).select{|prime| 的任何原因(素数 >= x)} ?
    • 谢谢。我一直在玩 Project Euler:有几个问题需要大量的素数列表,所以我已经尽可能地调整了。嗯。 sieve_to() - sieve_to() 确实是当我注意到介于 (x,y) 之间时进入我脑海的第一件事。对于大 x,您的可能会更好。或者,更改 sieve_to 以从 x 向上压缩数组。 :)
    【解决方案4】:

    埃拉托色尼筛法可以很好地作为寻找素数的说明性方法,但我会稍微不同地实现它。本质是您不必检查已知素数的倍数的数字。现在,除了使用数组来存储这些信息,您还可以创建一个包含所有连续素数的列表,直到您要检查的数字的平方根,然后通过素数列表来检查素数就足够了。

    如果你想一下,这和你在图像上所做的一样,但是以一种更“虚拟”的方式。

    编辑:快速破解我的意思(不是从网络复制的;)):

      public class Sieve {
        private readonly List<int> primes = new List<int>();
        private int maxProcessed;
    
        public Sieve() {
          primes.Add(maxProcessed = 2); // one could add more to speed things up a little, but one is required
        }
    
        public bool IsPrime(int i) {
          // first check if we can compare against known primes
          if (i <= primes[primes.Count-1]) {
            return primes.BinarySearch(i) >= 0;
          }
          // if not, make sure that we got all primes up to the square of i
          int maxFactor = (int)Math.Sqrt(i);
          while (maxProcessed < maxFactor) {
            maxProcessed++;
            bool isPrime = true;
            for (int primeIndex = 0; primeIndex < primes.Count; primeIndex++) {
              int prime = primes[primeIndex];
              if (maxProcessed % prime == 0) {
                isPrime = false;
                break;
              }
            }
            if (isPrime) {
              primes.Add(maxProcessed);
            }
          }
          // now apply the sieve to the number to check
          foreach (int prime in primes) {
            if (i % prime == 0) {
              return false;
            }
            if (prime > maxFactor) {
              break;
            }
          }
          return true;
        }
      }
    

    在我的慢速机器上使用大约 67 毫秒.... 测试应用:

    class Program {
        static void Main(string[] args) {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Sieve sieve = new Sieve();
            for (int i = 10000; i <= 100000; i++) {
                sieve.IsPrime(i);
            }
            sw.Stop();
            Debug.WriteLine(sw.ElapsedMilliseconds);
        }
    }
    

    【讨论】:

      【解决方案5】:

      用 ruby​​-prof 对其进行基准测试。它可以吐出诸如 kcachegrind 之类的工具可以查看的内容,以查看您的代码在哪里运行缓慢。

      然后,一旦你使 ruby​​ 变得快速,使用 RubyInline 为你优化方法。

      【讨论】:

        【解决方案6】:

        我还注意到,根据我的经验,Ruby 在 Windows 系统上比在 *nix 上慢得多。当然,我不确定你的处理器速度是多少,但是在我的 Ubuntu 机器上用 Ruby 1.9 运行这段代码大约需要 10 秒,而 1.8.6 需要 30 秒。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-06-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-05
          • 2020-01-13
          • 1970-01-01
          相关资源
          最近更新 更多