【发布时间】:2018-09-01 13:36:48
【问题描述】:
我写了一个程序来渲染Julia Set。单线程代码非常简单,本质上是这样的:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
double X = map(x,0,WIDTH,-2.0,2.0);
double Y = map(y,0,HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
return img;
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < maxiter; i++) {
z.square();
z.add(c);
if (z.mod() > blowup) {
break;
}
}
brightness = (i < maxiter) ? 1f : 0;
hue = (i%maxiter)/(float)maxiter;
int rgb = Color.getHSBColor(hue,saturation,brightness).getRGB();
return rgb;
}
如您所见,它的效率非常低。因此,我使用 Java 中的 fork/join 框架来并行化这段代码,这就是我想出的:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
ForkCalculate fork = new ForkCalculate(img, 0, WIDTH, HEIGHT);
ForkJoinPool forkPool = new ForkJoinPool();
forkPool.invoke(fork);
return img;
}
//ForkCalculate.java
public class ForkCalculate extends RecursiveAction {
BufferedImage img;
int minWidth;
int maxWidth;
int height;
int threshold;
int numPixels;
ForkCalculate(BufferedImage b, int minW, int maxW, int h) {
img = b;
minWidth = minW;
maxWidth = maxW;
height = h;
threshold = 100000; //TODO : Experiment with this value.
numPixels = (maxWidth - minWidth) * height;
}
void computeDirectly() {
for (int x = minWidth; x < maxWidth; x++) {
for (int y = 0; y < height; y++) {
double X = map(x,0,Fractal.WIDTH,-2.0,2.0);
double Y = map(y,0,Fractal.HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
}
@Override
protected void compute() {
if(numPixels < threshold) {
computeDirectly();
return;
}
int split = (minWidth + maxWidth)/2;
invokeAll(new ForkCalculate(img, minWidth, split, height), new ForkCalculate(img, split, maxWidth, height));
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < Fractal.maxiter; i++) {
z.square();
z.add(Fractal.c);
if (z.mod() > Fractal.blowup) {
break;
}
}
brightness = (i < Fractal.maxiter) ? 1f : 0;
hue = (i%Fractal.maxiter)/(float)Fractal.maxiter;
int rgb = Color.getHSBColor(hue*5,saturation,brightness).getRGB();
return rgb;
}
private double map(double x, double in_min, double in_max, double out_min, double out_max) {
return (x-in_min)*(out_max-out_min)/(in_max-in_min) + out_min;
}
}
我测试了一系列不同的值,maxiter、blowup 和 threshold。
我设置了阈值,使线程数与我拥有的内核数(4)大致相同。
我测量了这两种情况下的运行时间,并期望在并行代码中进行一些优化。然而,代码有时会在同一时间运行,如果不是更慢的话。这让我很困惑。发生这种情况是因为问题规模不够大吗?我还测试了从 640*400 到 1020*720 的不同图像尺寸。
为什么会这样?如何并行运行代码以使其运行得更快?
编辑 如果您想查看完整的代码,请访问我的Github 主分支具有单线程代码。 名为 Multicore 的分支具有并行化代码。
【问题讨论】:
-
这个链接,gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html 是关于并行流,但重点是 FG 的性能。有很多关于为什么并行有时会变慢的问题,而且所有的原因都太多了,这里就不一一列举了。数据并行,en.wikipedia.org/wiki/Data_parallelism 需要许多处理器才能正常工作——4 是一个很小的数字。
-
@edharned 你说“FG”是什么意思?我浏览了文档,内容很多。虽然这是否意味着对于这个特定问题(Julia Set),编写并行代码毫无价值?
-
FG 是 ForkJoin。并行流基于 FG。搜索“java 并行有时比同步慢”会在 SO 上找到许多问题/答案。只有您可以回答是否并行化的问题。正如我上面所说,Data Parallel 需要将工作分散到很多很多处理器上。在 2、4、8 等核心机器流行之前,Data Parallel 曾经仅限于 Massively Parallel Processors(科学/学术)。在 256 核的机器上运行你的问题,看看会发生什么。
-
@edharned 感谢您的指点。我会查查的。我这样做的原因是我看过一个与生成分形的同一主题相关的 C++ 视频 (youtube.com/watch?v=Pc8DfEyAxzg),他通过并行化对代码进行了相当多的优化。我希望用 Java 实现同样的目标。希望我能做到。
-
看不到加速的原因肯定有几个。第一:几乎没有完成任何迭代!大多数分形没有达到
maxiter限制。使用imaginary = 0.056;和maxiter=1000生成一些工作负载。对我来说,单线程版本大约需要 1500 毫秒,并行版本大约需要 800 毫秒。进一步的优化可能是可能的。我见过“多核”分支:我的直觉是,简单地将图像平铺成可能 10x10 的图块并将它们放入 Threadpool ExecutorService 可能会更快(但尚未对其进行详细调查)
标签: java concurrency fractals fork-join