C++から動画を直接出力する自作ライブラリDollyを使って、マンデルブローを拡大する動画を作りました。
この部分のソースはこちらにあります。
……誰にも見つからないような場所に一人で、
心を無にして、何にも干渉せず、また干渉もされずに、
日々を過ごせばいいと、思い……ます
(ソ―シャルゲ―ム 「けものフレンズ」, キンコ)
C++から動画を直接出力する自作ライブラリDollyを使って、マンデルブローを拡大する動画を作りました。
この部分のソースはこちらにあります。
あけましておめでとうございます!今年もよろしくおねがいします。去年はあまりいろいろ出来なかったので、今年は少しずつでも、何かできればなと思っています。せっかく転学部までしたので物理をちゃんと身につけたい1ですし、大学再受験も本腰いれてやってきたいです。
さて、新年早速何かアウトプットしよう、ということで、去年作って放置していた「紙ジェクションマッピング」というネタを投稿します。
プロジェクションマッピングってご存知でしょうか。

東京駅が動き出す!? この冬見られる話題のプロジェクションマッピングまとめ
こういう感じで立体物に映像を投影するアートで、最近いろいろなところで行われているようです。
これっぽい事がしたいのですが、うちにはプロジェクタのような高いものはありません。ので、代わりに1枚50円で印刷できるコンビニのプリンタで何か似たようなことが出来ないかと模索した結果、こんな感じになりました。
仕組みは非常に単純で、ある角度から眺めると立体的に見えるように紙に印刷してあります。
作り方ですが、まずこのマーカー画像をセブンネットプリントで印刷して(宣伝)、
こんな感じで撮影します。このとき、カメラのファインダから眺めた時に台形が真四角に見えるようにするのがポイントです。
これを作ったソフトで変形させて適当にホワイトバランスを調整。
これを紙に印刷して、マーカー画像を印刷した紙の代わりにこの紙をおきます。
実態としては、台形に変形させただけの簡単画像加工をしただけということになりますw
今回は一番簡単な平面でしたが、もっといろいろなものに紙ジェクションしたいですね。たとえば、ティッシュの箱に貼り付けて、ある角度から見ると立体的に見える…とか。今位置合わせが非常にしんどいのですが、ARマーカーでなんとかできるといいんじゃないかなーとかも考えてます。3D力が問われる!!
ただ、結局プリンタの実力があまり高くないせいで、紙に写した方がそんなにリアルに見えないので限界かなという気がしています。っていうかそれがお蔵入りしてた原因です…(◞‸◟ )
おひさしぶりです。21日のニコニコ学会βは良かったですね。特に良かったのは研究100連発で、本職の人のすごさを見ると、私も何かつくりたいな〜って気分になります。私はボランティア(登壇者へのカンペ出し)として参加しました。凄い人を間近に見れるという特典付き(?)の嬉しいけど責任重大なお仕事です。イベント運営って大変ですね…。あと野糞の人は本気でマッドだ(褒め言葉)。
Noiz2saはABA Gamesさんの開発した、アブストラクトな感じのシューティングゲームです。私はこのゲームがSTGに入り始めたきっかけだけあって、大好きです。
去年はWebに移植したNoiz2saを公開しましたが、今年はAndroidに移植したものを公開します。
クリスマス、そして年末年始といえばシューティングゲーム!ぜひお楽しみ下さい。敵の弾を避けて撃つ、指一本で遊べるシンプルなゲームです。
USBのゲームパッドをつなぐと、PC版と同様な操作で遊ぶことも可能です。
移植時のバッドノウハウを書きます
このように置かないと、リソースが圧縮される時に圧縮されてしまい、AssetManagerからランダムリードすることが出来なくなってしまうからです。
あっコーナー分けた割に一個しか思い浮かばなかった…質問は随時受け付けています。
「ローレイヤー勉強会」というイベントでのLT。
ACアダプタ、好きですか。私はだいっっっきらい!です(何
ハードウェアごとに別々のACアダプタが必要で、使いたいのはハードウェアなのに大きなACアダプタばかりが部屋を占拠していってしまいますし、何より種類が無数にあるACアダプタの区別が全然付きません。
パナソニックやソニーのような大企業(?)の作っているデバイスでは、ACアダプタを見れば何のハードウェア用なのかすぐに分かるのです。たぶん、このACアダプタならPSPの充電用でしょう、とかね。
しかし、独自のラベルを付けたりせず、ACアダプタのメーカが製造したものをそのまま同封している場合、本当に区別がつかなくなります。このACアダプタは何用のACアダプタでしょう。電圧と電流は分かるのですが…。
こたえ:HDDケース。
…わからないですよね!
単純に区別がつかないだけなら、仮にどのデバイスがどのACアダプタか区別が付かなくなっても、トライ&エラーで動くまで試せばよいのですが、それをやると壊れてしまうことがあります…。
今回、HDDのケースに、誤って正しい12vのアダプタではなく、間違った24vのACアダプタを接続してしまったところ、HDDケースは無事だったのですが、HDD本体が壊れてしまいました。
ううう…。保護回路とかは流石に無いんだろうなぁ…。
一回失敗したら、それはもう仕方がないので「次がない」ように対策しましょう。やはりココは原始的にシール…で…。
メモックロールテープというのがたまたま手元にあったので、これを使いました。テプラとか普通のセロハンテープとかとは違って、べとつかずに綺麗に剥がせるので精神衛生によい気がします、が、もしかするとすぐ取れちゃうかも…(この辺は要検討)
なんか他にいい方法あったら教えてね!!
久しぶりの更新です。。。。
久しぶりにさきゅばすのアップデートを行いました。またマイナー更新ですけど…。
今回のver 2.0b4のソースから、
のデュアルライセンスになりました。派生物のライセンスはこのどちらか一方か、両方(デュアルライセンスのまま)を選ぶことができます。また、改造されたffmpegに関しては、勝手にBSDにすることはできないので、従来どおりGPL v3のままとなります。
また、GPL v3のライブラリがリンクされているので、配布バイナリも従来どおりGPL v3のままです。
その他の更新はこんな感じです。
2ちゃんで報告されてた「【第10回MMD杯本選】騒がしいゆーじょー【超遅刻カオス】 」の音ズレ問題ですが、たぶんacodecがaac(ffmpegの内蔵aacエンコーダ)が問題を引き起こしてるのではないかと思います1。外部ライブラリであるlibvo_aacencにすると解決するようです。
今までは変換レシピでaacを使うものがいくつかありましたが、今回のバージョンからlibvo_aacencだけにしました。
1月まで独立行政法人 情報処理推進機構(IPA)の未踏IT人材発掘・育成事業の支援で「プログラミング言語ど~なっつ」と、それを用いた「ファミコンを題材にした電子教材のような何か」を作っておりましたが、本事業でスーパークリエータに認定されました。わーいわーい(謎
5/29にはスーパークリエータ認定授与式があり、そこで再度プレゼンテーションを行います。でも平日だよ!
今後の展開なのですが、とりあえず時間操作ができるプログラミング言語、ど~なっつの改善をしたいな~と思っておりまして、
とかやりたいです。特に最後ですが、これが出来ればニワン語の/seekとかにも対応できるし、現状抽象構文木をそのまま評価している「ねこまた」をど~なっつVMへのトランスレータにすることが出来れば、リアルタイム再生も夢でないくらいには高速化できる…かも??
本業(学生)の方があるので支援期間中ほどの速度は出せませんが、ぼちぼち書けていければな~と思います。進学する学科、間違えちゃったかなあ…。
(以下、カノッサの屈辱(テレビ番組)のノリでお願いします)
コンピュータの理論を確立したチューリング。彼は、無限のテープの長さを持ち、単なる計算を行うだけのチューリングマシンを夢想した。
しかし、我々の生きる現実世界におけるコンピュータでのプログラムに於いては、計算の失敗=エラーが発生するのは避ける事ができない宿命である。ネットワーク接続失敗、ファイルが見つからない、メモリが確保出来なかった…等々。すべてのエラーを書き出すには、それこそ無限の長さの紙が必要であろう。プログラム進化の歴史は例外との戦いであると言っても過言ではない。今回の講義では、プログラミング言語の様々な進化のうちの「エラー処理」に着目し、その長い戦いの歴史を概観する。
まずは、いにしえの先エクセプション紀におけるエラー処理を見てみよう。
エクセプション(exception)と呼ばれる隕石が衝突する以前の時代は先エクセプション紀と呼ばれている。この時代での例外処理の特徴は、エラーコードという概念の存在である。数学の関数を模したプログラム言語の「関数」とよばれる生物は、値を一つだけ戻すことができる。当時地球を支配していたAPI関数たちはこの「戻り値」を用いてエラーが発生した旨を伝えることで、上位のプログラマにその処理を託した。
//int readNumberFromFile(const char* filename);
// ファイルを読んで、そこに書かれた数字を返す。失敗した場合は-1を返す。
// ex) number.txtに"12"と書かれていれば、12を返す。
int status = readNumberFromFile("number.txt");
これがこの時代における典型的な「失敗する可能性のある関数」の姿である。戻り値として計算が失敗した場合に特別な値を返すことでその旨を表現し、多くの場合、-1などマイナスの値が好んで使われた。これを「エラーコード」と呼ぶ。
しかし、上記の例を見れば分かる通り、正常な結果と失敗した結果を同じint型の変数一つで表してしまうことで、いくつか問題が発生した。例えば、上記のプログラムでは、もしもファイル中に「-1」と書かれていた場合、このAPI関数は「正常な結果として」-1を返してしまうかもしれない。プログラマにはそれを区別する事ができない。必ず非負の数値が書かれていることを要請し、マイナスの場合はすべてエラーとすれば問題は解決するが、APIに要求とされる機能によってはそれは許されないかもしれない。そのような場合、このように苦肉の策として追加のポインタを使うケースがあった。
// ファイルを読んで、そこに書かれた数字を返す。
// 失敗したか否かの情報がstatus内に格納され、失敗したら-1が格納される。
// int readNumberFromFile(const char*filename, int* status);
int status = 0;
int result = readNumberFromFile("number.txt", &status);
if(status == -1)....
このポインタを用いて間接的に値を返す手法によって、プログラマに結果の値を一つしか伝えることができない、という関数の問題を回避することができた。しかし、この方法には明らかに欠陥がある。「処理が失敗したか否か」も、「成功した場合のその結果」もどちらも「関数から返ってくる値」のはずであるが、この方法ではその対称性が崩れてしまっており、あたかもstatusは入力値のようにも見えてしまっている。このおかげで、せっかくエラーを報告しても無視されてしまう悲しい事故が散見された。また、どちらをポインタとして渡して格納させるかはライブラリごと、関数ごとに違い、勘違いして双方を入れ替えてしまう悲劇も発生していた。また、Javaなどのように「参照型」を渡すことができない言語においては、この方法を(直接は)使うことができない。
Windowsの一部のAPIやOpenGLなどでは、別の関数を呼びエラーの発生を確認するという方式も考えだされた。
// int readNumberFromFile(const char* filename);
int result = readNumberFromFile("number.txt");
int status = getError();
if(status == -1) ....
この方法では、失敗する可能性のあるAPI関数の呼び出しの直後に特定の関数を呼ぶことでエラーの発生を確認した。当然、エラーを確認するまえに別のAPI関数を呼び出してしまうと、エラーを正しく処理することができない。
// int readNumberFromFile(const char* filename);
int result = readNumberFromFile("number.txt");
int result2 = anotherFunction(); /* !ここでもエラーが発生するかもしれない ! */
// readNumberFromFileのエラーをもはや反映しない
int status = getError();
if(status == -1) ....
保守や機能拡張時などにおいては、その事をすっかり忘れてしまい、明後日なエラーメッセージを前に途方に暮れるという事態も珍しいことではなかった。
また、別の関数をわざわざ呼ばなければならない点はやはり手間が大きく、この方式ではポインタ方式よりも、さらに輪をかけてエラーが無視されることとなった。
エラーコード-1の亜種として、nullを返すAPIが棲息する地域もあった。Java大陸や、C++海溝の一部などに見られる化石などから明らかになっている。このような関数呼び出しの場合、一見エラーを返さないような計算に見える:
String data = client.readDataFromFile();
dataがnullであれば失敗である事を示し、そうでない場合は成功した結果を表すという方式である。
この方式も、エラーコードと同様、正しい結果としてnullが返ってくる可能性がある場合にうまく対処できない。さらに「null」というただひとつの値しか返すことができないため、結局失敗した理由を他の方法を用いてAPI使用者に伝えなければならないという問題があった。多くのAPIでは実装工数がないの魔法の一声で、その方法は提供しないこともあった。
エラーコードの体系や伝え方(nullを返す・関数を呼ぶ・エラーコードを使う・ポインタを渡す)は当然、APIの創造主や、APIごとにまちまちであり、APIを消費するプログラマはそれらを個別に理解し、正しく対処することが求められた。そしてもちろん、そこには言語処理系のサポートは一切ない。
結果、そのエラー判定コードが正しいことをコンパイラで事前に判定することができないため、プログラマのその日の体調や気分によってはバグが発生することとなった。
地球上でエラーコードを持つ多様な生物が棲息していたある日、ついに事件は起こった。エクセプション隕石の衝突がそれである。エクセプション隕石は地球上の多種多様な生物に深刻なダメージを与えたが、特に構造化の三要素へのダメージは深刻であった。
当時のプログラミング言語の構文細胞において、順次・反復・分岐の三生物が共生し、細胞小器官となる進化が発生していた。この三つの器官をまとめて「構造化の三要素」と呼ぶ。
/* 順次(上から下に実行) */
int a=10;
int b=a*10;
/* 反復(繰り返し) */
int sum = 0;
for(int i=0;i<100;++i){
sum += i;
}
/* 分岐 */
if( isOdd(x) ){
printf("odd number!\n");
}
しかし、一部の言語はそれよりも原始的なgotoと呼ばれる器官を退化させることなく持ち続け、APIを消費する一部の生物種は、例外の処理に好んでgotoを用いた。
if( mayFail() == ERROR_FAIL ){
goto fail1;
}
...
fail1:
/* 失敗時の処理 */
エクセプション隕石は、この原始的なgotoと構造化の三要素との悪魔融合合体進化を発生させた。なぜこのような奇妙な進化が起こったのかは、未だに議論のあるところである。この合体進化した新たな器官は、隕石の名前にちなんで「エクセプション」と呼ばれた。
try {
int result = mayFail();
int result2 = mayFail2();
....
} catch (Error const& e) {
/* 例外時の処理 */
}
エクセプションは構造化の三要素のような姿を持ちつつ、goto文のような文脈を跳躍する特性も有する。APIを消費する上位のプログラマは、エラーの発生があたかも無いかのように処理を記述しつつ、最後にエラーに纏めて対処する事となった。
構造化の三要素とエラーコードを用いたAPI消費の多くのケースでは、ひとつでもエラーが発生した場合、次の処理を行うことは通常不可能であったため、多くのプログラムではエラーが発生したら、すぐにgoto文で失敗時の処理に移るということが見られた。
if( mayFail() == ERROR ){
goto error;
}
if( mayFail2() == ERROR ){
goto error;
}
if( mayFail3() == ERROR ){
goto error;
}
error:
return -1;
エクセプションでは、これらをcatch節 という仕組みを使うことで共通化した。すなわち、エラーが発生したら移行の処理は全てスキップし、catch節の中でそのエラーに纏めて対処するということである。
try {
int result = mayFail();
int result2 = mayFail2();
int result3 = mayFail3();
....
} catch (Error const& e) {
/* 例外時の処理 */
}
エクセプションを持つ言語は地球上で広く繁栄することとなった。エクセプション器官を持つプログラミング言語は、オブジェクト指向と呼ばれる骨格も同時に持つことが多く、オブジェクト指向とエクセプションには関係があると考えられているが、先述の議論から構造化プログラミングとの関係の方が深いという異論も存在する。
しかし、このエクセプションは悪魔融合合体であるがゆえに、黒魔術的弱点を有する。
多くのプログラムでは、構造化の三要素が守られる事を暗黙の前提としてソフトウェアが構築されていくが、エクセプションの仕組みではエラーが発生するとそれがgoto由来の文脈跳躍生によってほぼ無化されてしまう。
またエクセプションの仕組みは処理の流れを変えることに依存しているため、処理の流れが複数存在するスレッド環境などではさらに技巧を用いることが必要とされ、多くの場合それはうまく動作することは無かった。
これらの事に端を発した多臓器不全で亡くなる生物は少なくなく、「More Effective C++」などの当時を研究する古医学書では1章をこのエクセプション由来の疾患に割かれている。
さらに、「で、結局纏めて対処しろって言われてもどうすれば良いんだ…」との声も根強く、catch節には何も書かれない事態も珍しい物では無くなった。これを「例外潰し」と呼ぶ。
エクセプション隕石はエラーコード種を概ね絶滅させたが、地下3000mではほそぼそと生きながらえる事となった。地下3000mには酸素がほぼ存在しないため、好気性生物であったエラーコード種には進化が求められた。そのための遺伝子を提供したのが、地殻内空洞に棲息する「数学」と呼ばれる、一連の生物群である。
この生物種がエラーコード種に提供したのは、「モナド」と呼ばれる強力な遺伝子である。この特徴を詳しく見てみよう。
エラーコード種に起こった問題である「同じ型で成功と失敗を表されるので、正しい結果の”-1″と失敗を表す”-1″の区別がつかない」という欠点を、値の上位に成功か失敗かを表す仕組み1である「Maybe」や「Either」を加えることで表現。さらに、nullの持つ「失敗としてただひとつの値しか返すことができない」という問題も、失敗時の型にその情報を付加できるようにすることで解決することとなった。
-- Leftが失敗、Rightが成功
data Either a b = Left a | Right b
--引数に応じて処理、失敗するかもしれない
mayFail :: ArgType -> Either String Result
-- ----------------
case mayFail arg of
Left a -> ... --aにはエラーの理由が
Right b -> ... --bには成功した結果が
思わぬ副作用として、エラーを無視することが出来なくなった。戻り値のMaybe/Eitherから結果を取り出すには、必ずエラーか否かをチェックする必要がある2。
これは今までのプログラミング言語種でも可能であった。Cなら構造体と共用体で、C++ならクラスの仕組みを用いることで実現できる。しかし、それだけではエラーコードかどうかをチェックする今までの方法とあまり変わらず、煩雑なエラーチェックが必要になる。
すなわち、MaybeやEitherの導入だけでは、次のような非常に煩雑な記述が必要になってしまう、ということである。
--mayFailとmayFail2とmayFail3を使った処理 --この三つの関数は、どれも失敗するかもしれないのでEitherを返す。 func = case mayFail arg of Left err -> return (Left "error on maybeFail") Right ok -> case (mayFail2 ok arg2) of Left err2 -> return (Left "error on maybeFail2") Right ok2 -> case (mayFail3 ok2 arg3) of Left err3 -> return (Left "error on maybeFail3") Right ok3 -> return (Right ok3)
この状況に力を与えたのが、かの有名な「モナド」遺伝子である。
モナド遺伝子のbindの力とMaybeやEitherが共生することで、上のような煩雑なエラー処理は次のように至極単純化された。
func = do ok <- mayFail arg ok2 <- mayFail2 ok arg2 ok3 <- mayFail3 ok2 arg3 return (Just ok3)
表層を見ると、エラー処理が全て消え去ってしまっている。エクセプションではエラーを処理するために存在するcatch節が、完全に消滅している。しかし、エラーを「潰した」りはされない。もし、この関数を実行した際にmaybeFail2でエラーが発生した場合、その下位関数のエラー値がそのまま返され、続くmaybeFail3はスキップされる。
-- 途中でエラーが発生すれば、その結果が返される。潰れされない。 func -- -> Left "error occur on maybeFail2" -- 最後まで成功すれば、最後にreturnした値が返される func -- -> Right result
エクセプションにあった、構造化の三要素が無化されてしまう事によって生じうる問題はここでもまだ残っているが、MaybeやEitherは制御構造ではなく、単なる値であるため、エクセプションに比べて柔軟である。例えば、エクセプションを用いたプログラミング言語種では「何度か実行し、成功を試行する」といった仕組みを作るのは一般的に面倒であるが、Maybeを使えばこのように簡単である。
-- 渡された関数を何度か実行し、一度でも成功すればその結果を、 -- n回実行しても失敗するならNothingを返す tryFunc :: Int -> ( Int -> Maybe Result ) -> Maybe Result tryFunc n thunk = if n <= 0 then Nothing else case (thunk n) of Nothing -> tryFunc (n-1) thunk Just x -> Just x
この関数を用いてラップすることで、既存のMaybeを返す関数も簡単に「N回試行」させることができるようになる。
func = do ok1 <- mayFail -- maybeFail2だけは10回だけ試行する。一回でも成功すればよい。 ok2 <- tryFunc 10 mayFail2 .... return (ok3)
このように、「失敗するかもしれない」処理の内側と外側の行き来が非常に簡単になったことで、エクセプションの問題も対処しやすくなっている。
私が現在理解したところによるMaybe/Either/Optionalの利点はこんな感じです。あと文中のHaskellがIOがどこでも起こせるかのような書き方ですが、IOと組み合わせると論点がぼやけそうなんです、ごめんなさい。ツッコミ、お待ちしています。
JavaでもC++でも、匿名クラスやlambda式を多用することでMaybe/Eitherモナドは使うことができます。C++だとこんな感じで実装できます。Javaでもバイトで書いたことがあるのですが、バイトで書いたので手元にコードはありません(ぉ
有名な「Effective C++」の続編の本です。未踏の期間が終わったこともあり、読む時間を作ることができました。
この本は英語版で、一応日本語版もあるのですが、訳の評判はあまり良くありません。英語版の方も非常に読みやすい英語で、JavaDocレベルの英語が読めればなんとかなると思います。気のきいたジョークに使われる日常単語がいまいち分からなくて全部調べてたので、いまいち笑えませんでしたが…(ーー;
続編ということもあり、前編であるEffective C++では言語の根本的な部分のと比較して、Moreの方では細かい話が多いです。
前著の方では、例えば
といった内容が多く、他言語を使っていたプログラマがC++の世界を理解するには丁度良かったと思うのですが、今回のMoreの方では、
といった内容です。最後の項目はvtableやvptrの実態がよくわかったので参考になったのですが、その他の項目は今ではインターネット上のC++に関する文章や、STLをある程度使っていると「そらそうよ…」「せやな…」と思ってしまう内容が多く、Effective C++を読んだ時に感じた、あの驚きをもう感じることはできませんでした。Effective C++を読んで少しC++を書き始めた時期に読めばもっと参考になったのでしょうが、数万行書いた後となると…。
改訂が繰り返されているEffective C++と違い、Moreは95年から一度も改訂されていません。STLは出たばかりの時期だったようです。
この本の中で特に一番大きく扱われているのは、参照カウント・スマートポインタです。sharedされている値そのものが参照カウント数を管理する、boostでいうところのintrusive_ptr相当のものをじっくり実装したあと、階層を一個上げてshared_ptr相当のものを実装します。が、相当のものがある事からも分かる通り、どちらも既に有名なもので、いまいち読んでも驚きがありません。しかもweak_ptrを扱っていませんし、参照カウントで問題になる「自分へのshared_ptrを基本的に持てない」という問題も扱っていません。
もしも読むなら、Effective C++を読んでちょっとC++を書いたらすぐに読むことをお勧めします!
JavaScriptでパフォーマンスを出すことを…強いられるんだ!!!(カッ
で、今回はその関係で色々試したことの一つについて、昔やった結果を纏めたのを書きました。
最近のJavaScriptにはTypedArrayというのがあります。通常のJavaScriptの配列は、要素にどんな型でも入れられる上、厳密には配列ではなくハッシュであるため効率が悪いのですが、このTypedArrayでは配列の型を指定した本物の配列であるため、高速・効率的に処理できます。
もともとはJSでOpenGLを使うWebGLという技術向けに策定された仕様なのですが、別にWebGLでなくても使用できて、以前開発したJSのファミコンエミュレータでも、ファミコンのRAMやフレームバッファをこれで表現しています。
さて、このTypedArray、色々とメソッドが定義されていて、よく使う処理は大体網羅されているのですが、memset(メモリ全体を特定のデータで塗りつぶす)を行うようなメソッドがありません。結構使う処理だと思うのですが、無いのでなんとかJSで塗りつぶさないといけない、という事になります。
今回はTypedArrayのmemset処理について色々と試してみた結果を書きます。
var memory = new Uint8Array(1024*1024);
log("normal: "+cycloa.probe.measure(function(){
var m = memory;
for(var i=0;i<5000;++i){
for(var j=0;j<1024*1024;++j){
m[j] = i;
}
}
}));
一番遅そうですが、一番オーソドックスな方法です。5000回、1MBのデータを0で埋めてみると、5回試行してそれぞれ
5008ms, 4950ms, 4955ms, 4949ms, 4957ms
になりました。
ということで、5000ms前後がベースラインということになります。これから遅くなったらむしろ逆効果ということでです。
setというメソッドがあって、これを使うと複数の要素を一気に上書きすることができます。
var mem = new Uint8Array(1024*1024); var another_mem = new Uint8Array(1024); var offset = 100; mem.set(another_mem, offset); -> 100バイト目から1,024バイトがanother_memの内容に上書きされる
さらにこのanother_memの作り方として、上のようにnewで作る方法とsubarrayを使って元の配列を部分的に共有したものを使う方法があります。
ver another_mem = mem.subarray(100,100+1024); -> memの100から100+1024バイト目までを共有したメモリ配列を作
全部ネイティブで0にするより、一旦小さな配列を0で埋めてからsetでセットしたほうが速い気がしませんか。私はそう思いました。
全部JSのループで回すのは遅いというのがわかりましたし、かといってあんまり小さな配列を使って何度もsetを呼び出してもネイティブ呼び出しのオーバーヘッドが重いだろうと思われるので、このバランスをどうするかが問題になります。今回は、1KBから1MB(意味なし)まで変化させて測定しました。
for(var z=10;z<=20;++z){
log(""+cycloa.probe.measure(function(){
var m = memory;
var bsize = 1<<z;
var copy_times = 1024*1024/bsize;
var buff = new Uint8Array(bsize);
for(var i=0;i<5000;++i){
for(var j=0;j<bsize;++j){
buff[j] = i;
}
for(var j=0;j<copy_times;++j){
m.set(buff, j*bsize);
}
}
})+",");
}
| 1 | 2 | 3 | 4 | 5 | 平均 | |
| 1024 | 557 | 559 | 547 | 553 | 543 | 551.8 |
| 2048 | 399 | 397 | 395 | 399 | 398 | 397.6 |
| 4096 | 340 | 343 | 340 | 342 | 342 | 341.4 |
| 8192 | 322 | 319 | 319 | 328 | 331 | 323.8 |
| 16384 | 367 | 369 | 373 | 369 | 372 | 370 |
| 32768 | 477 | 480 | 478 | 483 | 482 | 480 |
| 65536 | 630 | 630 | 631 | 632 | 638 | 632.2 |
| 131072 | 919 | 928 | 926 | 929 | 939 | 928.2 |
| 262144 | 1598 | 1583 | 1584 | 1589 | 1619 | 1594.6 |
| 524288 | 2854 | 2831 | 2821 | 2834 | 2935 | 2855 |
| 1048576 | 5624 | 5396 | 5326 | 5437 | 5418 | 5440.2 |
for(var z=10;z<=20;++z){
log(""+cycloa.probe.measure(function(){
var m = memory;
var bsize = 1<<z;
var copy_times = 1024*1024/bsize;
var buff = m.subarray(0, bsize);
for(var i=0;i<5000;++i){
for(var j=0;j<bsize;++j){
buff[j] = i;
}
for(var j=1;j<copy_times;++j){
m.set(buff, j*bsize);
}
}
})+",");
}
| 1 | 2 | 3 | 4 | 5 | 平均 | |
| 1024 | 640 | 564 | 553 | 565 | 557 | 575.8 |
| 2048 | 417 | 404 | 401 | 405 | 404 | 406.2 |
| 4096 | 343 | 348 | 342 | 347 | 341 | 344.2 |
| 8192 | 319 | 320 | 319 | 320 | 318 | 319.2 |
| 16384 | 369 | 377 | 367 | 362 | 369 | 368.8 |
| 32768 | 468 | 471 | 470 | 467 | 465 | 468.2 |
| 65536 | 611 | 614 | 610 | 610 | 607 | 610.4 |
| 131072 | 891 | 882 | 873 | 876 | 888 | 882 |
| 262144 | 1516 | 1502 | 1498 | 1493 | 1520 | 1505.8 |
| 524288 | 2678 | 2653 | 2652 | 2667 | 2658 | 2661.6 |
| 1048576 | 4993 | 4934 | 4922 | 4972 | 4923 | 4948.8 |
と、いうわけで、8192くらいの配列を作ってsetで回したほうが大体17倍くらい速いです。subarrayの方が配列生成コストが変わってくるのかなぁ、と思ったのですが、あんまり変わらないようですね。
ただこの8192という数字は2013年のFirefox18のWindows7でCore i5(Ivy Bridge)というひっっっっっっじょうに限られた環境での値なので、あんまり鵜呑みにしないほうがいいと思います。もし、JSの実行速度がネイティブ関数呼び出しのコストに対して相対的に速い環境があればこの値は大きくなるかもしれませんし、ネイティブ関数呼び出しのコストがJS実行速度よりも相対的に安い環境があれば、この値は小さくなってくると予想されます。
JSは大変ですね…。