18280


createHoge, createFuga, execPiyo の三つメソッドがあります。

  * createHoge, createFuga はとても大きなデータを生成する関数で、データを生成したらコールバックを呼び出すものです
  * execPiyo は、createHoge, createFuga で生成したデータを処理する関数で、データ処理が完了したらコールバックを呼び出すものです。

[code]

        createHoge(function(hoge) {
            createFuga(function(fuga) {
                execPiyo(hoge, fuga, function() {
                    console.log("1piyo done.");
                    // この時点で hoge, fuga を破棄したい
                    createHoge(function(hoge) {
                        createFuga(function(fuga) {
                            execPiyo(hoge, fuga, function() {
                                console.log("2piyo done.");
                            });
                        });
                    });
                });
            });
        });
    
[/code]

このようなコールバック呼び出しの連鎖で実行できるものとします。で、連続して execPiyo を何回も実行したい時にcreateHoge,
createFugaで生成したhoge, fuga をメモリから解放できずに困っています。

RxJSやPromiseやgeneratorを使ってcreateHoge, createFuga,
execPiyoの実行を複数回書いてみたんですが、どのやり方でもメモリを食いすぎて途中で落ちてしまいます。

補足: createHoge, createFuga, execPiyo の一連の処理は一回実行するだけなら問題ないんですが、複数回実行したいけど
callback のチェインを続けると途中でメモリが足りなくなります。

何か解決方法はありますか？


CC BY-SA 3.0
2015-10-28T13:34:19.047
18177
2015-11-01T09:56:14.147
2015-10-29T11:35:50.847
635
635
question
javascript node.js
Node.jsのコールバックの連鎖の中でメモリ解放を行う方法


hoge、fugaに直接値を入れず、  
hoge.data、fuga.dataなどのプロパティを作ってそこに入れることにしたら、  
呼び出し先でのクリアができるのでは？  
(dataという名称は適当です)

もう少し詳細に書くと、

[code]

    function createHoge(callback) {
        // 大きなデータhogeを生成
        var hoge = ...
        callback(hoge);
     }
    
[/code]

のようにしていたのを、

[code]

    function createHoge(callback) {
        // 大きなデータhogeを生成
        var hoge.data = ...
        callback(hoge);
    }
    
[/code]

として、createFugaでも同様にし、

[code]

    createHoge(function(hoge) {
        createFuga(function(fuga) {
            execPiyo(hoge, fuga, function() {
               console.log("1piyo done.");
               // この時点で hoge, fuga を破棄したい
              hoge.data = null;
              fuga.data = null;
    
[/code]

とすればうまく参照が切れるのではということです。


CC BY-SA 3.0
2015-10-30T00:39:43.230
18227
2015-10-31T01:35:53.383
2015-10-31T01:35:53.383
10103
10103
18177
answer


`createHoge`の実装によっては、`hoge`に`null`を代入しても参照を切れるとは限りません。以下のコードでは`hoge`に`null`を代入していますが、ページごと落ちます。

[code]

    function createHoge(callback) {
      // createHogeを呼ぶたびに10MB確保する
      var buffer = new ArrayBuffer(10000000); 
      callback(buffer);
    }
    
    function createFuga(callback) {
      var buffer = new ArrayBuffer(10000000);
      callback(buffer);
    }
    
    function execPiyo(a, b, callback) {
      callback();
    }
    
    
    function foo(i, n) {
      createHoge(function(hoge) {
        createFuga(function(fuga) {
          execPiyo(hoge, fuga, function() {
            console.log(i + "piyo done.");
            // この時点で hoge, fuga を破棄したい
    
            // 破棄しようとしてnull を代入しても無駄
            hoge = null;
            fuga = null;
    
            if (n < i) return;
            foo(i + 1, n);
          });
        });
      });
    }
    
    // 1000回再帰する
    foo(0, 1000);
[/code]

以下のように`createHoge`でローカル変数に参照を持たないようにすると回避できそうですが、実験するとどうもうまく参照を切れていないようです。理由はよくわかりませんが、以下のコードも落ちます。

[code]

    function createHoge(callback) {
      // ローカル変数からバッファを参照していない
      callback(new ArrayBuffer(10000000));
    }
    
    function createFuga(callback) {
      callback(new ArrayBuffer(10000000));
    }
    
    function execPiyo(a, b, callback) {
      callback();
    }
    
    
    function foo(i, n) {
      createHoge(function(hoge) {
        createFuga(function(fuga) {
          execPiyo(hoge, fuga, function() {
            console.log(i + "piyo done.");
            // この時点で hoge, fuga を破棄したい
    
            hoge = null;
            fuga = null;
    
            if (n < i) return;
            foo(i + 1, n);
          });
        });
      });
    }
    
    
    foo(0, 1000);
[/code]

`createHoge`の修正が難しい場合もあるでしょうし、`createHoge`を呼び出している側からできるひとつの方法としては、以下のように`setTimeout`などの非同期な処理を挟むという方法があります。こうすると実際にはコールスタックが積まれていかないので、一見再帰呼び出しのように見えますが正常に完了できます。

[code]

    function createHoge(callback) {
      var buffer = new ArrayBuffer(10000000);
      callback(buffer);
    }
    
    function createFuga(callback) {
      var buffer = new ArrayBuffer(10000000);
      callback(buffer);
    }
    
    function execPiyo(a, b, callback) {
      callback();
    }
    
    
    function foo(i, n) {
      createHoge(function(hoge) {
        createFuga(function(fuga) {
          execPiyo(hoge, fuga, function() {
            console.log(i + "piyo done.");
            // この時点で hoge, fuga を破棄したい
    
            if (n < i) return;
            setTimeout(function() {
              foo(i + 1, n);
            }, 0)
          });
        });
      });
    }
    
    
    foo(0, 1000);
[/code]

なお、この方法では一度の呼び出しに最低でも数ミリ秒のウェイトがかかることになりますが、  
もっと短い時間で非同期処理を繰り返す特殊なテクニックもあります。


CC BY-SA 3.0
2015-10-31T15:30:17.393
18280
2015-11-01T09:56:14.147
2015-11-01T09:56:14.147
10330
10330
18177
answer


[Hibiya
Ryutoさんの書かれた最初のコード](https://ja.stackoverflow.com/a/18280)で言いますと、プログラム終了まで参照を保持し続けているのは`hoge`や`fuga`の他、`buffer`,
`a`, `b`も同様です。

この参照を切るために、強いて形をなるべく変えずに修正すると次のようなコードになります(アプローチとしては[eripongさんの示されている回答](https://ja.stackoverflow.com/a/18227)と同じです)。

[code]

    function createHoge(callback) {
      var buffer = [new ArrayBuffer(10000000)];
      callback(buffer);
    }
    
    function createFuga(callback) {
      var buffer = [new ArrayBuffer(10000000)];
      callback(buffer);
    }
    
    function execPiyo(a, b, callback) {
      callback();
    }
    
    
    function foo(i, n) {
      createHoge(function(hoge) {
        createFuga(function(fuga) {
          execPiyo(hoge, fuga, function() {
            console.log(i + "piyo done.");
    
            // この時点で buffer[0] を破棄したい
            hoge[0] = null;
            fuga[0] = null;
    
            if (n < i) return;
            foo(i + 1, n);
          });
        });
      });
    }
    
    
    foo(0, 1000);
[/code]

`buffer`を要素数1の配列とし、この配列の第1要素に本来渡したいオブジェクト(大容量オブジェクト)をセットします。  
この配列を引数として渡せば、呼び出し先で大容量オブジェクトの参照を切ることができます。


CC BY-SA 3.0
2015-11-01T08:55:10.080
18299
2015-11-01T09:13:23.087
2017-04-13T12:52:38.920
-1
2808
18177
answer