为什么Elixir在Ruby中最慢并且在解决规划上面走得很慢?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (40)

我正在研究多语言项目欧拉问题,以了解语言的生产力和速度。我被要求找到能被1到20的所有数字均匀分割的最小正数。

我用多种语言实现了解决方案。这里是统计数据:

  1. go 1.4.2:0.58s
  2. Ruby 2.2 MRI:6.7s
  3. Elixir 1.0.5(我的第一个算法):57s
  4. Elixir 1.0.5(我的第一个MIX_ENV = prod前缀的算法):7.4s
  5. Elixir 1.0.5(Roman's Go等效算法):0.7s
  6. Elixir 1.0.5(Roman的Ruby等价算法):1.8s

为什么Elixir的表现如此之慢?我尝试在所有语言中使用相同的优化。

能做些什么来改善Elixir的表现吗?

在Go中:

func problem005() int {
  i := 20
outer:
  for {
    for j := 20; j > 0; j-- {
      if i%j != 0 {
        i = i + 20
        continue outer
      }
    }
    return i
  }
  panic("Should have found a solution by now")
}

在Ruby中:

def self.problem005
  divisors = (1..20).to_a.reverse

  number = 20 # we iterate over multiples of 20

  until divisors.all? { |divisor| number % divisor == 0 } do
    number += 20
  end

  return number
end

在Elixir中:

def problem005 do 
  divisible_all? = fn num ->
    Enum.all?((20..2), &(rem(num, &1) == 0))
  end

  Stream.iterate(20, &(&1 + 20))
  |> Stream.filter(divisible_all?)
  |> Enum.fetch! 0
end
提问于
用户回答回答于

我的第一个回答,关于实现你在Ruby中实现的相同算法。现在,这里是Go中的算法的Elixir版本:

defmodule Euler do
  @max_divider 20
  def problem005 do 
    problem005(20, @max_divider)
  end

  defp problem005(number, divider) when divider > 1 do
    if rem(number, divider) != 0 do
      problem005(number+20, @max_divider)
    else
      problem005(number, divider-1)
    end
  end
  defp problem005(number, _), do: number
end

我的笔记本电脑大约需要0.73秒。这些算法是不同的,所以我相信Ruby在这里也可以发挥更好的作用。

我猜,这里的一般规则是:如果你在Elixir中的代码具有来自Go代码或更好的80%的性能,那没关系。在其他情况下,您的Elixir代码中很可能有算法错误。

关于Ruby的更新:

作为奖励,这里是Ruby中的Go等效算法:

def problem_005
  divisor = max_divisor = 20
  number = 20 # we iterate over multiples of 20

  while divisor > 1 do
    if number % divisor == 0 
      divisor -= 1
    else
      number += 20
      divisor = max_divisor
    end
  end

  number
end

它的执行速度提高了4.5倍,所以我猜它可能会在计算机上显示〜1.5秒。

用户回答回答于

试试这个版本:

defmodule Euler do
  def problem005 do 
    problem005(20)
  end

  @divisors (20..2) |> Enum.to_list 
  defp problem005(number) do
    if Enum.all?(@divisors, &(rem(number, &1) == 0)) do
      number
    else
      problem005(number+20)
    end
  end
end

我的笔记本电脑需要约1.4秒。你的解决方案的主要问题是在每次迭代时将范围转换为列表。这是一个巨大的开销。另外,这里不需要创建“无限”的流。你在其他语言中没有这样做。

扫码关注云+社区