2016-05-26 15 views
0

処理3.1.1の反応拡散アルゴリズムの実装をvideo tutorialの後に行いました。私はビデオのような有界なボックスではなく、トーラス空間に実装するような、自分のコードにいくつかの変更を加えました。Processing + Multithreadingの反応拡散アルゴリズム

しかし、コードが実際には遅く、キャンバスのサイズに比例して(大きく、遅く)、この厄介な問題に遭遇しました。それで、私の(限られた)知識によれば、私はコードを最適化しようとしました。私がした主なことは、実行中のループの数を減らすことでした。

それでも、私のコードはまだかなり遅く走っていました。

私は50×50のキャンバスで気づいたので、アルゴリズムはうまくいっていました。マルチスレッド化しました。キャンバスがスレッド間で分割され、各スレッドキャンバスの小さな領域のアルゴリズムを実行します。

すべてのスレッドがキャンバスの現在の状態から読み取り、すべてキャンバスの将来の状態に書き込みます。キャンバスはProcessingのピクセル配列を使用して更新されます。

しかし、マルチスレッド化しても、パフォーマンスは向上しませんでした。逆に、私はそれが悪化しているのを見ました。現在では、レンダリングされた状態と完全に白い状態の間でキャンバスがちらつくことがあり、場合によってはレンダリングさえしません。

私は何か間違っていると確信しています。あるいは、私はこのアルゴリズムを最適化するために間違ったアプローチを取っているかもしれません。そして今、私は何が間違っているのかを理解する助けと、コードを修正したり改良したりする方法を求めています。

編集:PImageオブジェクトのバッファを使用して計算とレンダリングを事前に実装すると、ちらつきはなくなりましたが、バックグラウンドの計算ステップはバッファを満たすのに十分速く実行されません。

私の処理スケッチは下にあり、前もって感謝します。

ArrayList<PImage> buffer = new ArrayList<PImage>(); 
Thread t; 
Buffer b; 
PImage currentImage; 

Point[][] grid; //current state 
Point[][] next; //future state 

//Reaction-Diffusion algorithm parameters 
final float dA = 1.0; 
final float dB = 0.5; 
//default: f = 0.055; k = 0.062 
//mitosis: f = 0.0367; k = 0.0649 
float feed = 0.055; 
float kill = 0.062; 
float dt = 1.0; 

//multi-threading parameters to divide canvas 
int threadSizeX = 50; 
int threadSizeY = 50; 

//red shading colors 
color red = color(255, 0, 0); 
color white = color(255, 255, 255); 
color black = color(0, 0, 0); 

//if redShader is false, rendering will use a simple grayscale mode 
boolean redShader = true; 

//simple class to hold chemicals A and B amounts 
class Point 
{ 
    float a; 
    float b; 

    Point(float a, float b) 
    { 
    this.a = a; 
    this.b = b; 
    } 
} 

void setup() 
{ 
    size(300, 300); 

    //initialize matrices with A = 1 and B = 0 
    grid = new Point[width][]; 
    next = new Point[width][]; 

    for (int x = 0; x < width; x++) 
    { 
    grid[x] = new Point[height]; 
    next[x] = new Point[height]; 

    for (int y = 0; y < height; y++) 
    { 
     grid[x][y] = new Point(1.0, 0.0); 
     next[x][y] = new Point(1.0, 0.0); 
    } 
    } 

    int a = (int) random(1, 20); //seed some areas with B = 1.0 
    for (int amount = 0; amount < a; amount++) 
    { 
    int siz = 2; 
    int x = (int)random(width); 
    int y = (int)random(height); 

    for (int i = x - siz/2; i < x + siz/2; i++) 
    { 
     for (int j = y - siz/2; j < y + siz/2; j++) 
     { 
     int i2 = i; 
     int j2 = j; 
     if (i < 0) 
     { 
      i2 = width + i; 
     } else if (i >= width) 
     { 
      i2 = i - width; 
     } 
     if (j < 0) 
     { 
      j2 = height + j; 
     } else if (j >= height) 
     { 
      j2 = j - height; 
     } 


     grid[i2][j2].b = 1.0; 
     } 
    } 
    } 
    initializeThreads(); 
} 

/** 
* Divide canvas between threads 
*/ 
void initializeThreads() 
{ 
    ArrayList<Reaction> reactions = new ArrayList<Reaction>(); 

    for (int x1 = 0; x1 < width; x1 += threadSizeX) 
    { 
    for (int y1 = 0; y1 < height; y1 += threadSizeY) 
    { 
     int x2 = x1 + threadSizeX; 
     int y2 = y1 + threadSizeY; 

     if (x2 > width - 1) 
     { 
     x2 = width - 1; 
     } 
     if (y2 > height - 1) 
     { 
     y2 = height - 1; 
     } 

     Reaction r = new Reaction(x1, y1, x2, y2); 

     reactions.add(r); 
    } 
    } 

    b = new Buffer(reactions); 
    t = new Thread(b); 
    t.start(); 
} 

void draw() 
{ 
    if (buffer.size() == 0) 
    { 
    return; 
    } 
    PImage i = buffer.get(0); 

    image(i, 0, 0); 
    buffer.remove(i); 
    //println(frameRate); 
    println(buffer.size()); 

    //saveFrame("output/######.png"); 
} 

/** 
* Faster than calling built in pow() function 
*/ 
float pow5(float x) 
{ 
    return x * x * x * x * x; 
} 

class Buffer implements Runnable 
{ 
    ArrayList<Reaction> reactions; 

    boolean calculating = false; 

    public Buffer(ArrayList<Reaction> reactions) 
    { 
    this.reactions = reactions; 
    } 
    public void run() 
    { 
    while (true) 
    { 
     if (buffer.size() < 1000) 
     { 
     calculate(); 

     if (isDone()) 
     { 
      buffer.add(currentImage); 

      Point[][] temp; 
      temp = grid; 
      grid = next; 
      next = temp; 

      calculating = false; 
     } 
     } 
    } 
    } 

    boolean isDone() 
    { 
    for (Reaction r : reactions) 
    { 
     if (!r.isDone()) 
     { 
     return false; 
     } 
    } 

    return true; 
    } 

    void calculate() 
    { 
    if (calculating) 
    { 
     return; 
    } 

    currentImage = new PImage(width, height); 
    for (Reaction r : reactions) 
    { 
     r.calculate(); 
    } 

    calculating = true; 
    } 
} 

class Reaction 
{ 
    int x1; 
    int x2; 
    int y1; 
    int y2; 

    Thread t; 

    public Reaction(int x1, int y1, int x2, int y2) 
    { 
    this.x1 = x1; 
    this.x2 = x2; 
    this.y1 = y1; 
    this.y2 = y2; 
    } 

    public void calculate() 
    { 
    Calculator c = new Calculator(x1, y1, x2, y2); 

    t = new Thread(c); 
    t.start(); 
    } 

    public boolean isDone() 
    { 
    if (t.getState() == Thread.State.TERMINATED) 
    { 
     return true; 
    } else 
    { 
     return false; 
    } 
    } 
} 

class Calculator implements Runnable 
{ 
    int x1; 
    int x2; 
    int y1; 
    int y2; 

    //weights for calculating the Laplacian for A and B 
    final float[][] laplacianWeights = {{0.05, 0.2, 0.05}, 
    {0.2, -1, 0.2}, 
    {0.05, 0.2, 0.05}}; 

    /** 
    * x1, x2, y1, y2 delimit a rectangle. The object will only work within it 
    */ 
    public Calculator(int x1, int y1, int x2, int y2) 
    { 
    this.x1 = x1; 
    this.x2 = x2; 
    this.y1 = y1; 
    this.y2 = y2; 

    //println("x1: " + x1 + ", y1: " + y1 + ", x2: " + x2 + ", y2: " + y2); 
    } 

    @Override 
    public void run() 
    { 
    reaction(); 
    show(); 
    } 

    public void reaction() 
    { 
    for (int x = x1; x <= x2; x++) 
    { 
     for (int y = y1; y <= y2; y++) 
     { 
     float a = grid[x][y].a; 
     float b = grid[x][y].b; 

     float[] l = laplaceAB(x, y); 

     float a2 = reactionDiffusionA(a, b, l[0]); 
     float b2 = reactionDiffusionB(a, b, l[1]); 

     next[x][y].a = a2; 
     next[x][y].b = b2; 
     } 
    } 
    } 

    float reactionDiffusionA(float a, float b, float lA) 
    { 
    return a + ((dA * lA) - (a * b * b) + (feed * (1 - a))) * dt; 
    } 

    float reactionDiffusionB(float a, float b, float lB) 
    { 
    return b + ((dB * lB) + (a * b * b) - ((kill + feed) * b)) * dt; 
    } 

    /** 
    * Calculates Laplacian for both A and B at same time, to reduce amount of loops executed 
    */ 
    float[] laplaceAB(int x, int y) 
    { 
    float[] l = {0.0, 0.0}; 

    for (int i = x - 1; i < x + 2; i++) 
    { 
     for (int j = y - 1; j < y + 2; j++) 
     { 
     int i2 = i; 
     int j2 = j; 
     if (i < 0) 
     { 
      i2 = width + i; 
     } else if (i >= width) 
     { 
      i2 = i - width; 
     } 
     if (j < 0) 
     { 
      j2 = height + j; 
     } else if (j >= height) 
     { 
      j2 = j - height; 
     } 

     int weightX = (i - x) + 1; 
     int weightY = (j - y) + 1; 

     l[0] += laplacianWeights[weightX][weightY] * grid[i2][j2].a; 
     l[1] += laplacianWeights[weightX][weightY] * grid[i2][j2].b; 
     } 
    } 

    return l; 
    } 

    public void show() 
    { 
    currentImage.loadPixels(); 

    //renders the canvas using the pixel array 
    for (int x = 0; x < width; x++) 
    { 
     for (int y = 0; y < height; y++) 
     { 
     float a = next[x][y].a; 
     float b = next[x][y].b; 

     int pix = x + y * width; 

     float diff = (a - b); 

     color c; 

     if (redShader) //aply red shading 
     { 
      float thresh = 0.5; 

      if (diff < thresh) 
      { 
      float diff2 = map(pow5(diff), 0, pow5(thresh), 0, 1); 

      c = lerpColor(black, red, diff2); 
      } else 
      { 
      float diff2 = map(1 - pow5(-diff + 1), 1 - pow5(-thresh + 1), 1, 0, 1); 

      c = lerpColor(red, white, diff2); 
      } 
     } else //apply gray scale shading 
     { 
      c = color(diff * 255, diff * 255, diff * 255); 
     } 

     currentImage.pixels[pix] = c; 
     } 
    } 
    currentImage.updatePixels(); 
    } 
} 

答えて

0

プログラマが問題を抱えていました。彼は「私が知っている、私はスレッドで解決します!」と思った。今問題があります。 2人

処理では1つのレンダリングスレッドが使用されます。

これは正当な理由でこれを行い、他のほとんどのレンダラも同じことを行います。実際、私はマルチスレッドレンダラについて知らない。

画面の内容は、処理のメインレンダリングスレッドから変更するだけです。つまり、自分のスレッドではなく、処理の機能から変更するだけです。これはあなたが見ているちらつきの原因です。画面に描かれているように変化しています。これは恐ろしいアイデアです。 (なぜ処理が最初に1つのレンダリングスレッドを使用するのか)

レンダリングではなく、処理を行うために複数のスレッドを使用できます。しかし、私はそれがその価値があることを非常に疑っています。あなたが見たように、状況を悪化さえするかもしれません。

スケッチをスピードアップしたい場合は、リアルタイムではなく事前に処理することを検討することもできます。スケッチの始めにすべての計算を行い、次にフレームを描画するときに計算の結果を参照します。または、事前にPImageに描画してから描画することもできます。

+0

実際のレンダリングは、マルチスレッドではなく、計算のみで行われます。しかし、あなたが言ったように、ちらつきはおそらく、スケッチが描画されている間に操作されているデータから来るでしょう。 あなたが言及した別のアプローチを試し、結果を戻します。 – tady159

+0

@ tady159あるスレッドからデータ構造を変更して、それらを使ってレンダリングスレッドを描画する場合は、何らかのシンクロナイゼーションを実装して、画面に描画されている間は変更しないようにする必要があります。これは確かにあなたのプログラムをスピードアップしません。 –

+0

私は事前にあなたが提案したアプローチを実装しました。私はPImageオブジェクトを含むバッファを作った。 バックグラウンドでは、フレームの計算が完了するたびに、PImageがバッファに追加されます。 これによりちらつきがなくなりましたが、バックグラウンド計算が十分速く実行されていないため、スケッチのメインスレッドをキャンバスにレンダリングするときにバッファがほとんど空になります。 – tady159