处理+多线程的反应扩散算法

时间:2016-05-26 22:00:14

标签: multithreading processing

我已经在video tutorial之后在Processing 3.1.1上实现了反应扩散算法。我已经对我的代码进行了一些调整,比如在圆环空间上实现它,而不是像视频那样有限的框。

然而,我遇到了这个恼人的问题,代码运行速度非常慢,与画布大小成正比(更大,更慢)。有了这个,我尝试根据我的(有限的)知识来优化代码。我做的主要是减少循环次数。

即便如此,我的代码仍然运行缓慢。

因为我注意到使用尺寸为50 x 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();
  }
}

1 个答案:

答案 0 :(得分:0)

  

程序员遇到了问题。他想“我知道,我会用线程解决它!”。现在有问题。两个他

处理使用单个渲染线程。

这样做有充分理由,大多数其他渲染器都做同样的事情。实际上,我不知道任何多线程渲染器。

你应该只从Processing的主渲染线程中改变屏幕上的内容。换句话说,你应该只改变Processing的功能,而不是你自己的线程。这就是导致你看到闪烁的原因。你正在改变东西,因为它被吸引到屏幕上,这是一个可怕的想法。 (这就是为什么Processing首先使用单个渲染线程。)

您可以尝试使用多个线程进行处理,而不是渲染。但我非常怀疑这是值得的,就像你看到的那样,甚至可能会让事情变得更糟。

如果您想加快草图速度,您可能还会考虑提前进行处理而不是实时处理。在草图的开头进行所有计算,然后在绘制框架时参考计算结果。或者你可以提前画到PImage,然后画那些。