昨年末に見つけた本で少し遊んで勉強しようかと思いながら熱が冷めてしまった。。。
最近、つぶやきProcessingでp5.jsを少し書くようになったので、試しに、ALIFE本の2章目に出てくるGray ScottモデルのPythonコードをJSに移植してみました。
大元のコードはこれです。numpyのおかげで非常にすっきり書かれています。かつ高速なんですよね?
pythonはほとんど書かないので、pythonの(というよりnumpyの)動きを1つ1つ確かめながらJSに移植していきました。
最初に完成したのが以下です。
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);
で、できたのが以下です。
ブラウザというかJSは本当に速いですねぇ。。。
チューニング前後を比較したのが以下です。うーん素晴らしいぃ?
Gray Scott Model (2)
— タカハシラボ (@ke_takahashi) March 10, 2020
はよなった🎉#p5js #processinghttps://t.co/mQGZhq8v6A pic.twitter.com/t0Q4cbNHYX
おしまい。