【发布时间】:2021-03-24 21:43:01
【问题描述】:
在我的项目中,我发现排序性能是瓶颈。经过一番谷歌搜索,我想出了基数排序的并行版本(以 256 为基数)。但是它的行为不像我预期的那样。
首先将基数更改为 2^16 不会导致任何加速,理论上应该是 2。
第二次在我的并行版本中,我将其拆分为 4 个部分(核心数)并对它们进行基数排序,然后合并结果。同样,它仅与串行版本同时运行。
public class RadixSortPrototype {
public static void parallelSort(long[] arr) {
long[] output = new long[arr.length];
int MAX_PART = 1_000_000;
int numProc = Runtime.getRuntime().availableProcessors();
int partL = Math
.min((int) Math.ceil(arr.length / (double) numProc), MAX_PART);
int parts = (int) Math.ceil(arr.length / (double) partL);
Future[] threads = new Future[parts];
ExecutorService worker = Executors.newFixedThreadPool(numProc);
for (int i = 0; i < 8; i++) {
int[][] counts = new int[parts][256];
int radix = i;
for (int j = 0; j < parts; j++) {
int part = j;
threads[j] = worker.submit(() -> {
for (int k = part * partL; k < (part + 1) * partL && k < arr.length;
k++) {
int chunk = (int) ((arr[k] >> (radix * 8)) & 255);
counts[part][chunk]++;
}
});
}
barrier(parts, threads);
int base = 0;
for (int k = 0; k <= 255; k++) {
for (int j = 0; j < parts; j++) {
int t = counts[j][k];
counts[j][k] = base;
base += t;
}
}
for (int j = 0; j < parts; j++) {
int part = j;
threads[j] = worker.submit(() -> {
for (int k = part * partL;
k < (part + 1) * partL && k < arr.length;
k++) {
int chunk = (int) ((arr[k] >> (radix * 8)) & 255);
output[counts[part][chunk]] = arr[k];
counts[part][chunk]++;
}
});
}
barrier(parts, threads);
for (int j = 0; j < parts; j++) {
int part = j;
threads[j] = worker.submit(() -> {
for (int k = part * partL;
k < (part + 1) * partL && k < arr.length;
k++) {
arr[k] = output[k];
}
});
}
barrier(parts, threads);
}
worker.shutdownNow();
}
private static void barrier(int parts, Future[] threads) {
for (int j = 0; j < parts; j++) {
try {
threads[j].get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
}
任何想法为什么它运行这么慢?解决此优化的推荐方法是什么?
我真的很好奇答案。
谢谢!
更新
根据答案,我改进了数据的局部性,所以现在它使用了所有核心。更新了代码 sn-p。以下是 2 核 4 线程 CPU 的结果。
Java Parallel: 1130 ms
Radixsort Serial: 1218 ms
Radixsort Parallel: 625 ms
如果可以进一步改进,这个问题仍然悬而未决。
【问题讨论】:
-
输入有多大?它似乎在内存中,所以它不应该太大。在这种情况下,我会检查输入是否大到足以从并行执行中获益。请记住,启动新线程是一项昂贵的操作。根据我的经验,许多性能问题是由高内存消耗导致程序变慢。
-
输入是20M长。
标签: java performance optimization parallel-processing radix-sort