ラベル 比較 の投稿を表示しています。 すべての投稿を表示
ラベル 比較 の投稿を表示しています。 すべての投稿を表示

2018年11月19日

CPU負荷を抑えて重い処理を軽くするJavaScriptライブラリ「chillout.js」

「chillout.js」とは?


chillout.js は「処理時間を短くする」という物理的な高速化とは違い、CPU負荷を抑えてリソースに余裕を持たせ、重い処理でも軽く感じさせることでユーザーにとって体感的・心理的な高速化につなげる JavaScriptライブラリです。


重い処理から開放されるために


JavaScriptでfor文など繰り返し(ループ)処理をしたとき、重い処理により一瞬でもページが固まってしまうことがあります。
そんなときブラウザ画面は読み込み中のままローディングのくるくるが止まらなかったりゲーム中にマウスが効かなくてカクカクだったり、
Webページだけじゃなくnode.jsなど画面のいらない処理でも高CPU負荷が続くとマシンごと重くなって大変です。

特にスペックの低いPCやタブレットの場合、 CPU使用率100%の状態では加熱してきて冷却ファンが高回転になり、そのまま使い続けると冷却が追いつかずに熱暴走してしまう可能性もあるので、うっかり重いページを開いてファンが「ウイーン!」って言い出すとヒヤヒヤします。

最近のPCは性能がよくなってるのでそんな事態になりにくいかもしれませんが、自分のPCも真夏の気温で動かなくなって修理にだしたのはまだ新しい記憶です😭 (直りました)。

「CPUファンが回りっぱなしかと思ったらフリーズして電源が落ちた」なんてことになると作業途中だったらやり直しになるし、HDDやSSDなどの内部にもダメージが残るかもしれないのでなるべく処理を軽くしたいところです。

JavaScriptでCPU負荷を抑えるには?


重い処理のほとんどはループ処理によって発生します。ループの中でさらにループ、その中でさらにループ…。
ループ間の処理が重くなってくるとマシンのCPUは休む暇なく動き、結果として内部が熱くなるため冷却ファンをたくさん回すことになります。
単純に考えた場合、そうならないようループの途中で一定時間処理を休止させればいいんですが、それができません。
JavaScriptには一定の時間休む sleep のような機能がないからです

そこで、sleepするにはどうするか?というと「非同期」でループ処理します。

CPUを休ませるために非同期でループ処理する


JavaScriptのループは for文や while文、また Array に対して forEachなどがありそれぞれ同期で処理されますが、上述したように sleep して休ませることができないため、これらのループ処理を非同期で実現 します。

同期処理を非同期化するには、
  • setTimeoutを使う
  • process.nextTickを使う(node.js)
  • DOMイベントを使う
  • MessageChannelを使う

などの方法があり、一連の処理を Promise と組み合わせると非同期ループが実現できます

(setInterval や requestAnimationFrame を使うこともできますが、これらは精度を保とうとより正確にループしようとするため今回の用途には不向きです。)

今までも CPU負荷を抑えるためのライブラリをいくつか作ってきたのですが、当時は Promise という便利なものがJavaScriptになかったので自前で Deferred と呼ばれる Promise のようなものを定義していました。そのためにライブラリのサイズが大きくなるしAPIがガラパゴス化してたんですが、ようやくシンプルな実装にできたと思います。

処理時間を短くするんじゃなく、体感速度を向上させる心理的な高速化



処理の高速化というと、とにかく1ミリ秒でも処理時間を短くすることが手法とされますが、Webページやアプリ、ゲームといった、人が画面を見たり操作する場合 心理的に「速い」と感じればユーザーのストレスが減り結果として高速化につながります


Twitterでいい例があったので紹介。
これはエクセルマクロの話ですが 処理時間を短くするんじゃなく待ち時間を退屈させないようにして体感速度を向上させて、心理的な高速化しています。


CPU負荷を抑えてループ処理を軽くするJavaScriptライブラリ「chillout.js」



冒頭が長くなっちゃいましたが、ライブラリの紹介です。

chillout.js は、ループ処理中に適度な休憩(sleep)を入れてあげ、カクカクする重さを感じさせないライブラリ です。
また、処理が重くなるとでる「警告: 応答のないスクリプト」というブラウザ警告なしでJavaScriptを実行できます

ループ処理が重いときにはCPUが休まるくらいの休止時間、処理が速いときには休止時間なしか、わずかな休止時間をいれ本来のループを邪魔しないようにします

ベンチマーク


for文と chillout.repeat を比較します。

function heavyProcess() {
  var v;
  for (var i = 0; i < 5000; i++) {
    for (var j = 0; j < 5000; j++) {
      v = i * j;
    }
  }
  return v;
}

例として上のような重い処理(テスト用に5000*5000回繰り返す処理)に対して、

for文

JavaScriptのfor文。

var time = Date.now();
for (var i = 0; i < 1000; i++) {
  heavyProcess();
}
var processingTime = Date.now() - time;
console.log(processingTime);

CPUグラフ:
  • 処理時間: 107510ms.
  • CPU平均使用率(Nodeプロセス): 97.13%

CPUグラフは上のようになり、CPU均使用率は 100% までいってないけど 97.13% になりました。

chillout.repeat

これを「chillout.js」のメソッド chillout.repeat で実行します。

var time = Date.now();
chillout.repeat(1000, function(i) {
  heavyProcess();
}).then(function() {
  var processingTime = Date.now() - time;
  console.log(processingTime);
});

CPUグラフ:
  • 処理時間: 138432ms.
  • CPU平均使用率(Nodeプロセス): 73.88%

ベンチマーク結果


  ForStatement (for文) chillout.repeat
処理時間 107,510ms. 138,432ms.
CPU平均使用率(Nodeプロセス) 97.13% 73.88%


グラフでは少しわかりにくいかもしれませんが、CPU平均使用率は for文 97.13% に対し、 chillout.repeat は 73.88% となり、for文よりもCPU使用率が抑えられてます

そのかわりループ中にCPUを休ませてるため処理時間は 107,510ms から 138,432ms となり少しかかっています。
もっとCPU使用率を抑えるには単純に休止時間を増やせばいいんですが、そうすると処理時間が長くなるため適度なバランスにしています。

画面上ではわかりませんが、for文を実行してるときはCPUファンが「ウイーン!」と激しく回っていたのに対し、chillout.repeat のときはファンが静かめでした(環境によってそこまで変わらないかもですが)。

chillout.js は、処理速度を少し遅くするかわりに低いCPU使用率で安定してJavaScriptを実行できる特徴があります。

特にブラウザやゲームなど、人が画面を見て操作するJavaScriptのパフォーマンスにおいて最も重要なことの一つは、 数値的な速度だけでなく、安定したレスポンスによってユーザーにストレスなく動かすことと考えています。

(ベンチマークスペック: Windows8.1 タブレット / Intel(R) Atom(TM) CPU Z3740 1.33GHz)

API



for文やwhile文、Array.forEachに対応するメソッド4つがあります。そのうち2つ紹介。

chillout.repeat

for文のような動きをします。

// 5回繰り返す
chillout.repeat(5, function(i) {
  console.log(i);
}).then(function() {
  console.log('done');
});
// 0
// 1
// 2
// 3
// 4
// 'done'

chillout.forEach

Array.forEachと同じように動く。

// 配列を回す
var values = ['a', 'b', 'c'];
chillout.forEach(values, function(value) {
  console.log(value);
}).then(function() {
  console.log('done');
});

// 'a'
// 'b'
// 'c'
// 'done'

非同期処理で Promise が返されるため then で繋ぎます。
(他のAPIの詳細は chillout.jsのGitHub を参考ください。)

比較表


既存のJavaScriptループを chillout.js のAPIに置き換えると、大抵の場合 CPU使用率を抑えて実行できます。

変換例:

JavaScript chillout.jsの場合
[1, 2, 3].forEach(function(v, i) {}) chillout.forEach([1, 2, 3], function(v, i) {})
for (i = 0; i < 5; i++) {} chillout.repeat(5, function(i) {})
for (i = 10; i < 20; i += 2) {} chillout.repeat({ start: 10, step: 2, done: 20 }, function(i) {})
while (true) {} chillout.till(function() {})
while (cond()) {} chillout.till(function() {
  if (!cond()) return chillout.StopIteration;
})
for (value of [1, 2, 3]) {} chillout.forOf([1, 2, 3], function(value) {})


GitHub / chillout.js



GitHub / chillout.js

※このブログの内容は常に更新してるわけじゃないので古くなってる可能性があります。特にAPIの最新の情報は GitHub / chillout.jsを参考ください。


2012年4月22日

JavaScriptライブラリ Pot.js 関連リンクまとめ

Pot.js 関連のリンクまとめ

JavaScript ライブラリ Pot.js に関する記事やサンプルの紹介です。
Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。
MOONGIFT さんの記事 で紹介されたのもあって、せっかくなのでまとめてみました。



Pot.js / PotLite.js

Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。
PotLite.js は Pot.js の非同期な部分だけを抽出したライトバージョンです。

ダウンロード

マニュアル

その他の情報についてはマニュアル/マニュアルからのリンク から参照ください。

レポジトリ




その他、なにか問題・バグ・感想・指摘などあれば、
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。



2012年3月12日

jsFiddleだらけ-JavaScriptライブラリPot.js+PotLite.jsリリースノート



Pot.js 1.15 1.16PotLite.js 1.32 1.33 リリースしました。

2012-03-13 追記:
Pot.js 1.15 と PotLite.js 1.33 はバギーなためアップデートしました。詳細
2012-03-13 時点の最新は Pot.js 1.16PotLite.js 1.34 です。最新にアップデートお願いします。。


このバージョンでは、
  • 文字列処理の高速化
  • 重くなりそうな処理を非同期化
などを適応しました。

文字列処理の高速化

先日、ふと思って String.fromCharCode を呼ばずに
U+0000 - U+FFFF の配列をあらかじめ作成して インデックスに対応させたらどうなのかなって
ベンチマークとってみました。


結果として、String.fromCharCode(c) のような
apply で配列を使わない場合、かなり高速化できました。
メモリ消費も文字列などは気にするほどじゃなかった。
String.fromCharCode.apply(null, [...]); のような場合は逆に遅くなるので
従来通り String.fromCharCode を使用しています。

(この件はあほなミスをしてて@gochoさんにつっこまれて助かりました)

重くなりそうな処理を非同期化

重くなりそうな処理とは、巨大な文字列が渡される可能性のある関数や、
ループ回数が未知の処理などです。

途中で「応答のないスクリプト」警告なんぞ でてしまったらめんどうです。
そういった対処や負荷軽減も含めて 関数オブジェクトに deferred というメソッドを持たせました。

例えば Pot.md5(string); が同期実行に対して
Pot.md5.deferred(string); は、非同期で実行します。


圧縮・解凍

文字列を LZ77 アルゴリズムをベースに圧縮・解凍する
Pot.Archive.AlphamericString も同様に非同期化しています。

その他のサンプル

他にもいくつかテスト用にサンプルがあったので紹介します。



Pot.js / PotLite.js

Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。

PotLite.js は Pot.js の非同期な部分だけを抽出したライトバージョンです。

ダウンロード

マニュアル

その他の情報についてはマニュアル/マニュアルからのリンク から参照ください。

レポジトリ




その他、なにか問題・バグ・感想・指摘などあれば、
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。


2012年2月11日

JavaScript whileループとPot.Deferred.forEverイテレータでCPU使用率を比較-Pot.js+PotLite.jsリリースノート

Pot.js 1.13PotLite.js 1.30 リリースしました。

Pot.js 1.13 と PotLite.js 1.30 では、主に内部ループ処理を大幅に高速化しました。
(そろそろ ChangeLog 作らないとまずいかも…)

あとは、変数宣言とか 例の (function(){}()) とか (結局直してる)
細かい修正などです。

先日、Hacker NewsEcho JS で Pot.js が紹介されました (ありがとうございます)。
でもアクセスが今までの 1000 倍くらいになって、すごいことになってちょっとビビった。
(API サーバのほうは適当に調節しておいたので大丈夫だった。よかった。。)


それで Pot.js の本来の存在意義ですが、

実行環境の CPU に負荷をかけることなく JavaScript が実行できる。

といったことが本来の目的であり、常に追求している目標でもあります。
Deferred が重点にとらわれがちですが、Pot.Deferred はそれの足がかりであり、
Pot.Deferred だけがライブラリの中核ではないので、いろいろ使ってみてください。

新しいデザインのリファレンス

現在、新しいデザインでリファレンスを書き直しています。


前は 全部で 1 ファイルになっちゃってて、どんどん重くなるし更新もやり難くて
どうしようもなかったのですが、今回は 各ページを非同期読み込みにして
英語版と日本語版で見れるようにして、戻るボタンとかも再現したり、がんばってます。

でもまだ作成途中です (2012-02-11 現在)。

ある程度完成したら 本来の /index.html に移動させようと思ってるんですが、
先ほどの Echo JS などが /test-for-new-design/ にリンクしちゃってるもんだから
どうしようかと考え中。(たぶん リダイレクトか、もうこのままでいいか…)。

CPU 比較を実行

jsFiddle のほうで
JavaScript の while ループと、Pot.js の非同期イテレータとで CPU 使用率の比較を書いてみました。
前に書いた Pot.js イテレータと jQuery.each と for ループの CPU 使用率をグラフで比較
と同じ事ですが、実際に実行できます (ただし負荷テストなのでブラウザクラッシュに気をつけてください)。
もっとも最近のブラウザはループでクラッシュしないと思いますが。。

CPU 使用率は、Web ブラウザからの JavaScript では取得できないと思うので、
CPU モニタリングするアプリとか、
なければ Win ならタスクマネージャから「パフォーマンス」→ 「CPU 使用率」
を見ながら実行してみるとわかりやすいと思います。


今回は while 文と Pot.Deferred.forEver の比較ですが、
Pot.js 非同期イテレータは CPU 負荷が一定量に抑えられたループが可能になっていると思います。
ただし、この例のような処理だと実行時間はある程度伸びてしまいます。

実行時間に関しては 速度調整 が可能なので、処理に応じたスピードを選ぶこともできます。
(このへんは今後改善したいです)。

サンプルのソースコード

jsFiddle でも確認できますが、今回使用したサンプルのソースコードです。
ループ処理の部分だけ抽出しています。

JavaScript while ループを使ったソースコード:
// while で同期ループして圧縮
compressSync : function(s) {
    var a = 53300, b, c, d, e, f, g = -1,
        h, i, r = [], x = String.fromCharCode;

    s = new Array(a--).join(' ') + s;
    while ((b = s.substr(a, 256))) {
        for (c = 2, i = b.length; c <= i; ++c) {
            d = s.substring(
                a - 52275,
                a + c - 1
            ).lastIndexOf(b.substring(0, c));
            if (!~d) {
                break;
            }
            e = d;
        }
        if (c === 2 || c === 3 && f === g) {
            f = g;
            h = s.charCodeAt(a++);
            r.push(
                x(h >> 8 & 255),
                x(h & 255)
            );
        } else {
            r.push(
                x((e >> 8 & 255) | 65280),
                x(e & 255),
                x(c - 3)
            );
            a += c - 1;
        }
    }
    return r.join('');
}

Pot.js 非同期イテレータ (今回は Pot.Deferred.forEver) を使ったソースコード:
// Pot.js 非同期イテレータで圧縮
compressAsync : function(s) {
    var a = 53300, b, c, d, e, f, g = -1,
        h, i, r = [], x = String.fromCharCode;

    var deferred = new Pot.Deferred();

    s = new Array(a--).join(' ') + s;

    // whileループを forEver に置き換え
    Pot.Deferred.forEver[SPEED](function() {

        b = s.substr(a, 256);
        if (!b) {
            throw Pot.StopIteration;
        }

        for (c = 2, i = b.length; c <= i; ++c) {
            d = s.substring(
                a - 52275,
                a + c - 1
            ).lastIndexOf(b.substring(0, c));
            if (!~d) {
                break;
            }
            e = d;
        }
        if (c === 2 || c === 3 && f === g) {
            f = g;
            h = s.charCodeAt(a++);
            r.push(
                x(h >> 8 & 255),
                x(h & 255)
            );
        } else {
            r.push(
                x((e >> 8 & 255) | 65280),
                x(e & 255),
                x(c - 3)
            );
            a += c - 1;
        }
    }).then(function() {
        deferred.begin(r.join(''));
    }, function(err) {
        deferred.raise(err);
    });
    return deferred;
}

同期か非同期かの違いがありますが、
単に、メインの while 文を Pot.Deferred.forEver に変えてるだけです。
forEver は、StopIteration が throw されるまで、永久にループする関数です。

あと、この関数は LZ77アルゴリズムによる圧縮関数をJavaScript最短コードで | 圧縮電子精神音楽浮遊構造体 (見れないかも)
で作ったソースコードをちょっとだけ手直ししたものです。
文字列を圧縮解凍します。

Pot.js / PotLite.js

Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。

PotLite.js は Pot.js の非同期な部分だけを抽出したライトバージョンです。

ダウンロード

マニュアル

マニュアルは上に書いたように 2012-02-11 現在、まだすべてのオブジェクトの解説ができてません。。

その他の情報についてはマニュアル/マニュアルからのリンク から参照ください。

レポジトリ




その他、なにか問題・バグ・感想・指摘などあれば、
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。