Java:如何优化大数组的总和
我尝试在
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慢? 解决方法
是的.但我不知道有什么因素. 你可以做的一些事情是: >使用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; } }
使用的界面是这样的: 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; } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |