质量算法上的Scala性能

Gia*_*cci 6 performance scala c++11

我对Scala很新,所以为了开始编写一些代码,我已经实现了这个简单的程序:

package org.primes.sim

object Primes {

  def is_prime(a: Int): Boolean = {
    val l = Stream.range(3, a, 2) filter { e => a % e == 0}
    l.size == 0
  }

  def gen_primes(m: Int) = 
    2 #:: Stream.from(3, 2) filter { e => is_prime(e) } take m

  def primes(m : Int) = {
      gen_primes(m) foreach println      
  }

  def main(args: Array[String]) {
    if (args.size == 0)
      primes(10)
    else
      primes(args(0).toInt)
  }

}
Run Code Online (Sandbox Code Playgroud)

它从2开始生成n个素数.然后我使用Eric Nibler的range-v3库在C++ 11中实现了相同的算法.这是代码:

#include <iostream>
#include <vector>
#include <string>
#include <range/v3/all.hpp>

using namespace std;
using namespace ranges;

inline bool is_even(unsigned int n) { return n % 2 == 0; }

inline bool is_prime(unsigned int n)
{
    if (n == 2)
        return true;
    else if (n == 1 || is_even(n))
        return false;
    else
        return ranges::any_of(
                view::iota(3, n) | view::remove_if(is_even),
                [n](unsigned int e) { return n % e == 0; }
            ) == false;
}

void primes(unsigned int n)
{
    auto rng = view::ints(2) | view::filter(is_prime);
    ranges::for_each(view::take(rng, n), [](unsigned int e){ cout << e << '\n'; });
}

int main(int argc, char* argv[])
{
    if (argc == 1)
        primes(100);
    else if (argc > 1)
    {
        primes(std::stoi(argv[1]));
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,代码看起来非常相似,但性能却截然不同:

对于n = 5000,C++在0,265s完成,而Scala在24,314s完成!因此,从这个测试来看,Scala似乎比C++ 11慢100倍.

Scala代码有哪些问题?你能给我一些提示,以便更好地使用scalac吗?

注意:我使用gcc 4.9.2和-O3 opt编译了C++代码.

谢谢

Kol*_*mar 23

主要的速度问题在于您的is_prime实施.

首先,您过滤一个Stream来查找所有除数,然后检查是否有无(l.size == 0).但是false一旦找到第一个除数,它就会更快返回:

def is_prime(a: Int): Boolean =
  Stream.range(3, a, 2).find(a % _ == 0).isEmpty
Run Code Online (Sandbox Code Playgroud)

这使我的机器上的运行时间从22秒减少到5primes(5000).

第二个问题Stream本身.Scala Streams很慢,使用它们进行简单的数字计算是一个巨大的过度杀伤力.更换StreamRange运行时减少进一步对于1,2秒:

def is_prime(a: Int): Boolean =
  3.until(a, 2).find(a % _ == 0).isEmpty
Run Code Online (Sandbox Code Playgroud)

这很不错:比C++慢5倍.通常,我会停在这里,但如果我们删除高阶函数,可以减少更多的运行时间find.

虽然美观和功能,但find也会产生一些开销.循环实现(基本上替换findforeach)进一步将运行时间减少到0.45秒,这比C++慢了不到2倍(已经是JVM开销的顺序):

def is_prime(a: Int): Boolean = {
  for (e <- 3.until(a, 2)) if (a % e == 0) return false
  true
}
Run Code Online (Sandbox Code Playgroud)

还有另一个Stream gen_primes,所以用它做一些事情可能会更多地改善运行时间,但在我看来,没有必要.在性能改进的那一点上,我认为最好切换到其他一些生成素数的算法:例如,仅使用素数而不是所有奇数,来寻找除数,或者使用Eratosthenes的Sieve.

总而言之,Scala中的函数抽象是使用堆上的实际对象实现的,这些对象有一些开销,而JIT编译器无法修复所有内容.但C++的卖点是零成本抽象:在编译过程中template,所有可能的东西都会被扩展,constexpr并由编译器进一步优化.