多线程快速归并排序的性能优化问题咨询
1. 耗时误区澄清
你观测到的join()耗时高是假象:join()本身只是线程阻塞等待的操作,你统计到的耗时本质是最慢的那一个排序子线程的执行时间,而非join()本身的开销,不需要在join环节做优化,核心性能瓶颈在合并逻辑和不必要的内存拷贝。
2. 最高优先级优化:重构合并逻辑
你当前的串行顺序合并逻辑是最大性能瓶颈:从空数组开始依次和每个排序后的小数组合并,时间复杂度为O(n*k)(n为总元素数,k为切分块数)。以8核CPU为例,总内存拷贝次数达到总元素数的36倍,损耗极大。
优化方案:改用两两归并
每次合并两个长度相近的数组,总拷贝次数降低到log2(k)*n,8核场景下仅需要3倍总元素数的拷贝,性能提升非常明显。如果要进一步压榨性能,可以把归并也改成多线程并行执行,让归并阶段也利用多核资源。
最简改造的代码示例:
// 替换原有的串行合并逻辑 List<int[]> sortedChunks = new ArrayList<>(Arrays.asList(finalSlicedArray)); while (sortedChunks.size() > 1) { List<int[]> newChunks = new ArrayList<>(); for (int i = 0; i < sortedChunks.size(); i += 2) { if (i + 1 < sortedChunks.size()) { newChunks.add(merge(sortedChunks.get(i), sortedChunks.get(i + 1))); } else { newChunks.add(sortedChunks.get(i)); } } sortedChunks = newChunks; } int[] sorted = sortedChunks.get(0);
3. 优化归并函数的执行效率
你当前的归并函数尾部循环拷贝可以替换为JDK自带的System.arraycopy,底层是native实现,性能比Java层循环高不少,优化后代码:
private static int[] merge(int[] arr1, int[] arr2) { int[] mergedArray = new int[arr1.length + arr2.length]; int i = 0, j = 0, k = 0; while (i < arr1.length && j < arr2.length) { mergedArray[k++] = arr1[i] < arr2[j] ? arr1[i++] : arr2[j++]; } if (i < arr1.length) { System.arraycopy(arr1, i, mergedArray, k, arr1.length - i); } if (j < arr2.length) { System.arraycopy(arr2, j, mergedArray, k, arr2.length - j); } return mergedArray; }
4. 减少不必要的内存拷贝
你当前的slice方法用Arrays.copyOfRange把原始数组全量拷贝了一遍,完全无必要:可以给每个排序线程传入原始数组的起止下标,直接在原始数组的对应区间排序,省掉一次全量内存拷贝的开销。
5. 降低线程调度开销
每次排序都新建一批原生Thread,线程创建销毁的开销会拉高整体耗时,建议改用JDK自带的ForkJoinPool,它本身就是为并行分治任务设计的,完美适配排序+归并的分治场景,线程复用能降低调度开销。
开箱即用方案
如果你不需要自己造轮子实现排序逻辑,直接调用JDK自带的Arrays.parallelSort()方法即可,它的底层实现就是你这个思路的官方优化版,经过大量性能调优,比自定义实现性能高30%以上。
内容的提问来源于stack exchange,提问作者No_Name

