memorandums

日々の生活で問題解決したこと、知ってよかったことなどを自分が思い出すために記録しています。

ALIFE本のGray Scott モデルをJS(p5.js)に移植してみた

昨年末に見つけた本で少し遊んで勉強しようかと思いながら熱が冷めてしまった。。。

最近、つぶやきProcessingでp5.jsを少し書くようになったので、試しに、ALIFE本の2章目に出てくるGray ScottモデルのPythonコードをJSに移植してみました。

大元のコードはこれです。numpyのおかげで非常にすっきり書かれています。かつ高速なんですよね?

github.com

pythonはほとんど書かないので、pythonの(というよりnumpyの)動きを1つ1つ確かめながらJSに移植していきました。

最初に完成したのが以下です。

www.openprocessing.org

JSでの2次元配列の書き方が間違っていて最初うまく動作しませんでしたが。。。なんとか動作しました。

numpyでは行列計算を意識しなくても(←意識しないというのは言い過ぎですが)いいのですが、JSでは1つ1つ計算してあげなければいけません。forで2重ループで書きました。

で、遅いんですね。。。少なくともPythonの実行結果と比べて。。。JSだからしかがたないか。。。と思いつつ。チューニングしてみました。

チューニングする前にJSの高速化方法をぐぐって調べるとグローバル変数は遅いとか。ほんとかわかりませんが。。。で、とりあえず、constやletを使ってスコープを明確化しました。

でも、遅い。。。

drawメソッドは最短で1/60ごとにコールされるのですが、1回の処理がこれが上限になってしまうので、draw内でstep回数分繰り返すようにしました。これは元々のpythonコードもそのように書いていたのもあります。でも。。。変わらない。つまり、draw内の処理の方が時間がかかっているということになります。

で、さらにコードをブロックごとに時間計測してみると。。。一番時間がかかっていたのは配列計算ではなくグラフィックでした。

チューニング前が以下です。forの2重ループで1点ずつ描画しています。これが遅い。。。これぐらい難なくコナしてくれると思っていましたが。。。

  background(255);
  for (i=0; i<SPACE_GRID_SIZE; i++) {
    for (j=0; j<SPACE_GRID_SIZE; j++) {
      stroke(map(u[i][j], 0, 1, 0, 255));
      point(i, j);
    }
  }

で、javaでいうPImageのオブジェクトの値をimg.setで変更して一気に描画に反映する方式にしてみました。。。すると劇的(計測していませんが1/10くらい)になったと思います。

  img.loadPixels();
  for (let i=0; i<SPACE_GRID_SIZE; i++) {
    for (let j=0; j<SPACE_GRID_SIZE; j++) {
      img.set(i, j, map(u[i][j], 0, 1, 0, 255));
    }
  }
  img.updatePixels();
  image(img, 0, 0);

で、できたのが以下です。

www.openprocessing.org

ブラウザというかJSは本当に速いですねぇ。。。

チューニング前後を比較したのが以下です。うーん素晴らしいぃ?

おしまい。