matlab 并行计算算例
前段时间,我的一个朋友问我有关加快以下过程的可能性的问题:他们正在分两个阶段生成一些数据,即从数据库中读取数据并处理结果。 读取大约需要70%的时间,而处理需要30%的时间。 不幸的是,他们不能简单地将整个数据加载到内存中,因此他们将读取分为多个小块(页面),并在检索到这些页面后对其进行处理,从而将这两个阶段交错在一起。 这是到目前为止他们拥有的伪代码:
public Data loadData(int page) {
//70% of time...
}
public void process(Data data) {
//30% of time...
}
for (int i = 0; i < MAX; ++i) {
Data data = loadData(i);
process(data);
}
他改进算法的想法是,当仍在处理当前页面时,以某种方式开始获取下一页数据,从而减少了算法的总运行时间。 他是正确的,但是不知道如何将其放入Java代码中,并且对华丽的java.util.concurrent
包还没有足够的经验。 本文针对此类人群,简要介绍了Java并发编程的基本概念,例如线程池和Future<T>
类型。 首先,让我们使用甘特图可视化初始和所需的实现:
第二张图表代表了我们要实现的解决方案。 您应该做的第一个观察是第二个过程较早完成,这很好。 第二个是:当我们在处理第一页(黄色1)时,第二页已经被下载(绿色2)。 当我们开始处理第2页时,第3页开始下载。 等等。 一旦有了可行的实现,我们将在稍后返回此图表。 让我们将其放入代码中。
线程是实现后台加载数据(绿色块)的方法。 但是,仅为每个绿色块启动线程既缓慢又不方便。 仅具有一个线程的线程池更加灵活,更易于使用。 首先, loadData()
Callable<Data>
包装到Callable<Data>
:
private class LoadDataTask implements Callable<Data> {
private final int page;
private LoadDataTask(int page) {
this.page = page;
}
@Override
public Data call() throws Exception {
return loadData(page);
}
}
一旦有了这样的类,就很容易提供线程池(由ExecutorService
表示)并等待答复。 这是一个完整的实现:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Data> next = executorService.submit(new LoadDataTask(0));
for (int i = 0; i < MAX; ++i) {
Future<Data> current = next;
if (i + 1 < MAX) {
next = executorService.submit(new LoadDataTask(i + 1));
}
Data data = current.get(); //this can block
process(data);
}
executorService.shutdownNow();
Executors.newSingleThreadExecutor()
基本上创建一个后台线程,等待任务运行。 我们不能使用更大的池(具有更多线程),因为那样的话,我们将有风险在处理数据之前将太多数据保留在内存中。
出于示例目的,假设在处理页面(黄色块)时加载页面(绿色块)需要700毫秒-300毫秒。 首先,我们提交初始任务以加载页面0(第一个蓝色箭头指向下方)。 因此,我们必须为第一个块等待700ms。 但是,一旦数据可用,在开始处理数据之前,我们会立即要求下一页。 当我们运行第二次迭代时,我们不必再等待700毫秒,因为加载数据已经进行了300毫秒,因此Future.get()仅阻塞400毫秒。 我们重复此过程,直到处理完最后一页。 当然,由于我们已经处理了所有数据,因此我们没有加载下一页数据,因此循环内会出现这种丑陋的情况。 通过在页面超出范围时从loadData()
返回空对象很容易避免这种情况,但是为了示例清楚起见,我们将其保留。
这种方法在企业中非常普遍,以至于Spring和EJB都添加了专门的支持。 让我们以Spring为例。 我们唯一需要更改的是将loadData()
返回值从Data调整为Future<Data>
。 需要使用AsyncResult
包装结果值才能进行编译:
@Async
public Future<Data> loadData(int page) {
//...
return new AsyncResult<Data>(new Data(...));
}
当然,此类是某些Spring bean(例如dao
)的一部分。 API现在更加简洁:
Future<Data> next = dao.loadData(0);
for (int i = 0; i < MAX; ++i) {
Future<Data> current = next;
if (i + 1 < MAX) {
next = dao.loadData(i + 1);
}
Data data = current.get();
processor.process(data);
}
我们不再需要使用Callable
并与某些线程池进行交互。 另外,引导Spring从未如此简单(所以不要告诉我Spring是重量级的!):
@Configuration
@ComponentScan("com.blogspot.nurkiewicz.async")
@EnableAsync
public class Config implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return Executors.newSingleThreadExecutor();
}
}
从技术上讲, getAsyncExecutor()
,但是默认情况下,Spring将为@Async方法创建一个包含10个线程的线程池(我们只需要一个)。 现在,只需在代码中的某个地方运行它即可。
ApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
从本文中学到的教训:不要害怕并发,只要您使用内置抽象并理解它们,它就会比您想象的简单得多。
参考: Java和社区博客上的JCG合作伙伴 Tomasz Nurkiewicz 解释了一个简单用例的并行化 。
翻译自: https://www.javacodegeeks.com/2012/11/parallelization-of-a-simple-use-case-explained.html
matlab 并行计算算例