Java:如何优化大数组的和

Java:如何优化大数组的和,java,optimization,Java,Optimization,我试着解决一个问题。而且我得到了超期的judjment。唯一耗时的操作是计算大数组的和。所以我试着优化它,但没有结果 我想要什么:优化下一个函数: //array could be Integer.MAX_VALUE length private long canocicalSum(int[] array) { int sum = 0; for (int i = 0; i < array.length; i++) sum += array[i];

我试着解决一个问题。而且我得到了超期的judjment。唯一耗时的操作是计算大数组的和。所以我试着优化它,但没有结果

我想要什么:优化下一个函数:

//array could be Integer.MAX_VALUE length
private long canocicalSum(int[] array) { 
    int sum = 0;
    for (int i = 0; i < array.length; i++)
        sum += array[i];
    return sum;
}
问题:但似乎
canonicalSum
optimizedSum
快十倍。以下是我的测试:

@Test
public void sum_comparison() {
    final int ARRAY_SIZE = 100000000;
    final int STEP = 1000;
    int[] array = genRandomArray(ARRAY_SIZE);

    System.out.println("Start canonical Sum");
    long beg1 = System.nanoTime();
    long sum1 = canocicalSum(array);
    long end1 = System.nanoTime();
    long time1 = end1 - beg1;
    System.out.println("canon:" + TimeUnit.MILLISECONDS.convert(time1, TimeUnit.NANOSECONDS) + "milliseconds");

    System.out.println("Start optimizedSum");
    long beg2 = System.nanoTime();
    long sum2 = optimizedSum(array, STEP);
    long end2 = System.nanoTime();
    long time2 = end2 - beg2;
    System.out.println("custom:" + TimeUnit.MILLISECONDS.convert(time2, TimeUnit.NANOSECONDS) + "milliseconds");

    assertEquals(sum1, sum2);
    assertTrue(time2 <= time1);
}

private int[] genRandomArray(int size) {
    int[] array = new int[size];
    Random random = new Random();
    for (int i = 0; i < array.length; i++) {
        array[i] = random.nextInt();
    }
    return array;
}
@测试
公开作废金额(比较){
最终整数数组大小=100000000;
最后的整数步=1000;
int[]数组=数组(数组大小);
System.out.println(“开始标准和”);
long beg1=System.nanoTime();
长sum1=canocicalSum(阵列);
long-end1=System.nanoTime();
长时间1=end1-beg1;
System.out.println(“佳能:+时间单位.毫秒.转换(time1,TimeUnit.纳秒)+”毫秒”);
System.out.println(“启动优化DSUM”);
long beg2=System.nanoTime();
长sum2=优化的DSUM(数组,步长);
long-end2=System.nanoTime();
长时间2=end2-beg2;
System.out.println(“自定义:“+TimeUnit.millizes.convert(time2,TimeUnit.NANOSECONDS)+”毫秒”);
资产质量(sum1、sum2);

assertTrue(time2如果您想添加N个数字,那么运行时是
O(N)
。因此在这方面,您的
canonicalSum
无法“优化”。
减少运行时间的方法是使求和并行。也就是说,将数组分成若干部分,并将其传递给不同的线程,最后求和每个线程返回的结果。
更新:这意味着多核系统,但是有一个JavaAPI来获取核的数量

问题1[主要]:是否可以优化canonicalSum

是的,但我不知道是什么因素

您可以做的一些事情是:

  • 使用Java 8中引入的并行管道。处理器具有对2个数组(及更多数组)进行并行求和的指令。当您使用“+”(并行加法)或“+”对两个向量求和时,可以在
    Octave
    中观察到这一点。这比使用循环快得多

  • 使用多线程。您可以使用分治算法。可能如下所示:

    • 将数组划分为2个或多个
    • 继续递归分割,直到得到一个线程大小可管理的数组
    • 开始计算具有单独线程的子数组(分割数组)的和
    • 最后,将为所有子数组生成的和(从所有线程)相加,以生成最终结果
  • 也许展开循环也会有所帮助。我所说的循环展开是指通过在循环中手动执行更多操作来减少循环必须执行的步骤

例如:

以及使用示例:

public static double varianceForkJoin(double[] population){
   final ForkJoinPool forkJoinPool = new ForkJoinPool();
   double total = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {
     @Override
     public double computeSequentially(double[] numbers, int start, int end) {
       double total = 0;
       for (int i = start; i < end; i++) {
         total += numbers[i];
       }
       return total;
     }
  }));
  final double average = total / population.length;
  double variance = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {
    @Override
    public double computeSequentially(double[] numbers, int start, int end) {
      double variance = 0;
      for (int i = start; i < end; i++) {
        variance += (numbers[i] - average) * (numbers[i] - average);
      }
      return variance;
    }
 }));
 return variance / population.length;
}
公共静态双变量rkjoin(双[]填充){
final ForkJoinPool ForkJoinPool=新的ForkJoinPool();
double total=forkJoinPool.invoke(新的ForkJoinCalculator(填充,新的顺序计算器)(){
@凌驾
公共双精度计算顺序(双[]数字,整数开始,整数结束){
双倍合计=0;
for(int i=start;i
从Java 9开始,此操作的矢量化已经完成,但是,基于衡量代码及其编译的全部成本的基准测试。根据处理器的不同,这将导致一个相对有趣的结果,即如果您进入简化循环,您可以触发自动矢量化并获得更快的结果!因此,最快的代码,目前,假设数字足够小,不会溢出,则为:

public int sum(int[] data) {
    int value = 0;
    for (int i = 0; i < data.length; ++i) {
        value += 2 * data[i];
    }
    return value / 2;
}
public int sum(int[]数据){
int值=0;
对于(int i=0;i

这并不是一个建议!这更多的是为了说明Java代码的速度取决于JIT、它的权衡以及它在任何给定版本中的缺陷/特性。编写可爱的代码来优化这样的问题充其量是徒劳的,并且会延长您编写的代码的有效期。例如,如果您手动打开一个loop为了优化旧版本的Java,您的代码在Java 8或9中会慢得多,因为这一决定将完全禁用自动矢量化。您最好真的需要这种性能来实现这一点。

我不明白为什么您希望“优化”求和以获得更好的性能。它更加复杂,到处分支,包含堆分配,并且不会做更少的工作。在某个点上,它也会将大量的数字加在一起。只要您保持在
int
范围内,这就完全无关了。添加0和1的成本与添加999999和12329834的成本一样高。这是possible,使用并行管道(我认为它们也是用Java实现的),使用多线程和展开循环。也可以切换到一段时间而不是一个for。32位int始终是32位,无论它有多少个0或1。所花费的时间将在内存访问中,而不是求和。添加can比内存访问快10+。@VolodymyrBakhmatiuk
(i&1)==0
至少快10倍。通过循环展开,通常可以消除这种情况。由于上下文切换和线程固有的开销,鉴于循环的简单性,多线程有很好的机会使代码慢得多。如果它只有一个核心,那么它保证会使代码更慢
for (int x = 0; x < 100; x+=5)
{
    delete(x);
    delete(x+1);
    delete(x+2);
    delete(x+3);
    delete(x+4);
}
public class ForkJoinCalculator extends RecursiveTask<Double> {

   public static final long THRESHOLD = 1_000_000;

   private final SequentialCalculator sequentialCalculator;
   private final double[] numbers;
   private final int start;
   private final int end;

   public ForkJoinCalculator(double[] numbers, SequentialCalculator sequentialCalculator) {
     this(numbers, 0, numbers.length, sequentialCalculator);
   }

   private ForkJoinCalculator(double[] numbers, int start, int end, SequentialCalculator sequentialCalculator) {
     this.numbers = numbers;
     this.start = start;
     this.end = end;
     this.sequentialCalculator = sequentialCalculator;
   }

   @Override
   protected Double compute() {
     int length = end - start;
     if (length <= THRESHOLD) {
         return sequentialCalculator.computeSequentially(numbers, start, end);
     }
     ForkJoinCalculator leftTask = new ForkJoinCalculator(numbers, start, start + length/2, sequentialCalculator);
     leftTask.fork();
     ForkJoinCalculator rightTask = new ForkJoinCalculator(numbers, start + length/2, end, sequentialCalculator);
     Double rightResult = rightTask.compute();
     Double leftResult = leftTask.join();
     return leftResult + rightResult;
  }
}
public interface SequentialCalculator {
  double computeSequentially(double[] numbers, int start, int end);
}
public static double varianceForkJoin(double[] population){
   final ForkJoinPool forkJoinPool = new ForkJoinPool();
   double total = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {
     @Override
     public double computeSequentially(double[] numbers, int start, int end) {
       double total = 0;
       for (int i = start; i < end; i++) {
         total += numbers[i];
       }
       return total;
     }
  }));
  final double average = total / population.length;
  double variance = forkJoinPool.invoke(new ForkJoinCalculator(population, new SequentialCalculator() {
    @Override
    public double computeSequentially(double[] numbers, int start, int end) {
      double variance = 0;
      for (int i = start; i < end; i++) {
        variance += (numbers[i] - average) * (numbers[i] - average);
      }
      return variance;
    }
 }));
 return variance / population.length;
}
public int sum(int[] data) {
    int value = 0;
    for (int i = 0; i < data.length; ++i) {
        value += 2 * data[i];
    }
    return value / 2;
}