p5.jsコードの最適化

本稿は「Optimizing p5.js Code for Performance」の永井による翻訳です。

注意すべき事柄

パフォーマンスに関して言うと、最初からできるだけ速いスピードを出そうとしたくなるものです。コードを書くということは、読みやすくかつ維持管理しやすいコードと、ちゃんと仕事をするコードとのバランスをいかにうまく取って書くかということです。パフォーマンスの最適化には何らかの犠牲がともなうことが多いので、一般的には、最適化について悩むのは、問題はスピードにあると分かっている場合に限るべきです。

それに加え、”最適化するのは、コードではなく、アルゴリズムだ”という真言を胸に刻んでおくことも重要です。1行や2行の最適化で良い結果が得られることもあるでしょうが、最良の結果はアプローチを変えることからやって来る場合がほとんどです。たとえば、イメージの全ピクセルを走査するループのコードの場合なら、ピクセルを全部ではなく1つおきに調べると決めれば、コードは間違いなくスピードアップします(高速化、実行速度が速くなること)。

遅いコードの特定:プロファイリング

コードを高速化する最初の手順はそれをプロファイリング(性能解析)する、つまり、コードの各部の実行にどれだけ時間がかかっているかを把握することです。

FPS (1秒当たりのフレーム数)

プログラムのスピードを計測する1つの方法に、プログラムがレンダリング(表示)できるFPS(1秒当たりのフレーム数)があります。コードにインタラクションやアニメーションが含まれているなら、30から60の安定したFPSを目指したいところです(これは、60FPS、30、15FPSがどのように見えるかを示すデモです)。

現時点でのFPSは次の2つの方法のどちらかで簡単に分かります。

p5.jsでは、パラメータなしで呼び出すと、現在のFPSを返すframeRate()関数があるので、その結果をコンソールに出力するか画面に描画します。

// キャンバス左下にFPSを描画(小数点以下2桁を四捨五入)
let fps = frameRate();
fill(255);
stroke(0);
text("FPS: " + fps.toFixed(2), 10, height - 10);

Chromeブラウザでは、[デベロッパーツール]を使用します。

  1. FPSを計測したいページをChromeで開く
  2. Command + Option + Iキー(Mac) か、Ctrl + Shift + Iキー(Windows)を押して[DevTools]を開く
  3. Command + Shift + Pキー(Mac)か、Ctrl + Shift + Pキー(Windows)を押してコマンドメニューを開く
  4. コマンドメニューにRenderringと入力し、[Show Rendering]を選択する
  5. [FPS meter]チェックボックスを選択する
手動によるプロファイリング

コードのある部分の実行にかかる時間を調べるには、コードの実行がスタートしたタイミングと終了したタイミングを知る必要があります。p5.jsでは、millis()関数によって現在の時間がミリ秒単位で得られます(内部的には、この関数はただ、JavaScriptネイティブのperformance.now()メソッドの結果を返しているだけです)。

millis()関数を使ってコードの特定部分の時間を計測するには:

// 開始時間
let start = millis();

// かかる時間を計測したい作業をここで行う
random(0, 100);
// 終了時間
let end = millis();
// 経過時間 = 終了時間 - 開始時間
let elapsed = end - start;
console.log("This took: " + elapsed + "ms.")

通常は、プロファイリングするコードを何度も実行し、実行にかかった平均時間を求めることになります。パフォーマンステストのサンプルは”p5.js-website/src/assets/learn/performance/code/cpu-profiler-demo/“で見ることができます。

注意:console.log()やprint()はコードの実行を明らかに遅くするので、プロジェクトの最終版では忘れずに削除してください。


次のコードは、上記パフォーマンステストのsketch.jsを永井が修正したものです。コンピュータにWebカメラをつないで実行します。

// friendly error system(FES)をオフにする
p5.disableFriendlyErrors = true;

let capture;
let videoWidth = null;
let videoHeigh = null;
let sampledPixels = [];
const stepSize = 2;

function setup() {
    createCanvas(400, 300);

    // Webカメラからのビデオ、オーディオをキャプチャする<video>要素を作成する
    capture = createCapture(VIDEO);
    // <video>要素のサイズは設定できない。size()を使うとエラーが発生する
    //capture.size(640, 480);
    // <video>要素を隠す => ビデオ動画は表示しない
    capture.hide();

    // ストリームが、ロードされたフレームを持つまで待ってから、処理を開始する
    capture.elt.addEventListener("loadeddata", function() {
        videoWidth = this.videoWidth;
        videoHeight = this.videoHeight;
        samplePixels();
        sortPixels();
        drawPixels();
    });
}

// カメラの画像のピクセルをsampledPixels配列に入れる
function samplePixels() {
    // カメラからのピクセルを使用できるようにする
    capture.loadPixels();
    // カメラからのピクセルをサンプリングし、そのカラーを配列に保持する
    sampledPixels = [];
    for (let y = 0; y < videoHeight; y += stepSize) {
        for (let x = 0; x < videoWidth; x += stepSize) {
            const i = 4 * (y * videoWidth + x);
            const c = color(
                capture.pixels[i],
                capture.pixels[i + 1],
                capture.pixels[i + 2]
            );
            sampledPixels.push(c);
        }
    }
}

// ピクセルをソートする
function sortPixels() {
    // 配列のsort()メソッドを使って、面倒な並べ替えを行う
    // => hue値の小さい順に並ぶ
    sampledPixels.sort(sortHue);
}

function sortHue(color1, color2) {
    // 渡された2つのp5.Colorオブジェクトのどちらのhue値が大きいかを調べ、
    // color1のhue値が大きいなら1を、color2のhue値が大きいなら-1を、
    // どちらでもないなら0を返す
    const hue1 = hue(color1);
    const hue2 = hue(color2);
    if (hue1 < hue2) return -1;
    else if (hue1 > hue2) return 1;
    return 0;
}

function drawPixels() {
    // サンプリングしたビデオのサイズを計算する
    const sampledWidth = Math.ceil(videoWidth / stepSize);
    const sampledHeight = Math.ceil(videoHeight / stepSize);
    // サンプリングしたカラーごとに矩形を描画するので、矩形がキャンバスに合うように
    // 矩形をスケーリングする
    const w = width / sampledWidth;
    const h = height / sampledHeight;
    for (let i = 0; i < sampledPixels.length; i++) {
        const x = i % sampledWidth;
        const y = Math.floor(i / sampledWidth);
        fill(sampledPixels[i]);
        noStroke();
        // 矩形は、丸め誤差に対処するため、必要なサイズより少し大きく描画する
        rect(x * w, y * h, w + 1, h + 1);
    }
}

hue()関数は与えられたカラーのhue値(色相の値)を返します。hue値は下図に示す色相スケールから分かるように、0から360までの数値で表されます。sortHue()関数では、配列要素の2つのhue値を比較して結果を-1,0,1で返すので、sortPixels()関数のsampledPixels.sort(sortHue)によって、sampledPixels配列の要素はhue値の小さい順に並ぶことになります。色相については「7:カラー(Color)」で述べています。

このsketch.jsを実行すると、少し間があってから下図のような結果が描画されます。少し間があるというのは、実行に時間のかかる、遅いコードが含まれているということです。


自動的なプロファイリング

繰り返しになりますが、Chromeは自動化されたツールで助けてくれます。CPU Profilerを使用すると、時間を計測するコードを手作業で追加しなくても、コード内の各関数の実行にかかった時間を調べることができます。プロジェクトが複雑で、最適化をどこから始めれば分からない場合、この方法は問題解決の開始点として役立ちます(CPU ProfilerはJavaScript Profilerに名前が変更されたものと思われます)。

コードをプロファイリングするには、デベロッパーツールを開き、パネル右上にある[・・・]が3つ縦に並んだボタン([Customize and control DevTools])のクリックで表示されるメニューから[More tools]->[JavaScript Profiler]を選びます。

コードの時間計測を開始するには、[JavaScript Profiler]パネル下部にある[Start]ボタンをクリックします。十分なサンプルが記録できたら、[Stop]ボタンをクリックして結果をみます(上記サンプルを試す場合には、setup()関数での処理の時間を調べたいので、計測開始後にページを再読込します)。

上記サンプルの場合には、Webカメラからイメージを取得し、そのピクセルをhue値でソートして、その結果をキャンバスに描画するp5スケッチの記録です。このsketch.jsには次の4つの関数が含まれています。

  • samplePixels – カメラからピクセルのセットを取得(サンプリング)する
  • sortPixels – サンプリングしたピクセルをソートする
  • sortHue – 並べる順番を決めるために、2つのカラーのhue値を比較する
  • drawPixels – ピクセルを、着色した矩形として描画する

[JavaScript Profiler]のデフォルトビュー([Heavy(Bottom up)])は計測に関係した関数の表です。ここには、コード内の全関数に関する”self”と”total”時間が表示されます。”self”時間は、ほかの関数への呼び出しを除いた、関数内のステートメントにかかった時間の量で、”total”時間は、その関数と、その関数が呼び出した全部の関数の実行にかかった時間の量です。

“total”時間からは、samplePixels()とdrawPixels()、sortPixels()にかかった時間の量はおおよそ等しいことが分かります。したがって、これらはどれも最適化の候補だと言えます。最大の効果が得られるもっとも容易な最適化はおそらく、カメラのフレームからサンプリングするピクセル数を減らすことでしょう。これにより、3つの関数はどれもスピードアップするでしょう。(永井注:3つの関数の”total”時間はとても近いとは言えず、ほぼ等しいと思えるのは”self”時間ですが、ここでは原文のまま訳しています。またこのサンプルの高速化を図るには、サンプリングする量を減らせばよいことは、[JavaScript Profiler]を使わなくても、想像はつきます)。

ビューを[Heavy (Bottom Up)]から[Chart]に切り替えると、概要がより理解でき、記録のインタラクティブな調査が行えます。

[JavaScript Profiler]は関数名の表を提示するので、コードで使用する関数には名前をつけることが重要になります。したがって以下の2つを実行すべきです。

  • p5関数が”minified”されない名前を持つように、”p5.min.js”ではなく”p5.js”を用いる
  • 無名関数の使用は避ける

詳細は「JavaScript 実行の高速化」ページを参照してください。


Chromeのデベロッパーツールはその後さらに改変され、[JavaScript Profiler]の機能は[Performance]タブに移ったようです。詳細は「Chrome DevTools: JavaScript CPU Profiling in Chrome 58」で読むことができます。


ベンチマーク

p5.js関数のパフォーマンスに疑いを持ったり、p5.jsに自分でパフォーマンスの最適化を実装してみようと思われる方は、ぜひp5.jsのベンチマークシステムを調べるべきです。

p5 パフォーマンスティップス

本チュートリアルは、7/21/16時点のp5.js masterブランチのビルドを使って書かれています。これは、ここで述べている機能や最適化の中には、p5.js Webサイトでまだ公開されていないものがあるかもしれない、ということです。materブランチから自分でカスタムビルドを作成したいと思われる方は、この指示にしたがってください。

FES(Friendly Error System)の無効化

p5.min.jsでなく、圧縮されていないp5.jsファイルを使用するときには、たとえば関数に期待されない引数を入力したような場合などに警告を表示するフレンドリーエラーシステムが働きます。このエラーチェックシステムはコードの処理速度を大幅に低下させる恐れがあります(場合によっては10倍まで)。

この機能は、スケッチの冒頭に1行コードを書くだけで無効化できます。

p5.disableFriendlyErrors = true; // FESを無効化

function setup() {
    // セットアップ作業
}

function draw() {
    // 描画作業
}

これにより、パフォーマンスを低下させる(引数チェックのような)FESは無効になりますが、パフォーマンスに影響しないフレンドリーエラー(ファイルのロードに失敗した場合の説明エラーや、グローバル空間でp5.js関数をオーバーライドしようとした場合の警告など)はそのまま残ります。フレンドリーエラーシステムについては”p5.js Friendly Error System (FES)“ページでさらに学ぶことができます。

プラットフォームの切り替え

可能なら、プラットフォームの切り替えを試すことができます。p5.js v0.5.2 と p5 エディタ v0.6.0の時点では、

  • Chromeはp5エディタよりも優秀
  • ChromeはFirefoxとIE、Edgeよりも優秀
  • FirefoxはIE、Edgeよりも優秀です

これは、実行しようとしている特定のコードや、使用しているプラットフォームのバージョンによって変わりますが、一般的に言えることです。実際のサンプルコードは”p5.js-website/src/assets/learn/performance/code/platforms-test/“で見ることができます。

ボトルネックでネイティブJavaScriptを使用する

パフォーマンスのボトルネックがコードのどこにあるか分かっている場合には、p5メソッドではなくネイティブのJavaScriptメソッドを使うことで、ボトルネックの部分を高速化できます。

多くのp5メソッドにはオーバーヘッド(付帯的な作業)がともないます。たとえばsin()関数は、p5が度数モードなのかラジアンモードなのかを、サインを計算する前に調べる必要があり、random()関数は、渡されたものが最大値なのか最小値なのかまたはその両方なのかを、ランダム値を計算する前に調べる必要があります。このどちらの場合でも、JavaScriptネイティブのMath.sin()やMath.random()が使用できます。

スピードアップの程度は、使用しているp5メソッドによって変わります。v0.5.3では多くのメソッドが最適化されています(たとえばabs()やsqrt()、log()など)が、それでもMath.random()やMath.sin()、Math.min()を使った方が、これらのp5版を使うよりもパフォーマンスはアップします。これは特にp5エディタのv0.6.0において顕著です。サンプルコードは”p5.js-website/src/assets/learn/performance/code/native-vs-p5/“で見ることができます。

イメージ処理
サンプリング/リサイジング

イメージのピクセルをループするときには、ただイメージのサイズを小さくするかサンプリング(全部ではなく標本抽出)することで、容易にパフォーマンスの向上が得られます。1,000 x 1,000 のイメージでは1,000,000ピクセルを対象にループを繰り返すわけですが、半分の500 x 500(250,000ピクセル)に切り分けると、繰り返しは前の1/4でよくなります。これはかなりの節約です。

リサイジング(サイズ変更)とサンプリングについてはいくつかの選択肢があります。

  1. イメージを、実行以前にPhotoshopやGIMPなどを使ってサイズ変更しておく。サイズ変更のアルゴリズムが制御でき、イメージをシャープにするフィルターが適用できるので、最高品質の縮小画像が作成できる
  2. イメージを、p5.Imageのresize()メソッドを使ってリサイズする。ただし、どのようにダウンサンプリング補間する(サンプリングの頻度を下げ補間する)かはブラウザ任せになる(つまり十分な制御は得られない)。
  3. ピクセルを1つおき(または2つおき、3つおきなど)にサンプリングする。単純で効果的だが、スキップしすぎると、イメージの細部が失われる可能性がある

具体的なサンプルはp5.js-website/src/assets/learn/performance/code/resizing-images/にあります。

以下はそのsketch.jsのコードです。

// フレンドリーエラーシステムを無効にする
p5.disableFriendlyErrors = true;

let originalImage; //  元のイメージ 1200 x  800
let preshrunkImage; // 事前に縮小したイメージ 120 x 80

let stepSize;
const iterations = 1; // 繰り返し回数

function preload() {
    //originalImage = loadImage("images/floral-original.jpg");
    //preshrunkImage = loadImage("images/floral-tiny-sharpened.jpg");
    originalImage = loadImage("images/blackberry-original.jpg");
    preshrunkImage = loadImage("images/blackberry-tiny-sharpened.jpg");
}

function setup() {
    // キャンバスの幅は元のイメージの4倍にする
    createCanvas(originalImage.width * 4, originalImage.height);
    background(255);
    // 元のイメージの幅は縮小したイメージの10倍なので、stepSizeは10になる
    stepSize = originalImage.width / preshrunkImage.width;

    resizeBeforeRuntime();
    translate(originalImage.width, 0);
    sampleImage();
    translate(originalImage.width, 0);
    imageResize();
    translate(originalImage.width, 0);
    iterativeImageResize();
}

// 事前に縮小したイメージのピクセルを全部使って、元のイメージを再現しようとする方法
function resizeBeforeRuntime() {
    // スタート時間をメモ
    const start = millis();
    // 走査する対象イメージの幅と高さ
    const w = preshrunkImage.width;
    const h = preshrunkImage.height;
    // 繰り返し
    for (let i = 0; i < iterations; i++) {
        // 対象イメージのピクセルデータにアクセスできるようにする
        preshrunkImage.loadPixels();
        // 縮小したイメージの全ピクセルを走査し、そのカラーで10x10の矩形を描く
        for (let x = 0; x < w; x++) {
            for (let y = 0; y < h; y++) {
                // ピクセルデータの最初の数値(r値)を特定
                // r,g,b,a
                const index = 4 * (y * w + x);
                const r = preshrunkImage.pixels[index]; // r値
                const g = preshrunkImage.pixels[index + 1]; // g値はr値の1つ右
                const b = preshrunkImage.pixels[index + 2]; // b値はr値の2つ右
                const a = preshrunkImage.pixels[index + 3]; // a値はr値の3つ右 1ピクセルはここまで
                fill(r, g, b, a);
                noStroke();
                rect(x * stepSize, y * stepSize, stepSize, stepSize);
            }
        }
    }
    // 開始してからかかった時間
    const elapsed = millis() - start;
    print("実行前リサイズ:" + elapsed.toFixed(2) + "ms")
}

// 元のイメージのピクセルを10飛ばしで使って、元のイメージを再現しようとする方法
function sampleImage() {
    const start = millis();

    const w = originalImage.width;
    const h = originalImage.height;

    for (var i = 0; i < iterations; i++) {
        originalImage.loadPixels();

        // 元のイメージのピクセルを10飛ばしで走査し、そのカラーで10x10の矩形を描く
        for (let x = 0; x < w; x += stepSize) {
            for (let y = 0; y < h; y += stepSize) {
                const index = 4 * (y * w + x);
                const r = originalImage.pixels[index];
                const g = originalImage.pixels[index + 1];
                const b = originalImage.pixels[index + 2];
                const a = originalImage.pixels[index + 3];
                fill(r, g, b, a);
                noStroke();
                rect(x, y, stepSize, stepSize);
            }
        }

    }
    // 開始してからかかった時間
    const elapsed = millis() - start;
    print("サンプリング: " + elapsed.toFixed(2) + "ms")
}

// p5.Image.resize()を使って元のイメージを縮小し、
// そのピクセルを全部使って元のイメージを再現しようとする方法
function imageResize() {
    // リサイズしてイメージを破壊してもいいようにイメージのコピーを使う
    const originalWidth = originalImage.width;
    const originalHeight = originalImage.height;

    // 元のイメージと同じサイズのp5.Imageオブジェクト
    const img = new p5.Image(originalWidth, originalHeight);
    // そのイメージに元のイメージをコピー
    img.copy(originalImage, 0, 0, originalWidth, originalHeight, 0, 0,
        originalWidth, originalHeight);

    // 縮小したイメージのサイズ
    const w = preshrunkImage.width;
    const h = preshrunkImage.height;

    const start = millis();
    // p5.Imageのresize()メソッドで、元のイメージのコピーのサイズを、
    // 縮小したイメージのサイズに縮小する
    img.resize(w, h);

    for (let i = 0; i < iterations; i++) {
        img.loadPixels();

        // 縮小した元のイメージのコピーの全ピクセルを走査し、そのカラーで10x10の矩形を描く
        for (let x = 0; x < w; x++) {
            for (let y = 0; y < h; y++) {
                const index = 4 * (y * w + x);
                const r = img.pixels[index];
                const g = img.pixels[index + 1];
                const b = img.pixels[index + 2];
                const a = img.pixels[index + 3];
                fill(r, g, b, a);
                noStroke();
                rect(x * stepSize, y * stepSize, stepSize, stepSize);
            }
        }
    }
    // 開始してからかかった時間
    const elapsed = millis() - start;
    print("p5.Image.resize(): " + elapsed.toFixed(2) + "ms")
}

function iterativeImageResize() {
    // 元のイメージのコピーを作成
    const originalWidth = originalImage.width;
    const originalHeight = originalImage.height;
    const img = new p5.Image(originalWidth, originalHeight);
    img.copy(originalImage, 0, 0, originalWidth, originalHeight, 0, 0,
        originalWidth, originalHeight);

    const start = millis();

    // イメージを可能な限り、繰り返し半分にリサイズする。
    // 実際に用いるときには、半分にする回数を決める(1回から2回)。
    // 次のコードでは、デモ目的で極端なリサイジングを行っている(3回)
    // Source: http://stackoverflow.com/a/19262385

    // 目標とするサイズは1/10 => imgのサイズを1/10まで小さくしたい
    const targetWidth = preshrunkImage.width;
    const targetHeight = preshrunkImage.height;

    // whileループでリサイズをつづける
    // 1200x800のイメージは、600x400,300x200,150x100と半分ずつ小さくなる
    while (img.width > (2 * targetWidth) && img.height > (2 * targetHeight)) {
        img.resize(img.width / 2, img.height / 2);
        print(img.width, img.height);
    }

    // 最後、目標サイズに合うようリサイズする => 150x100が最終的に120x80に調整される
    if (img.width !== targetWidth || img.height !== targetHeight) {
        img.resize(targetWidth, targetHeight)
    }
    print(img.width, img.height);
    // 縮小が終わったイメージの幅と高さ
    const w = img.width;
    const h = img.height;

    for (var i = 0; i < iterations; i++) {
        img.loadPixels();

        // imgの全ピクセルを走査し、そのカラーで10x10の矩形を描く
        for (let x = 0; x < w; x++) {
            for (let y = 0; y < h; y++) {
                const index = 4 * (y * w + x);
                const r = img.pixels[index];
                const g = img.pixels[index + 1];
                const b = img.pixels[index + 2];
                const a = img.pixels[index + 3];
                fill(r, g, b, a);
                noStroke();
                rect(x * stepSize, y * stepSize, stepSize, stepSize);
            }
        }

    }
    // 開始してからかかった時間
    const elapsed = millis() - start;
    print("p5.Image.resize()を繰り返す: " + elapsed.toFixed(2) + "ms")
}

可能な場合には事前にイメージサイズを変更しておくことで、最終的な見え方が最も制御できます。無理な場合には(ビデオのフレーム処理など)、サンプリングやリサイジングが有効な選択肢になります。

フロントロードイメージ処理

可能な場合には、フロントロード(負荷の高い作業は早めに済ませておく)イメージ処理が最善の方法です。setup()の間にできるだけ多くのことを行っておくと、draw()ループが高速になります。これにより、スケッチのインタラクティブな部分のもたつきが回避できます。

たとえば、イメージのカラー情報が欲しい場合には、setup()時にp5.Colorオブジェクトを抽出して保持しておきます。そしてdraw()内では、必要なときに、保持したカラー情報を参照するようにします(その場で再計算するのはなく)

DOM操作

ドキュメントオブジェクトモデル(DOM)は、HTML要素を作成、操作、削除するためのアクセスを与えてくれるプログラミングインターフェースです。DOMには、パフォーマンスに大きな影響を及ぼすいくつかの落とし穴があります。

DOMの一括操作

DOMを扱っているとき、ブラウザが不必要な”リフロー”を起こすこと、つまり、DOMを変更するたびにブラウザがページの全要素をレイアウトし直すことは避けたいでしょう。やりたいのはすべてのDOMの一括変更なので、多くの小さな変更ではなく大きな1つの変更を施します。ブラウザが多くの小さな変更をひっきりなしに再レイアウトすることは、レイアウトスラッシング(レイアウトの強制的な同期が繰り返し行われる状態)と呼ばれます(リフローについては「6_1 ブラウザがWebページを表示するまでの仕組み」で少し述べています)。

リフローを引き起こすDOMのメソッドとプロパティは「What forces layout / reflow」ページに記載されています。ブラウザはできるだけ”怠け者”になろうとしてリフローを先延ばししますが、要素のoffsetLeftを求めたりするときにはリフローが必要になります。

変更を一括処理してレイアウトスラッシングを避ける方法はいくつかありますが、残念ながらp5.Elementとp5.domは現在、一括処理に十分な余地を与えていません。たとえばp5.Elementを作成すると(offsetWidthとoffsetHeightを通して)レイアウトスラッシングが発生します。

DOMのパフォーマンス問題にぶつかったときに取れそうな最善の方法は、プレーンなJavaScriptを選ぶか、fastdomのようなDOM操作ライブラリを使うかです。「p5.js-website/src/assets/learn/performance/code/reflow-dom-manipulation/」では、p5とネイティブJavaScriptでのDOM操作のパフォーマンスをテストしたコードを見ることができます。リフローを避けるネイティブJavaScriptは、p5の400倍から500倍高速です。

検索の最小化

DOM内の要素の検索は、特にdraw()内で実行しているとき、負荷が高くなる可能性があるので、要素への参照をsetup()内に保持することで、DOM検索を最小限に抑えます。たとえば、マウスカーソルからつねに逃げるように位置を変えるボタンがあるとします。

let button;

function setup () {
  // 要素への参照はsetup()に保持
  button = select("#runner");
}

function draw() {
  let x, y;
  // ボタンの移動先を決めるための何らかの作業をここで行う
  button.position(x, y);
}

パフォーマンステストのコードは「p5.js-website/src/assets/learn/performance/code/cache-dom-lookups/」を見てください。テストでは、要素をキャッシュした方がしない方よりも10倍ほど高速でした。パフォーマンスの向上は、DOMツリーの深度とセレクタの複雑さによって異なります。

数学のティップス

2点間の距離やベクトルの大きさを比較したいときは、距離の2乗や大きさの2乗(p5.Vector.magSq())と試します。パフォーマンステストのコードは「p5.js-website/src/assets/learn/performance/code/distance-squared/」を見てください。

function distSquared(x1, y1, x2, y2) {
    let dx = x2 - x1;
    let dy = y2 - y1;
    return dx * dx + dy * dy;
}

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA