加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

Java:如何优化大数组的总和

发布时间:2020-12-15 05:11:39 所属栏目:Java 来源:网络整理
导读:我尝试在 codeforces上解决一个问题.我得到时间限制超过判断.唯一耗时的操作是大数组的计算和.所以我试图优化它,但没有结果. 我想要的:优化下一个功能: //array could be Integer.MAX_VALUE lengthprivate long canocicalSum(int[] array) { int sum = 0;
我尝试在 codeforces上解决一个问题.我得到时间限制超过判断.唯一耗时的操作是大数组的计算和.所以我试图优化它,但没有结果.

我想要的:优化下一个功能:

//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;
}

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

我已经尝试过:避免使用非常大的数字进行操作.所以我决定使用辅助数据.例如,我将array1 [100]转换为array2 [10],其中array2 [i] = array1 [i] array1 [i 1] array1 [i 9].

private long optimizedSum(int[] array,int step) {
    do {
        array = sumItr(array,step);
    } while (array.length != 1);
    return array[0];
}

private  int[] sumItr(int[] array,int step) {
    int length = array.length / step + 1;
    boolean needCompensation = (array.length % step == 0) ? false : true;
    int aux[] = new int[length];
    for (int i = 0,auxSum = 0,auxPointer = 0; i < array.length; i++) {
        auxSum += array[i];
        if ((i + 1) % step == 0) {
            aux[auxPointer++] = auxSum;
            auxSum = 0;
        }
        if (i == array.length - 1 && needCompensation) {
            aux[auxPointer++] = auxSum;
        }
    }
    return aux;
}

问题:但似乎canonicalSum比optimizeSum快十倍.在这里我的测试:

@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;
}

问题2:为什么optimizeSum的工作速度比canonicalSum慢?

解决方法

Question1 [main]: Is it possible to optimize canonicalSum?

是的.但我不知道有什么因素.

你可以做的一些事情是:

>使用Java 8中引入的并行管道.处理器具有执行2个数组(以及更多)的并行求和的指令.当您使用“.”(并行加法)或“”将两个向量相加时,可以在Octave中观察到这种情况.它比使用循环更快.
>使用多线程.你可以使用分而治之的算法.也许是这样的:

>将数组分成2个或更多
>继续递归分割,直到获得一个具有可管理大小的线程的数组.
>开始使用单独的线程计运算符数组(分割数组)的总和.
>最后将所有子阵列生成的总和(从所有线程)添加到一起以产生最终结果

>也许展开循环也会有所帮助.通过循环展开,我的意思是通过手动在循环中执行更多操作来减少循环必须执行的步骤.

http://en.wikipedia.org/wiki/Loop_unwinding的一个例子:

for (int x = 0; x < 100; x++)
{
    delete(x);
}

for (int x = 0; x < 100; x+=5)
{
    delete(x);
    delete(x+1);
    delete(x+2);
    delete(x+3);
    delete(x+4);
}

但是如上所述,这必须谨慎和剖析,因为JIT本身可能会进行这种优化.

可以在here中看到用于多线程方法的数学运算的实现.

使用在Java 7中引入的Fork / Join框架的示例实现基本上执行上面的分而治之的算法:

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,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 + length/2,sequentialCalculator);
     leftTask.fork();
     ForkJoinCalculator rightTask = new ForkJoinCalculator(numbers,end,sequentialCalculator);
     Double rightResult = rightTask.compute();
     Double leftResult = leftTask.join();
     return leftResult + rightResult;
  }
}

Here we develop a RecursiveTask splitting an array of doubles until
the length of a subarray doesn’t go below a given threshold. At this
point the subarray is processed sequentially applying on it the
operation defined by the following interface

使用的界面是这样的:

public interface SequentialCalculator {
  double computeSequentially(double[] numbers,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 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 end) {
      double variance = 0;
      for (int i = start; i < end; i++) {
        variance += (numbers[i] - average) * (numbers[i] - average);
      }
      return variance;
    }
 }));
 return variance / population.length;
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读