如何提高Java多线程性能?从NoSQL数据库(如Redis)到ArrayList保存/加载数据的时间效率?

我正在评估SDK,我需要交叉比较存储在图库文件夹中的~15000个虹膜图像,并生成相似度分数为15000 x 15000矩阵.

所以我预处理了所有图像并将处理后的blob存储在ArrayList中.然后我在run方法中使用带有2’for’循环的多个线程来调用’compare’方法(来自SDK)并传递ArrayList的索引作为参数来比较那些相应的blob并将整数返回值保存在使用Apache poi库的excel表.性能非常低效(每次比较需要大约40ms)并且整个任务需要花费大量时间(估计大约100天,8个核心以100%运行)来进行所有225,000,000次比较.请帮我理解这个瓶颈.

Multithreading code

int processors = Runtime.getRuntime().availableProcessors();
ExecutorService executor = Executors.newFixedThreadPool(processors); 
for(int i =0; i<processors; i++) { 
  //each thread compares 1875 images with 15000 images
  Runnable task = new Thread(bloblist,i*1875,i*1875+1874); 
  executor.execute(task);
}   
executor.shutdown();

Run Method

public void run(){
 for(int i = startIndex; i<= lastIndex; i++) {
    for(int j=0;j<15000;j++){
        compare.compareIris(bloblist.get(i),bloblist.get(j));
        score= compare.getScore();
        //save result to Excel using Apache POI
        ...
        ...
        }
 }
}

请建议我一个节省时间的架构来完成这项任务.我应该将blob存储在NoSQL DB中,还是有其他方法可以做到这一点?

最佳答案 作为第一步,我会考虑为您的代码添加一些简单的分析.分析库很棒,但可能有点令人生畏.你真正需要的就是:

public void run(){
 long sumCompare = 0;
 long sumSave = 0
 for(int i = startIndex; i<= lastIndex; i++) {
    for(int j=0;j<15000;j++){
        final long compareStart = System.currentTimeMillis();
        compare.compareIris(bloblist.get(i),bloblist.get(j));
        score= compare.getScore();
        final long compareEnd = System.currentTimeMillis();
        compareSum += (compareEnd - compareStart);
        //save result to Excel using Apache POI
        ...
        ...
        final long saveEnd = System.currentTimeMillis();
        saveSum += (saveEnd - compareEnd);
        }
 }
System.out.println(String.format("Compare: %d; Save: %d", sumCompare, sumSave);
}

也许在100×100网格上运行它,而不是大致了解运行时的大部分内容.

如果是保存步骤,我强烈建议使用数据库作为计算得分并将其导出到电子表格之间的中间步骤.一个NoSQL数据库可以工作,虽然我也鼓励你看看像SQLite这样的东西,只是为了简单起见. (许多NoSQL数据库旨在提供跨数据库节点集群的优势,同时处理非常大的数据集;如果您在一个节点上存储只写数据,那么SQL可能是您最好的选择.)

如果瓶颈是计算步骤,则提高性能将更加困难.如果blob并不能完全适合RAM以及比较所消耗的任何RAM,那么您可能需要付出将这些数据交换到磁盘上和磁盘外的价格.通过让每个线程“脱离队列”而不是从预先分配的块开始,您可能会看到一个小的改进:

final int processors = Runtime.getRuntime().availableProcessors();
final ExecutorService executor = Executors.newFixedThreadPool(processors); 
final AtomicLong nextCompare = new AtomicLong(0);

for(int i =0; i<processors; i++) { 
  Runnable task = new Thread(bloblist, nextCompare); 
  executor.execute(task);
}   
executor.shutdown();
public void run(){
  while (true) {
    final long taskNum = nextCompare.getAndIncrement();
    if (taskNum >= 15000 * 15000) {
      return;
    }
    final long i = Math.floor(taskNum/15000);
    final long j = taskNum % 15000;
    compare.compareIris(bloblist.get(i),bloblist.get(j));
    score = compare.getScore();
    // Save score, etc.)
  }
}

这将导致处理blob的所有线程在内存中相对靠近地存储.通过这种方式,没有线程在不久的将来从另一个线程将需要的缓存中驱逐数据.但是,你要付出锁定AtomicLong的代价;如果内存颠簸不是你的问题,这可能会慢一些.

点赞