ラベル 高速化 の投稿を表示しています。 すべての投稿を表示
ラベル 高速化 の投稿を表示しています。 すべての投稿を表示

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年9月4日

重い処理を軽くできるJavaScriptライブラリ作ってみた

追記: この記事のものは古くメンテナンスされていません。
CPU負荷をかけずにループ処理するJavaScriptライブラリは「CPU負荷を抑えて重い処理を軽くするJavaScriptライブラリ「chillout.js」 | 圧縮電子どうのこうの」を参照ください。

lazyIter.js

lazyIter.js は CPU 負荷をかけずにループ処理が可能な JavaScript ライブラリです。

重いループ処理などを軽くすることができます。
ループ内で処理の負荷に応じて遅延させ、結果的に重い処理は軽くなり
元々軽い処理は、ほぼ従来の速度のまま実行できます。

処理の負荷だけでなく、時間のかかるループ処理は
「応答のないスクリプト」警告がでてしまいます。
そのような警告なしで実行できるのも特徴の一つです。

どうに違いがあるのかは CPU 負荷テストページ で実際に試してみてください

Pot.js が実装しているイテレータから派生しています。
パフォーマンスなどは Pot.js のイテレータとほとんど同じです

ループ処理は非同期で実行されます。

使い方は Pot.js のイテレータ と同じですが、Deferred を使っていないので
引数にコールバックを指定する点が違います。

動作環境

主な Web ブラウザで動作します
  • Mozilla Firefox *
  • Internet Explorer 6+
  • Safari *
  • Opera *
  • Google Chrome *
また、以下の環境でも動作します
  • Greasemonkey (userscript)
  • Mozilla Firefox Add-On (on XUL)
  • Node.js
  • Other non-browser environment

lazyIter.js は、CommonJS などに対応しています

使い方

lazyiter.js を読み込みます (Webページの場合)

<script src="lazyiter.js"></script>

読み込むと、lazyIter というオブジェクトが定義されます (定義されるのは lazyIter だけです)。

lazyIter.forEach :

オブジェクトや配列に対してループ実行します

// forEach:
//
// lazyIter.forEach(object, func, callback [, speed [, context]])

// Array.
lazyIter.forEach(['a', 'b', 'c'], function(val, i) {
  console.log(i + ':' + val);
}, function() {
  console.log('End loop');
});
// result:
//   '0:a'
//   '1:b'
//   '2:c'
//   'End loop'

// Object.
lazyIter.forEach({a: 1, b: 2, c: 3}, function(val, key) {
  console.log(key + ':' + val);
}, function() {
  console.log('End loop');
});
// result:
//   'a:1'
//   'b:2'
//   'c:3'
//   'End loop'

lazyIter.repeat :

指定の数だけループ実行します

// repeat:
//
// lazyIter.repeat(max, func, callback [, speed [, context]])

// Specify as number.
lazyIter.repeat(10, function(i) {
  console.log(i);
}, function() {
  console.log('End loop');
});
// result:
//   0
//   1
//   2
//   3
//   4
//   5
//   6
//   7
//   8
//   9
//   'End loop'

// for文のように 初め(begin)、ステップ(step)、終わり(end) を指定
lazyIter.repeat({begin: 0, step: 5, end: 30}, function(i) {
  console.log(i);
}, function() {
  console.log('End loop');
});
// result:
//   0
//   5
//   10
//   15
//   20
//   25
//   'End loop'

lazyIter.forEver :

lazyIter.StopIteration が throw されるまでループし続けます

// forEver:
//
// lazyIter.forEver(func, callback [, speed [, context]])

var end = 10;
lazyIter.forEver(function(i) {
  if (i === end) {
    throw lazyIter.StopIteration;
  }
  console.log(i);
}, function() {
  console.log('End loop');
});
// result:
//   0
//   1
//   2
//   3
//   4
//   5
//   6
//   7
//   8
//   9
//   'End loop'

throw lazyIter.StopIteration; により各イテレートを止めることができます。
引数 'context' は、コールバック関数の 'this' として使われます (省略可)。
引数 'speed' を指定することで速度の調節ができます (省略可)。
  • 'ninja' : もっと速い
  • 'rapid' : 速い
  • 'fast' : 速め
  • 'normal' : 通常 (default)
  • 'slow' : 遅め
  • 'doze' : 遅い
  • 'limp' : もっと遅い

速度の指定 (rapid, ninja など) は、Pot.js と同じです

Download

ダウンロード

動作テスト (サンプル)

レポジトリ



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



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年4月15日

JavaScriptライブラリPot.js 1.18 リリース。strict modeになりました




Pot.js 1.18 と PotLite.js 1.35 リリースしました。





変更点など

  • 'use strict'つけてライブラリ全体が strict mode になりました
  • DropFile が Firefox で不具合出てたので修正
  • minify したときの最適化

などで、とくに機能の変更はありません。
ねんがんの strict mode にできたので、'use strict' 内で取り込まれても問題なく動きます。
全体を strict mode にするためにソースコードの表現はけっこう変わってますが
動作はむしろ速くなって、サイズがちょっとだけ減りました。


リファレンスのほうは、定義してある関数をとりあえず全部メニューに載せるようにしました。
でもメニューだけで中身の html が無いので、はやいとこ作りたいなと思ってます。




Pot.js / PotLite.js

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

ダウンロード

マニュアル

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

レポジトリ




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