首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么Elixir在解决Euler #5项目中是Ruby和Go中最慢的?

为什么Elixir在解决Euler #5项目中是Ruby和Go中最慢的?
EN

Stack Overflow用户
提问于 2015-09-07 20:38:17
回答 5查看 7.9K关注 0票数 20

更新:灵丹妙药并不慢,我的算法是慢。我的算法甚至不是苹果和苹果的比较。关于Ruby和Go的等同算法,请参阅下面的Roman回答。多亏了José,我的缓慢算法可以通过在MIX_ENV=prod前面加上前缀来显著加快速度。我已经更新了问题中的统计数据。

原始问题:我正在研究多语言的项目欧拉问题,只是想看看语言的效率和速度有多快。在problem #5中,我们被要求找到能被从1到20的所有数字整除的最小正数。

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

  1. Go 1.4.2 : 0.58s
  2. ruby2.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 (罗曼Go等效算法):0.7s
  6. Elixir 1.0.5 (罗曼红宝石等效算法):1.8s

为什么Elixir的表现这么慢?我尝试在所有语言中使用相同的优化。警告:我是FP和Elixir新手。

我能做些什么来提高Elixir的性能吗?如果您在找出更好的解决方案时使用了分析工具,可以在回复中包含它们吗?

在Go中:

代码语言:javascript
复制
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中:

代码语言:javascript
复制
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中:

代码语言:javascript
复制
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
EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2015-09-08 01:15:40

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

代码语言:javascript
复制
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等效算法:

代码语言:javascript
复制
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秒。

票数 12
EN

Stack Overflow用户

发布于 2015-09-08 00:45:25

试试这个版本:

代码语言:javascript
复制
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秒。您的解决方案的主要问题是在每次迭代时将范围转换为列表。这是一个巨大的开销。此外,这里没有必要创建“无限”流。在其他语言中,你没有做过类似的事情。

票数 5
EN

Stack Overflow用户

发布于 2015-09-09 00:28:54

你的代码可能没问题,但是数学让我牙疼。有一个简单的递归解决方案,它很好地匹配了长生不老的做事方式。它还展示了如何在灵丹妙药中只做递归,而不用担心递归在其他语言中导致的性能问题。

代码语言:javascript
复制
defmodule Euler_5 do
@moduledoc """
Solve the smallest number divisible by 1..X using Greatest Common Divisor.
"""

  def smallest(1), do: 1
  def smallest(2), do: 2

  def smallest(n) when n > 2 do
    next = smallest(n-1)
    case rem(next, n) do
      0 -> next
      _ -> next * div(n,gcd(next,n))
    end
  end

  def gcd(1,_n), do: 1

  def gcd(2,n) do
    case rem(n,2) do
      0 -> 2
      _ -> 1
    end
  end

  def gcd( m, n) do
    mod = rem(m,n)
    case mod do
      0 -> n
      _ -> gcd(n,mod)
    end
  end

end

不管怎么说,这在我的电脑上需要8微秒

代码语言:javascript
复制
iex> :timer.tc(Euler_5, :smallest, [20])
{8, 232792560}

与其他语言相比并不公平,因为它不包括加载VM和执行I/O所需的时间。

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

https://stackoverflow.com/questions/32438978

复制
相关文章

相似问题

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