Java 我能做些什么来加速这个代码?

Java 我能做些什么来加速这个代码?,java,scala,clojure,Java,Scala,Clojure,我正在努力学习Java、Scala和Clojure 我正在用三种语言处理Euler项目的问题。下面列出了问题#5()的代码以及到目前为止前五个问题的运行时间(以秒为单位)。对于问题5,Java和Clojure版本比Scala版本慢得多,这让我感到震惊。它们运行在同一台机器、同一个jvm上,并且经过几次试验,结果是一致的。我能做些什么来加速这两个版本(尤其是Clojure版本)?为什么Scala版本要快得多 运行时间(秒) 问题5的Java版本 问题5的Clojure Verson 编辑 有人建议

我正在努力学习Java、Scala和Clojure

我正在用三种语言处理Euler项目的问题。下面列出了问题#5()的代码以及到目前为止前五个问题的运行时间(以秒为单位)。对于问题5,Java和Clojure版本比Scala版本慢得多,这让我感到震惊。它们运行在同一台机器、同一个jvm上,并且经过几次试验,结果是一致的。我能做些什么来加速这两个版本(尤其是Clojure版本)?为什么Scala版本要快得多

运行时间(秒) 问题5的Java版本 问题5的Clojure Verson 编辑 有人建议我把各种答案汇编成自己的答案。然而,我想在应该得到表扬的地方给予表扬(我自己真的没有回答这个问题)

至于第一个问题,使用更好的算法可以加快所有三个版本的速度。具体来说,创建数字1-20(2^4、3^2、5^1、7^1、11^1、13^1、17^1、19^1)的最大公因数列表,并将它们相乘

更有趣的方面是使用基本相同的算法来理解这三种语言之间的差异。在某些情况下,像这样的暴力算法可能会有所帮助。那么,为什么会出现性能差异呢

对于Java,一个建议是将ArrayList更改为int的原始数组。这确实减少了运行时间,减少了大约0.5-1秒(我今天早上刚刚运行了它,它将运行时间从4.386秒减少到了3.577秒。这减少了一点,但没有人能想出一种方法将其缩短到半秒以下(类似于Scala版本)。考虑到这三种方法都可以编译成java字节码,这是令人惊讶的。@didierc建议使用不可变迭代器;我测试了这个建议,它将运行时间增加到了5秒多一点

对于Clojure,@mikera和@Webb给出了一些加快速度的建议。他们建议使用loop/recur进行两个循环变量的快速迭代,使用未经检查的数学进行稍快的数学运算(因为我们知道这里没有溢出的危险),使用基本长度,而不是装箱数字,并避免像each?这样的高阶函数

运行@mikera的代码,我最终的运行时间为2.453秒,没有scala代码好,但比我的原始版本和Java版本要好得多:

(set! *unchecked-math* true)

(defn euler5 
  []
  (loop [n 1 
         d 2]
    (if (== 0 (unchecked-remainder-int n d))
      (if (>= d 20) n (recur n (inc d)))
      (recur (inc n) 2))))

(defn is-divisible-by-all?
  [number divisors]
  (= 0 (reduce + (map #(mod 2 %) divisors))))
对于Scala,@didierc声明范围对象1到20实际上不是一个对象列表,而是一个对象。非常酷。因此,Scala中的性能差异在于,我们迭代单个对象,而不是整数列表/数组1-20

事实上,如果我将scala方法中的helper函数从一个范围对象更改为一个列表(见下文),那么scala版本的运行时间将从0.302秒增加到226.59秒

private def isDivisibleByAll2(n: Int, top: Int): Boolean = {
    def divisors: List[Int] = List(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
    divisors.forall(n % _ == 0)
  }
因此,@didierc似乎正确地识别了scala在这个实例中的优势。了解这种类型的对象如何在java和clojure中实现会很有趣

@didierc建议通过创建ImmutableRange类来改进代码,如下所示:

import java.util.Iterator;
import java.lang.Iterable;

public class ImmutableRange implements Iterable<Integer> {

  class ImmutableRangeIterator implements Iterator<Integer> {
    private int counter, end, step;

    public ImmutableRangeIterator(int start_, int end_, int step_) {
      end = end_;
      step = step_;
      counter = start_;
    }

    public boolean hasNext(){
      if (step>0) return counter  <= end;
      else return counter >= end;
    }

    public Integer next(){
      int r = counter;
      counter+=step;
      return r;
    }

    public void remove(){
      throw new UnsupportedOperationException();
    }

  }

  private int start, end, step;

  public ImmutableRange(int start_, int end_, int step_){
    // fix-me: properly check for parameters consistency
    start = start_;
    end = end_;
    step = step_;
  }

  public Iterator<Integer> iterator(){
    return new ImmutableRangeIterator(start,end,step);
  }
}
import java.util.Iterator;
导入java.lang.Iterable;
公共类ImmutableRange实现了Iterable{
类ImmutableRangeIterator实现迭代器{
专用int计数器,结束,步骤;
公共ImmutableRangeIterator(int开始、int结束、int步骤){
结束=结束;
步骤=步骤;
计数器=启动;
}
公共布尔hasNext(){
如果(步骤>0)返回计数器=结束;
}
公共整数next(){
int r=计数器;
计数器+=步进;
返回r;
}
公共空间删除(){
抛出新的UnsupportedOperationException();
}
}
私有int开始、结束、步骤;
公共不可变范围(int开始、int结束、int步骤){
//修复我:正确检查参数的一致性
开始=开始;
结束=结束;
步骤=步骤;
}
公共迭代器迭代器(){
返回新的ImmutableRangeIterator(开始、结束、步骤);
}
}
没有提高运行时间。java版本在我的机器上以5.097秒的速度运行。因此,最后,我们对Scala版本性能更好的原因有了令人满意的答案,我们了解如何提高Clojure版本的性能,但缺少的是了解如何在J中实现Scala的不可变范围对象艾娃

最后的想法 正如一些人所评论的,提高此代码运行时间的最有效方法是使用更好的算法。例如,以下java代码使用and在不到1毫秒的时间内计算答案:

/**
*最小倍数
*
*2520是可以被每个数字除的最小数字
*从1到10,没有余数。最小的正数是多少
*可以被1到20的所有数字整除吗?
*
*用户:Alexandros Bantis
*日期:2013年1月29日
*时间:下午7:06
*/
公共类问题005{
最终私有静态整数被划掉=0;
最终私有静态整数未被划掉=1;
私有静态int intPow(int基,int指数){
int值=1;
对于(int i=0;i<指数;i++)
值*=基数;
返回值;
}
/**
*primes可使用试用法计算n以下的所有素数
*除法算法
*
*@param n指定的素数应在2…n范围内
*@return int[]所有素因子的筛选
*(0=划掉,1=未划掉)
*/
私有静态int[]primesTo(int n){
整数上限=(整数)数学sqrt(n*1.0)+1;
int[]筛=新的int[n+1];
//设置默认值

对于(inti=2;i首先,如果一个数字可以被,例如,4整除,那么它也可以被2整除(4的因子之一)

所以,从1到20,你只需要检查一些数字,
(defn smallest-multiple-of-1-to-n
  [n]
  (loop [divisors (range 2 (inc n))
        i n]
    (if (every? #(= 0 (mod i %)) divisors)
      i
      (recur divisors (inc i)))))
(set! *unchecked-math* true)

(defn euler5 
  []
  (loop [n 1 
         d 2]
    (if (== 0 (unchecked-remainder-int n d))
      (if (>= d 20) n (recur n (inc d)))
      (recur (inc n) 2))))

(defn is-divisible-by-all?
  [number divisors]
  (= 0 (reduce + (map #(mod 2 %) divisors))))
private def isDivisibleByAll2(n: Int, top: Int): Boolean = {
    def divisors: List[Int] = List(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
    divisors.forall(n % _ == 0)
  }
import java.util.Iterator;
import java.lang.Iterable;

public class ImmutableRange implements Iterable<Integer> {

  class ImmutableRangeIterator implements Iterator<Integer> {
    private int counter, end, step;

    public ImmutableRangeIterator(int start_, int end_, int step_) {
      end = end_;
      step = step_;
      counter = start_;
    }

    public boolean hasNext(){
      if (step>0) return counter  <= end;
      else return counter >= end;
    }

    public Integer next(){
      int r = counter;
      counter+=step;
      return r;
    }

    public void remove(){
      throw new UnsupportedOperationException();
    }

  }

  private int start, end, step;

  public ImmutableRange(int start_, int end_, int step_){
    // fix-me: properly check for parameters consistency
    start = start_;
    end = end_;
    step = step_;
  }

  public Iterator<Integer> iterator(){
    return new ImmutableRangeIterator(start,end,step);
  }
}
/**
 * Smallest Multiple
 *
 * 2520 is the smallest number that can be divided by each of the numbers 
 * from 1 to 10 without any remainder. What is the smallest positive number
 * that is evenly divisible by all of the numbers from 1 to 20?
 *
 * User: Alexandros Bantis
 * Date: 1/29/13
 * Time: 7:06 PM
 */
public class Problem005 {

  final private static int CROSSED_OUT = 0;
  final private static int NOT_CROSSED_OUT = 1;

  private static int intPow(int base, int exponent) {
    int value = 1;
    for (int i = 0; i < exponent; i++)
      value *= base;
    return value;
  }

  /**
   * primesTo computes all primes numbers up to n using trial by 
   * division algorithm
   *
   * @param n designates primes should be in the range 2 ... n
   * @return int[] a sieve of all prime factors 
   *              (0=CROSSED_OUT, 1=NOT_CROSSED_OUT)
   */
  private static int[] primesTo(int n) {
    int ceiling = (int) Math.sqrt(n * 1.0) + 1;
    int[] sieve = new int[n+1];

    // set default values
    for (int i = 2; i <= n; i++)
      sieve[i] = NOT_CROSSED_OUT;

    // cross out sieve values
    for (int i = 2; i <= ceiling; i++)
      for (int j = 2; i*j <= n; j++)
        sieve[i*j] = CROSSED_OUT;
    return sieve;
  }


  /**
   * getPrimeExp computes a prime factorization of n
   *
   * @param n the number subject to prime factorization
   * @return int[] an array of exponents for prime factors of n
   *               thus 8 => (0^0, 1^0, 2^3, 3^0, 4^0, 5^0, 6^0, 7^0, 8^0)
   */
  public static int[] getPrimeExp(int n) {
    int[] factor = primesTo(n);
    int[] primePowAll = new int[n+1];

    // set prime_factor_exponent for all factor/exponent pairs
    for (int i = 2; i <= n; i++) {
      if (factor[i] != CROSSED_OUT) {
        while (true) {
          if (n % i == 0) {
          n /= i;
          primePowAll[i] += 1;
          } else {
            break;
          }
        }
      }
    }

    return primePowAll;
  }

  /**
   * findSmallestMultiple computes the smallest number evenly divisible 
   * by all numbers 1 to n
   *
   * @param n the top of the range
   * @return int evenly divisible by all numbers 1 to n
   */
  public static int findSmallestMultiple(int n) {
    int[] gcfAll = new int[n+1];

    // populate greatest common factor arrays
    int[] gcfThis = null;
    for (int i = 2; i <= n; i++) {
      gcfThis = getPrimeExp(i);
      for (int j = 2; j <= i; j++) {
        if (gcfThis[j] > 0 && gcfThis[j] > gcfAll[j]) {
          gcfAll[j] = gcfThis[j];
        }
      }
    }

    // multiply out gcf arrays
    int value = 1;
    for (int i = 2; i <= n; i++) {
      if (gcfAll[i] > 0)
        value *= intPow(i, gcfAll[i]);
    }
    return value;
  }
}
def smallestMultipe(n: Int): Int = {
  @scala.annotation.tailrec
  def gcd(x: Int, y: Int): Int = if(x == 0) y else gcd(y%x, x)
  (1 to n).foldLeft(1){ (x,y) => x/gcd(x,y)*y }
}
(1 until 1000) filter (n => n%3 == 0 || n%5 == 0) sum
(1 until 1000).foldLeft(0){ (r,n) => if(n%3==0||n%5==0) r+n else r }
(defn smallest-multiple-of-1-to-n
 [n]
  (let [divisors (range 2 (inc n))]
   (loop [i n]
     (if (loop [d 2]
        (cond (> d n) true
              (not= 0 (mod i d)) false
              :else (recur (inc d))))
     i
     (recur (inc i))))))
private static int[] divisors;

private static void initializeDivisors(int ceiling) {
    divisors = new int[ceiling];

    for (Integer i = 1; i <= ceiling; i++)
        divisors[i - 1] = i;
}
user=> (time (smallest-multiple-of-1-to-n 20))
"Elapsed time: 561420.259 msecs"
232792560
(defn smallest-multiple-of-1-to-n [n]
  (loop [c (int n)] 
    (if 
      (loop [x (int 2)]
        (cond (pos? (unchecked-remainder-int c x)) false
              (>= x n) true
              :else (recur (inc x))))
      c (recur (inc c)))))

user=> (time (smallest-multiple-of-1-to-n 20))
"Elapsed time: 171921.80347 msecs"
232792560
(defn gcd [a b]
  (if (zero? b) a (recur b (mod a b))))

(defn lcm 
  ([a b] (* b (quot a (gcd a b))))
  ([a b & r] (reduce lcm (lcm a b) r)))

user=> (time (apply lcm (range 2 21)))
"Elapsed time: 0.268749 msecs"
232792560
;Euler 5 helper
(defn divisible-by-all [n]
  (let [divisors [19 17 13 11 20 18 16 15 14 12]
        maxidx (dec (count divisors))]
  (loop [idx 0]
     (let [result (zero? (mod n (nth divisors idx)))]
       (cond
          (and (= idx maxidx) (true? result)) true
          (false? result) false
          :else (recur (inc idx)))))))

;Euler 5 solution
(defn min-divisible-by-one-to-twenty []
  (loop[ x 38 ] ;this one can be set MUCH MUCH higher...
    (let [result (divisible-by-all x)]
       (if (true? result) x (recur (+ x 38))))))

user=>(time (min-divisible-by-one-to-twenty))
"Elapsed time: 410.06 msecs"
(set! *unchecked-math* true)

(defn euler5 []
  (loop [n 1 
         d 2)]
    (if (== 0 (unchecked-remainder-int n d))
      (if (>= d 20) n (recur n (inc d)))
      (recur (inc n) 2))))

(time (euler5))
=> "Elapsed time: 2438.761237 msecs"
public class Euler5 {
  public static void main(String[] args) {
    int test = 2520;
    int i;
    again: while (true) {
      test++;
      for (i = 20; i >1; i--) {
        if (test % i != 0)
          continue again;
      }
      break;
    }
    System.out.println(test);
  }
}