优化速度代码

时间:2012-05-08 16:06:18

标签: ruby performance optimization primes

这是一个素数筛子,但不是Eratosthenes的筛子。

我觉得它编写得很糟糕,因为我是编程和Ruby的新手。这只是我用Ruby编写的第二个程序,但我想尽可能优化它。问题是我没有完全理解我需要更改以使其更快,除了我知道程序路径/数据结构不理想 - 我只是没有一个概念可以工作到MAKE他们理想

一个理想的答案不一定会说“将X改为Y”,但如果它指向了这种信息的良好资源方向,或者我可以从中获取信息的方法,那将会更有帮助。关于程序不同部分的效率。

count = 0
x = 0
$results = Array.new []
inpt = []

class PrimeFun

  def initialize(x, y)

    array1 = (x..y).to_a
    array1.each do |n|

      if PrimeFun.primelogic(n%60, n) == 1
        array1.delete_if { |p| p % n == 0}
        $results << n
      elsif n == 2 || n == 3 || n == 5
        $results << n

      end
    end
  end


  def self.primelogic(r, n)

    @prime = case r
      when 1, 13, 17, 29, 37, 41, 49, 53
        formulaone(n)
      when 7, 19, 31, 43
        formulatwo(n)
      when 11, 23, 47, 59
        formulathree(n)
      else -1

    end  
  end


  def self.formulaone(n)
   @x = 1
   @y = -1

   until 4*(@x**2) >= n
      @y = -@y if Math.sqrt(n-(4*(@x**2))).floor - Math.sqrt(n-(4*(@x**2))) == 0  
     @x += 1

   end
   @y
  end

  def self.formulatwo(n)
    @x = 1
    @y = -1

    until 3*(@x**2) >= n
      @y = -@y if Math.sqrt(n-(3*(@x**2))).floor - Math.sqrt(n-(3*(@x**2))) == 0
      @x += 1

    end
    @y
  end

  def self.formulathree(n)
    @x = 1
    @y = -1

    until 3*(@x**2) >= n
      @y = -@y if Math.sqrt(((@x**2)+n)/3).floor - Math.sqrt(((@x**2)+n)/3) == 0 && @x > @y
      @x += 1

    end
   @y
  end

end


x = STDIN.gets.to_i

while count < x
  inpt << STDIN.gets.chomp
  count += 1
end

inpt.each do |n|
  a = n.split(" ").map { |a| a.to_i }
  PrimeFun.new(a[0], a[1])
  $results << ""
end

puts $results

2 个答案:

答案 0 :(得分:5)

您应该熟悉Ruby标准库中包含的Benchmark模块,以测量您的方法(不同版本)的运行时间。我没有通过Benchmark自己运行以下代码建议,它们只是一些关于如何提高代码速度和可读性的快速想法 - 随意对它们进行基准测试并报告结果! : - )

分析代码以找到瓶颈也是一个好主意 - 没有必要花费数小时来优化代码中不会对总运行时间贡献很大的部分。查看ruby-prof gem是一个很好的工具来帮助您解决这个问题。


现在快速查看您的代码和一些改进建议。

如果不考虑您使用的实际算法,您的首要任务应该是消除代码重复多次重复计算相同值的倾向。

此外,您似乎正在使用实例变量(@x,@ y等),其中局部变量可以很好地完成工作。更不用说你使用的类方法只能在同一个类的实例方法中调用。您应该将它们转换为实例方法。 (这些不是真正的优化提示,而是关于如何改进Ruby代码的建议。)

以此方法为例:

def self.formulaone(n)
  @x = 1
  @y = -1
  until 4*(@x**2) >= n
    @y = -@y if Math.sqrt(n-(4*(@x**2))).floor - Math.sqrt(n-(4*(@x**2))) == 0  
    @x += 1
  end
  @y
end

在循环中,您正在计算表达式4*(@x**2) 三次次。一个就足够了,所以将结果存储在一个临时局部变量 fsq 中。您还在循环内计算两次相同数字的平方根。再次,将值存储在临时变量 root 中,然后使用它。

def formulaone_b(n)
  x = 1
  y = -1
  until (fsq = 4*(x**2)) >= n
    root = Math.sqrt(n - fsq)
    y = -y if root.floor - root == 0
    x += 1
  end
  y
end

这应该是一个好的开始。

可能不是优化,但您可以通过事先计算x的范围来使代码更清晰,然后使用每个迭代它:

def formulaone_c(n)
  y = -1
  (1..Math.sqrt(n / 4)).each do |x|
    root = Math.sqrt(n - 4*(x**2))
    y = -y if root.floor == root # See below
  end
  y
end

在上面的代码中,我还将比较root.floor - root == 0替换为更简单但等效的比较root.floor == root,删除了一个不必要的减法。

还有一个想法:不是为每次迭代计算n - 4*(x**2),而是通过注意每一步该值减少x * 8 + 4来获得一点点速度,所以使用辅助变量< em> d 更新前一个表达式的值,如下所示:

def formulaone_d(n)
  y = -1
  d = n - 4 # Value of n - 4*(x**2) when x = 1
  (1..Math.sqrt(n / 4)).each do |x|
    root = Math.sqrt(d)
    y = -y if root.floor == root
    d -= x * 8 + 4 # Value of n - 4*(x**2) after x increases
  end
  y
end

答案 1 :(得分:3)

正确性

首先,您的代码不正确:

def self.formulathree(n)
  @x = 1
  @y = -1

  until 3*(@x**2) >= n
    @y = -@y if Math.sqrt(((@x**2)+n)/3).floor - Math.sqrt(((@x**2)+n)/3) == 0 && @x > @y
    @x += 1

  end
  @y
end

@y是否小于@x并不重要,而且始终如此,因为@y = ±1@x = 1@y = -1 < 1

您感兴趣的是表示的数量

n = 3*a^2 - b^2

使用整数a > b > 0。现在,a^2 = (n + b^2)/3,您想要

(n + b^2)/3 > b^2
n + b^2 > 3*b^2
n > 2*b^2

而非n > 3*b^2(此处b代表@x)。例如,

143 = 11* 13 = 3*7^2 - 2^2 = 3*8^2 - 7^2

但是3 * 7 ^ 2 = 147> 143,因此@x = 7将不被考虑,因此formulathree

将143视为素数
179 = 3*9^2 - 8^2

不会被视为素数,尽管它是3*8^2 = 192 > 179

当您在n中输出每个被考虑initialize进行调试时,另一个问题就会变得明显。

array1 = (x..y).to_a
array1.each do |n|

  if PrimeFun.primelogic(n%60, n) == 1
    array1.delete_if { |p| p % n == 0}

array1.each或多或少

for(index = 0; index < array1.length; ++i)

但是当你移除n的倍数时,你也会删除n本身,所以直接移动到索引n之后的元素已经被跳过了。您可以通过仅删除大于n的多个n来解决此问题:

array1.delete_if { |p| p > n && p % n == 0 }

效果

最大的性能问题是算法。如果你打电话给initialize(2,n),对于每个素数,你遍历数组并通过试验除法移除倍数。每个素数除以每个较小的素数(2,3和5除外),以查看它是否应从数组中删除。这是臭名昭着的特纳“筛子”,其复杂性为O((n / log n)^ 2),几乎是二次方的。由于你甚至没有从数组中删除2,3和5的倍数,除非这些倍数具有更大的素因子,复杂性甚至可能稍差。

在选择更好的算法之前,微观优化不值得付出努力。

下一个问题是使用formulaX确定素数。如果您还从阵列中删除了2,3和5的倍数,则甚至不需要测试,每个考虑的数字将是每个试验部门的已知素数。由于您没有,因此将候选人的可分性检查为2,3或5将比primelogic快得多。

primelogic使用也用于Atkin筛的逻辑来确定原始性,但它会单独测试每个数字,因此测试每个数字n是O(√n)。计算平方根比分裂复杂得多,因此比试验师的初步测试需要更长的时间。