- BACKNUMBERS -
2024 / 5
2024 / 6
2024 / 7
2024 / 8
2024 / 9
2024 / 10
2024 / 11
2024 / 12
2025 / 1
2025 / 2
2025 / 3
2025 / 4
2025 / 5
2025 / 6
2025 / 7
最新月

web6047 - 2025年 8月

ここは個人の趣味のページです。


私がプログラミングを好むわけは、プログラミングは私の「物づくり」に対する探求心創造性を十分に満たしてくれるからです。

ここの管理人、
AI の利用やめるってよ。

AI の問題点:

これらが向こう10年解決されないだろうと見込んで、やめることにしました。(2025年5月23日~)

私は AI に頼らず「自分の力」を大切にしたいです。

…とはいえ、人間は新しく見つけた技術を手放すということは基本的にやらないと思います。

だから私も後々 AI を利用することにはなると思います。

でも上記の問題点は確かなもので、将来 AI ロボが私の家の扉をコンコンと叩いて

「市からの要請で、お手伝いするため おうかがいしました」

と言うまでの間は、この AI 技術を導入せず、頑張ってみようと思います。

でもこの考え方、キツイと思うのでマネしなくていいです。

- Special Documents -

特別な記事へのリンク

▼3DCG プログラミングの方法
▼書籍「はじめて読む486」の
サンプル動作環境の作成方法
▼3Dお姉さんによるプログラミング解説
▼RPG のルーツ(PDF)

ゲームコーナー 

▼クレイジーバルーン
(↑, ↓, ←, →)
▼テトリス
(←, →, ↓, z, x)

その他 単発のアプリ

▼矩形波は複数の波形の合成です
▼SVC/簡易的な試作
▼SVC/パーツ連結のみ
▼SVC/パーツ連結とポーズ補間

- 以降は日記です -

2025年8月8日

生活 花

数年の生産作業(産業機器の製造)でそろそろ限界が来ているようで、今日は右肩から指先にかけて、しびれてしまい、作業ができず、会社を休んで整形外科に行ってきました。

その帰りにいつも通勤途中で見かけるデージーだかマーガレットだか(いわゆる菊)が気になっていて、病院の帰りに買いました。

デージーを買いましたが、その他、店内で切り花がいろいろ売っていたので、それも2つ買いました。

以下の写真は一緒に購入したその他の切り花です。

家に帰る途中で撮影。

350円×2束。

花瓶が無いので…

  1. 板は電子機器組み立て1、2級の練習用の板。
  2. 細長い段ボールを「仮ペットボトル」に巻き付け、輪ゴムで留める。巻き付けた段ボールに半分くらいまで縦に切り込みを入れて、開き、板にガムテープで貼り付け。仮ペットボトルを外す。(マウンタの出来上がり)
     
  3. 新品の「おいしい水 500ml」を開封し、少し水を捨て、水が入ったまま、ペットボトルの上部をカッターで上手に切り取る。(注:皮膚を切るおそれあり)
  4. 「切り花長持ち」の液体を入れる。
  5. 切り花の茎を下から1cmくらい切る。(母からのアドバイス)
  6. 茎をタコ糸で束ねる。
  7. ペットボトルに挿す。
  8. そのペットボトルを板上の段ボールに差し込む。
     
  9. アルミホイルを巻き付ける(抗菌の意味で)
  10. 窓際に置いて、小さい扇風機を回す。(良いか悪いかは知らないけど)
  11. 水は2日に1度交換(母からのアドバイス)

「俺の花瓶。」みたいな よくある よくわからん商品みたいな、そんなふうなことをやって…

自宅の出窓にて撮影。

自宅はこの数年、エアコンを使っていないので、熱帯雨林的な室温。

そのため窓際、扇風機。

これでもすぐに(半日で)花びらがほつれてくると思うけど。

たしか植物に扇風機を直接当てるのはよくなかった気がします。

いつも通販で花束を人に贈るばかりで、自分のために買ったことはなく、今回が初めてかな。

(訪問者のどんなニーズと この記事がつながるか)

  • 日記を読みたい

2025年8月9日

試した ChatGPT5

ChatGPT5 が発表・公開されたのでどんな様子か調査目的で会話してみました。

会話記録(発言数16個)


一番大きいのはこちらを褒めてくることが少なくなったことです。

褒められるために話しているわけではないのでいちいち褒めてくるのは今まで大きな障害でした。

そのほか、話の内容がより洗練されている印象を受けました。

OpenAI の勝利かなって感じがします。


でも勝てば何でもよい、強ければ何でもよいではなく、ジブリの絵柄を合法的に利用してジブリに失礼な態度を取ったのは確かだと思うので、謝ったほうが良いと思います。

▼旧 Twitter のサムアルトマン氏リンクのアイコンはジブリの絵柄になっている

相手がジブリでなくても、人の絵柄を合法的に公然と無断で利用するのは人道的・倫理的に良いことではありません。

好きだから公然と利用する、まねるというのとは性格が異なります。(サム・アルトマン氏個人のネガティブな意思)


このようにせっかく勝利の評価を得たのに、ケチが付くのは企業としてはいまいちです。

(訪問者のどんなニーズと この記事がつながるか)

  • GPT-5 の性能について
  • AI について読みたい
  • 日記を読みたい

生活 花2

昨日買ったデージーのほうは、こんな感じで鉢に移しました。

黄色い花が咲くそうです。

(イラストAC 素材)

(訪問者のどんなニーズと この記事がつながるか)

  • 日記を読みたい

2025年8月11日

プログラミング 「クォータニオン」はじめました6

3D モデルがページと同化

人型モデルがページ内に出現し、HTML 要素をポイントできるようになりました。

ページ上の文章は著作権フリーの青空文庫です。


▼span タグに手を添えている ▼ウィンドウ横幅を広げて追従 ▼スクロールして追従


▼ウィンドウの右端に固定 ▼ウィンドウの左端に固定 ▼中央下部に固定




クォータニオン(数学の四元数)により、ポージングは自由自在になったようです。

ガンダムのプラモデルをいじるのとほぼ同一な感じ。

このプログラムの技術詳細

技術内容としては、


上記はそれぞれ難しいと言えば難しいけど、越えられないものではないと思います。

今回のプログラムで越えがたい特に難しいのは次の 2 点です。

私個人の感想としては、「強烈に難しかった」です。頭を抱えたり、心が折れそうになることが多かった。

プログラミングって表面的な部分は実に簡単だと思いますけど、今回の人型モデルのプログラムのように複数の技術をうまく管理しながら、かじをとる(船の丸いあのハンドルを操作する)ようにプログラムするのは難しいです。

親子関係は、パーツ同士の 3D 空間中の様子を頭で思い描きながら正しい動きを探るので、難しいです。分からなくて途方に暮れることもあります。

それらができるようになるコツは、プログラミングを楽しむことと、先を急がないことと、いろいろ教えてくれる人に感謝の気持ちを持つことです。


今回のプログラムの例は WEB ページに新しい体験を産むエッセンスと言って間違いありません。

(「間違いないでしょう」と言うほうがフレンドリーですが、1+1=2ぐらい確かなものを「2でしょう」っていうのも変なので「2です」、「間違いありません」と言っておきます)

このような技術はこれまでいろいろなページを見てきて一度も見たことないけど、どこかの誰かが同じことをやってるかもしれませんね。

「デスクトップマスコット」というものが流行っていて、それを WEB ページに導入しない手はないですからね。

サンプルページ

スマートホンではうまく表示できないかもしれませんが、

スクリーンショットで紹介した実際のサンプルページへ

(訪問者のどんなニーズと この記事がつながるか)

  • 新しい WEB 技術
  • 面白いものを見たい
  • 日記を読みたい

2025年8月15日

プログラミング どこが遅いのか

あまり知られていない「プロファイラ」というプログラミングツールについて説明します。

  1. 「プロファイラ」が必要となる状況の話
  2. 「プロファイラ」を活用する
  3. 「プロファイラ」の使い方手順
  4. 使い方の補足
  5. さらに使いこなすために

かなり長い説明となっています。

1. 「プロファイラ」が必要となる状況の話

私が最近作っている「人型 3DCG モデルのプログラム」なんですが、

通常、3DCG のプログラムは、「3DCG 計算のための専用の演算回路」(CPU や GPU の中に入っている回路)を利用します。

ソフトウェアではなく、演算回路、つまりハードウェアを使って計算させると電気のスピードで計算されるので、ものすごく速いんです。

上図のような人型のモデルが 100 人くらい表示されていても、たぶんへっちゃらです。


ところが私は手作業にこだわっていて、自分の手計算で 3DCG を計算しています。

ライブラリもフレームワークも使わずに手作りで作っています。


その弊害として、表示できる人型はどうも 1 ~ 3 人までが限界で、"ランバート反射" というちょっとした光の計算による陰影をつけると、たぶん1人でもきつそうです。

パソコンの処理が追い付かず、コマ落ちしたり、重くなったりします。


そこで

『プログラムが実際に動いているとき、いったいどこで処理に時間をかけているのか』

というのを見極めて、その部分の高速化を図る、という解決手段が必要になってきます。

「一番時間をかけている処理」で無駄なプログラムを省くことができれば、時間 対 効果 が高いんですね。

「時間がかかっていない処理」でプログラムを省いても効果は薄いです。


その無駄なプログラムを省く手段が「プロファイラ」です。

プログラミングをするときは、「テキストエディタ」、「コンパイラ」、「デバッガ」などいろいろなツールがありますが、その仲間として実は「プロファイラ」というものがあります。

デバッガは聞いたことがある。でも使ったことがない。という人は多いと思います。

それに加えて「プロファイラ」なんてものはほとんど知らないんじゃないかと思います。


▼ 「Firefox Profiler」


『プログラムが実際に動いているとき、いったいどこで処理に時間をかけているのか』

…上図で言うと、下部中央、やや左にある、青い部分「 get VisualViewport.width   」というのが無駄に時間がかかる処理だというのが分かります。

VisualViewport.width とペアの VisualViewport.height というのは JavaScript でもともと用意している値で、”スクロールバーを含まないウィンドウ 内部幅” ですが、読み出しは「プログラマーの見えないところでいろいろ手続きが必要」らしく、大変なようです。

画面の左端が「測定開始」、画面の右端が「測定終了」、その幅の中で、たった一つの値を取り出すために、この青い部分の幅だけ時間を使っている。というのが分かるんですね。

積み木みたいに上方向に積みあがっているのは、どの関数から呼ばれたかを示しています。

「get VisualViewport.width」は、下の方から、

  1. 黄色の、FrameRequestCallBack(つまり JavaScript のアニメで使う requestAnimationFrame() のこと)
  2. 黄色の、frame 関数(私が requestAnimationFrame() で指定してるコールバック関数)
  3. 黄色の、draw 関数(後述している、私が作った描画関数)
  4. そのなかで実行される、青い部分「get VisualViewport.width」

という順の流れ(積み重ね)で呼ばれているんですね。


余談ですが、ひときわ高く黄色いスカイツリーみたいになっている部分(中央やや右)は、人型モデルの親子関係を計算している部分です。

人型で親子関係と言っているからと言って、お父さんお母さんと子供の人間関係ということではありません。

おなかパーツから始まって、胸や腰パーツ、腕パーツ、足パーツへと各モデルがつながり人型を形成するという関係ということです。

『かわいがると言っても「たかいたかい」とか「いいこいいこ」するということではないぞ!痛めつけるということだ!』

というギニュー特戦隊のジースのセリフみたいですね。


(今回プロファイルしたのは人型ではなく、別のサンプルの4節のヌンチャクみたいな単純なモデルです。それでも積み上げが高くなっています)

スカイツリーは呼び出しの層は高いが、幅が短いので時間はかかっていない、と言えます。

2. 「プロファイラ」を活用する

以下が「get VisualViewport.width」(以下反転赤で示す)を行っている draw() 関数です。

どう直せば改善できるのでしょうか。読むと、コメントはあるもののプログラムは意味不明なので漠然と見てください。

(余談ですが、黄色で示した 2 行がいつもこのページで言っている 3DCG の原理の式です。このプログラムを 3DCG プログラムとして動かす核となる式です)


draw() { this.update(); const polygons = new Array(); for( let i = 0; i < this.figures.length; i ++ ) { const figure = this.figures[ i ]; //--- [3/8] 要素の位置 x, y, z if( figure.targetingEnable ) { // 1 figure.updateTargeting(); } //--- [4/8] CANVASサイズを決めるためのモデルが占めるサイズ figure.maxH = Number.MIN_VALUE; figure.maxV = Number.MIN_VALUE; figure.minH = Number.MAX_VALUE; figure.minV = Number.MAX_VALUE; // 「モデルの指定点」から見た「HTML 指定位置」までの距離 if( figure.targetingEnable ) { // 2 const tenC1 = figure.targeting.model.tenCss1[ MODEL ][ figure.targeting.modelTenIndex ]; figure.targeting.distanceToHTMLElement[ X ] = figure.targeting.position[ X ] - tenC1[ X ]; figure.targeting.distanceToHTMLElement[ Y ] = figure.targeting.position[ Y ] - tenC1[ Y ]; figure.targeting.distanceToHTMLElement[ Z ] = figure.targeting.position[ Z ] - tenC1[ Z ]; } for( const model of figure.models ) { //--- 投影 for( let i = 0; i < model.tenss.length; i ++ ) { const tenCs1 = model.tenCss1[ i ]; const tenCs2 = model.tenCss2[ i ] = new Array(); for( let j = 0; j < tenCs1.length; j ++ ) { const tenC1 = tenCs1[ j ]; const tenC2 = tenCs2[ j ] = tenC1.slice(); applyQuaternion( tenC2, this.camF.camM.quaternion ); tenC2[ X ] -= this.camF.camM.position[ X ]; tenC2[ Y ] -= this.camF.camM.position[ Y ]; tenC2[ Z ] -= this.camF.camM.position[ Z ]; if( figure.targetingEnable ) { // 3 // モデルは HTML 要素を指し示す tenC2[ X ] += figure.targeting.distanceToHTMLElement[ X ]; tenC2[ Y ] += figure.targeting.distanceToHTMLElement[ Y ]; tenC2[ Z ] += figure.targeting.distanceToHTMLElement[ Z ]; } //check. z が原点を超えた if( tenC2[ Z ] < 0 ) tenC2[ Z ] = 1; tenC2[ H ] = tenC2[ X ] * this.camF.s / tenC2[ Z ] * this.camF.zm; tenC2[ V ] = tenC2[ Y ] * this.camF.s / tenC2[ Z ] * this.camF.zm; //--- [6/8] figure が占めるサイズを算定中 if( tenC2[ H ] < figure.minH ) figure.minH = tenC2[ H ]; if( tenC2[ H ] > figure.maxH ) figure.maxH = tenC2[ H ]; if( tenC2[ V ] < figure.minV ) figure.minV = tenC2[ V ]; if( tenC2[ V ] > figure.maxV ) figure.maxV = tenC2[ V ]; } // for j tenCs1 } // for i tenss } // for model models // minH,maxH,minV,maxV を確定させてから次を行う必要があるので、 // この上下の for models は1つにしない。 // アクティブカメラなど不可視の場合、ポリゴン作成をしない。 //check. カメラとか if( ! figure.getVisibility() ) { continue; } //--- [7/8] canvas サイズについて figure.maxH += 2; figure.minH -= 2; figure.maxV += 2; figure.minV -= 2; figure.rect[ 0 ] = figure.minH + this.windowOriginLeft; // x figure.rect[ 1 ] = -figure.maxV + this.windowOriginTop; // y figure.rect[ 2 ] = figure.maxH - figure.minH; // w figure.rect[ 3 ] = figure.maxV - figure.minV; // h if( figure.targetingEnable ) { // 4 const index = figure.targeting.modelTenIndex; figure.rect[ 0 ] += figure.targeting.position[ H ] -figure.targeting.model.tenCss2[ MODEL ][ index ][ H ]; figure.rect[ 1 ] += -( figure.targeting.position[ V ] - figure.targeting.model.tenCss2[ MODEL ][ index ][ V ] ); } //check. figure.rect[ 0 ] = Math.floor( figure.rect[ 0 ] ); figure.rect[ 1 ] = Math.floor( figure.rect[ 1 ] ); // これがないとウィンドウ境界で1ドットのスキマの有無を繰り返す。 // 前の canvas とのかぶりチェック let kabu = false; for( let j = 0; j < i; j ++ ) { const figure0 = this.figures[ j ]; //check. if( figure0 == this.camF ) continue; if( areTheyKaburi( figure0.cc.rect, figure.rect ) ) { kabu = true; figure.cc = figure0.cc; const minX = Math.min( figure.rect[ 0 ], figure0.cc.rect[ 0 ] ); const minY = Math.min( figure.rect[ 1 ], figure0.cc.rect[ 1 ] ); const maxX = Math.max( figure.rect[ 0 ] + figure.rect[ 2 ], figure0.cc.rect[ 0 ] + figure0.cc.rect[ 2 ] ); const maxY = Math.max( figure.rect[ 1 ] + figure.rect[ 3 ], figure0.cc.rect[ 1 ] + figure0.cc.rect[ 3 ] ); figure.cc.rect[ 0 ] = minX; figure.cc.rect[ 1 ] = minY; figure.cc.rect[ 2 ] = maxX - minX; figure.cc.rect[ 3 ] = maxY - minY; figure.cc.canvas.style.left = scrollX + figure.cc.rect[ 0 ] + "px"; figure.cc.canvas.style.top = scrollY + figure.cc.rect[ 1 ] + "px"; figure.cc.canvas.width = figure.cc.rect[ 2 ]; figure.cc.canvas.height = figure.cc.rect[ 3 ]; } // canvas.width, canvas.height への代入は、 // 同値だとしても代入を行った時点で // 画面消去される。 // だから以降に clearRect() は無い。 } // for j if( kabu ) { figure.defaultCc.canvas.style.display = "none"; } else { figure.defaultCc.canvas.style.display = "block"; figure.cc = figure.defaultCc; figure.cc.rect = figure.rect; figure.cc.canvas.style.left = scrollX + figure.rect[ 0 ] + "px"; figure.cc.canvas.style.top = scrollY + figure.rect[ 1 ] + "px"; figure.cc.canvas.width = figure.rect[ 2 ]; figure.cc.canvas.height = figure.rect[ 3 ]; } //check. 右端、下端からはみ出したとき canvas サイズを小さくしてはみだしを避ける if( figure.cc.rect[ 0 ] + figure.cc.rect[ 2 ] > visualViewport.width ) { // figure.cc.rect[ 2 ] や figure.rect[ 2 ] の更新は省略 figure.cc.canvas.width = innerWidth - clientX; // innerWidth, innerHeight じゃないと、スクロールバーの分のスキマが出るときがある。 } if( figure.cc.rect[ 1 ] + figure.cc.rect[ 3 ] > visualViewport.height ) { figure.cc.canvas.height = innerHeight - clientY; } for( const model of figure.models ) { // ● //--- ポリゴン作成 for( let i = 0; i < model.mens.length; i ++ ) { const men = model.mens[ i ]; // 面 const polygon1 = [ model, // 0 : model true, // 1 : ISNOTPATHS new Array(), // 2 : tenCs2 0, // 3 : avgZ figure, // 4 : figure ] for( const tenIndex of men ) { const tenC2 = [ model.tenCss2[ MODEL ][ tenIndex ][ X ], model.tenCss2[ MODEL ][ tenIndex ][ Y ], model.tenCss2[ MODEL ][ tenIndex ][ Z ], model.tenCss2[ MODEL ][ tenIndex ][ H ] - ( figure.cc.rect[ 0 ] - this.windowOriginLeft ), // ● model.tenCss2[ MODEL ][ tenIndex ][ V ] + ( figure.cc.rect[ 1 ] - this.windowOriginTop ), // ● ] polygon1[ TENCS2 ].push( tenC2 ); polygon1[ AVGZ ] += tenC2[ Z ]; } polygon1[ AVGZ ] /= men.length; const polygon1Visibility = isPolygonVisible( polygon1[ TENCS2 ] ); if( ! model.hiddenSurfaceRemoval || polygon1Visibility ) polygons.push( polygon1 ); //debug. 面の点番号を表示するときに使用 polygon1.men = men; // 面にヒモづけられたパス if( model.pathz[ i ] ) { // i は面番号であり、pathz のキーにその面番号があれば //--- ●●●paths2 make args // paths について、関数呼び出しの場合、args を準備する。 for( const path of model.pathz[ i ] ) { const polygon2 = [ path, // 0 : paths ( pathz[ i ] は配列である ) false, // 1 : ISNOTPATHS new Array(), // 2 : tenCs2 0, // 3 : avgZ figure, // 4 : figure ] let cnt = 0; for( const drawingInst of path.drawingInsts ) { //check. if( ! drawingInst.isFunc ) continue; drawingInst.args.length = 0; for( let n = 0; n < drawingInst.xyArgsToTenIndex.length; n ++ ) { const tenIndex = drawingInst.xyArgsToTenIndex[ n ]; drawingInst.args[ n * 2 ] = model.tenCss2[ MODEL ][ tenIndex ][ H ] - figure.minH; drawingInst.args[ n * 2 + 1 ] = -model.tenCss2[ MODEL ][ tenIndex ][ V ] + figure.maxV; polygon2[ AVGZ ] += model.tenCss2[ MODEL ][ tenIndex ][ Z ]; cnt ++; } } polygon2[ AVGZ ] /= cnt; //check. 面が見えているとき、パスは面よりも手前に来るはずだ if( polygon1Visibility && polygon1[ AVGZ ] < polygon2[ AVGZ ] ) { polygon2[ AVGZ ] = polygon1[ AVGZ ]; } else if( ! polygon1Visibility && polygon1[ AVGZ ] > polygon2[ AVGZ ] ) { // 見えていないときにパスのほうが手前に来るなら、AVGZ値を交換(=描画順を交換) const tmp = polygon1[ AVGZ ]; polygon1[ AVGZ ] = polygon2[ AVGZ ]; polygon2[ AVGZ ] = polygon1[ AVGZ ]; } polygons.push( polygon2 ); } // for path pathz[i] } // if } // for i mens } // for model models } // for figure figures polygons.sort( function( a, b ) { if( a[ AVGZ ] < b[ AVGZ ] ) return 1; if( a[ AVGZ ] > b[ AVGZ ] ) return -1; return 0; } ); //--- 描画開始 // 軸 if( 0 ) { line( 0, - cc.canvas.height / 2, 0, cc.canvas.height / 2, "cyan" ); // tate line( - cc.canvas.width / 2, 0, cc.canvas.width / 2, 0, "cyan" ); // yoko } for( const polygon of polygons ) { const cc = polygon[ FIGURE ].cc; if( polygon[ ISNOTPATHS ] ) { cc.beginPath(); for( let i = 0; i < polygon[ TENCS2 ].length; i ++ ) { const tenC2 = polygon[ TENCS2 ][ i ]; if( i ) { cc.lineTo( tenC2[ H ], -tenC2[ V ] ); } else { cc.moveTo( tenC2[ H ], -tenC2[ V ] ); } } cc.closePath(); cc.fillStyle = polygon[ MODEL ].color; cc.fill() if( polygon[ FIGURE ] && polygon[ MODEL ] == polygon[ FIGURE ].baseModel ) cc.strokeStyle = "red"; else cc.strokeStyle = "rgba(0,0,0,.25)"; cc.stroke(); //debug. 点番号を表示 if( polygon[ MODEL ].debugTenNumDisp ) { for( let i = 0; i < polygon[ TENCS2 ].length; i ++ ) { const tenC2 = polygon[ TENCS2 ][ i ]; cc.fillStyle = "white"; cc.fillRect( tenC2[ H ], -tenC2[ V ], 14, -12 ); cc.fillStyle = "black"; cc.fillText( polygon.men[ i ], tenC2[ H ] + 1, -tenC2[ V ] - 2 ); } } } else { //--- ●●●paths3 描画 for( const drawingInst of polygon[ PATHS ].drawingInsts ) { if( drawingInst.isFunc ) cc[ drawingInst.name ]( ...drawingInst.args ); else cc[ drawingInst.name ] = drawingInst.args[ 0 ]; } } } // for polygon polygons } // (App).draw()


答えは、必ずしも一つではありませんが、一例として、

『この draw() 関数(メソッド)の前に初期設定として、visualViewport.width ではなく visualViewportWidth という名前で値を事前に保存しておき、draw() 関数(メソッド)の中でその visualViewportWidth を見る』

です。

また、ウィンドウがリサイズされるとウィンドウの内部幅は変わるので、onresize でその値(visualViewportWidth)を更新するようにします。

つまり、visualViewport.width と height は、draw() の中で参照する必要はまったくなかったということです。

この改善により、draw() の中では単純な変数を見るだけとなるので早くなるはずです。


プロファイラによって速度的な問題点を浮き彫りにして、プログラマーが解決を行い、高速化を図ることができます。


3. 「プロファイラ」の使い方手順

ではプロファイラの使い方を見ていきましょう。

図は私が作っている人型モデルのプログラムで、2つのヌンチャク型モデルを表示しているところです。

ヌンチャクというのは別名、三節棍と呼ばれるもので、中国の古い武器ですね。四節棍というものもあるそうです。

右クリックして「調査」を選びます。


「開発者ツール」という画面が表示されます。

上部の真ん中付近の「パフォーマンス」タブをクリックします。

すると図のような画面になります。


ちょっと、一番下の「設定を編集...」というリンクをクリックして設定を見ることにします。


するとブラウザのほうで「プロファイラーの設定」という画面が表示されます。

ここに、測定を粗くするか細かくするかという設定があります。

「粗い測定」だと、「一瞬で終わる関数」を取りこぼすことがあります。

「細かい測定」にすれば多くの関数の実行を検知することができます。ただし、細かい分、処理が重くなります。

画面を下にスクロールして「バッファー設定」の「サンプリング間隔:」を小さい値にすれば「細かい測定」になります。

最初の内は設定を変更しないでそのままで構いませんが、測定後に必要な情報が入っていないときはこの設定を小さくするようにします。これは覚えておきましょう。

画面を×を押して閉じます。


目的のプログラムを動かします。

「この辺かな~」というところで、「開発者ツール」>「パフォーマンス」の画面で、画面中央の青い「記録を開始」ボタンを押します。


するとボタンが変わります。

この状態で測定中です。


30秒くらい時間を見てから図の「記録をキャプチャ」というボタンを押すと、測定を終了し、測定した記録のキャプチャ獲得する、取る、とらえる、つまり集計)を開始します。

CPU が core i5 8500T(6コア)のパソコンで、

上記の設定のサンプリング間隔: 0.5 ms(やや細かい)にして、

10 秒間測定すると、

キャプチャにかかる時間は 1 分でした。


結構待ちます。設定 0.5 ms で 10 秒測定というのは、

10 秒 × 1,000 ms = 10,000 ms

10,000 ms ÷ 0.5 ms = 100,000/5 = 20,000 回サンプリング

ということで、2 万個のレコードを集計(分析)しているなら 1 分かかるのも何となく分かります。


キャプチャ(集計)が終わると、プロファイラの画面が表示されます。

最初に大きく示した画面です。

グラフを見ながら、

『get VisualViewport.width が無駄なんじゃないか??』

…のようにプログラムを遅くしている原因や、高速化できる部分を探るわけです。

プロファイラがないとこの画面は見ることができません。

(実際は、キャプチャが終わるとこの画面ではなく、「Call Tree」というタブがまず最初に表示されます。補足をご覧ください)


積み上げられたグラフのうち、「draw()」のグラフにマウスを当てると、図のようにプロファイリングらしい画面が表示されます。

この表示の左上を見ると、「354 ms」と表示されています。

draw() 関数を実行するのに(測定処理も含めて)「354 ms」かかっているということです。

% 表示は、測定した期間(例では10秒)の内で「39 %」はこの draw() 関数の処理が占めている、という意味です。

つまり、プログラム全体の動きの内、40 % くらいは描画に費やしているということです。

VisualViewport.width の参照を改善することで draw() の処理時間が短くなれば良いわけです。


改善前は、図のように draw() の中で、39% を占めていましたが、改善を行って、、


このように、単なる変数の参照に変えたためか、「get VisualViewport.width」がそっくり消えました。


draw() のプロファイル画面では、

354 ms → 214 ms

のように 140 ms 短縮されました。

(測定ごとに多少のばらつきはあります)


…以上のように、プロファイラを使うと、無駄な処理を見つけることができ、効率化のきっかけとなります。

しかし、Firefox Profiler は公式の日本語マニュアルが無いし、使って解説している日本人も少ないので、習得がちょっと難しいです。

でも基本の操作は以上の通りです。

ゲームとか作って動きが悪いなーという人は、プロファイラを使うと良いと思います。

4. 使い方の補足

以下は補足です。使い方が難しいですから補足しないと…


「記録をキャプチャ」(集計)ボタンを押してしばらくしてから最初に表示される画面です。

この画面で、下部の「Flame Graph」というタブをクリックすると、、


このような画面になります。

タブのすぐ下の

◎All frames、○JavaScript、○Native

で、JavaScript を選ぶと、、


測定結果の内、JavaScript のレコードだけを集計したグラフに変わります。

でも、測定したとき複数のページを開いていたので、関係のないページのグラフが表示されています。

画面上部には複数の項目が並んでおり、この中から目的のページをクリックします。

項目はページ名ではなく、サーバーのドメイン名になっています。

ドメイン名の次に Memory や CanvasRenderer などの測定値が並んでいます。

今回のページは「ローカルマシン」で動かしていたので、「127.0.0.1」というドメイン名(アドレス)です。

クリックすると、、


これで目的の画面になりました。

(まぁ、難しいですね。)

改めて測定したものですが、ちゃんと、

requestAnimationFrame を表す関数と、

そのコールバックに指定した frame() 関数、

draw() 関数、があります。

3D モデルの親子関係を処理してるスカイツリーも見えます。


ドメイン名が並ぶ部分の上のほうにはスクリーンショットが並んでいます。

マウスでポイントすると拡大表示されます。

公式ドキュメントによれば、このスクリーンショット表示は単なる「参考」だそうです。

測定期間中の、「画面の変化(スクリーンショット)」と、「画面上半分のグラフ(CPU使用率やメモリ使用率など)」は時間軸で対応しています。

”この画面のときにどれだけ CPU を使っていたのか” という対応関係が分かります。

画面下半分の複数のタブのうち、「Stack Chart」タブと「Marker Chart」タブのみ、時間軸を対応させて見ることができます。

「Call Tree」タブは樹形図的な画面で時間軸との対応はありません。

「Flame Graph」タブは測定期間中に占める、各処理の時間の割合を幅で示して並べているだけであり、時間軸との対応はありません。

「Marker Table」は表ですね。これも時間軸との対応はありません。


以上の詳細は、このプロファイラの公式ドキュメント「Getting Started」に、英文ですが、書かれています。

Firefox なら、右上の≡マークから「ページを翻訳...」で日本語にできます。うまくいっていない翻訳ですが、なんとか読めます。

5. 5つのタブを説明

本気でプロファイラを使ってみようという方は以下の説明も読んでもらうと良いと思います。

どうしてここまで詳しく説明するのかと言うと、他のニュースサイトの「Firefox Profiler」の記事を読むと、「詳細は英文の公式マニュアルを読んでください」で終わっていて、どうも参考(実用)にならないからです。

ここではプロファイラを使いこなすための、下図の5つのタブについて説明します。

1. Call Tree タブ

英文公式サイトこれの翻訳:

  1. Call Tree パネルは、サンプルデータの総合的なビューです。
    スタックサンプルと Call Tree に関するページでは、サンプルデータから Call Tree 構造がどのように計算されるかを詳しく説明しています。
  2. 簡単に言うと、スタック内の共通の祖先をマージすることで、プログラムのどの部分がより頻繁に実行されているかを特定できます。
  3. フィルタリングガイドでは、このビューの操作方法(検索、JavaScriptへのフィルタリング、変換、反転 (ボトムアップツリーとも呼ばれます) )に関する役立つ情報が提供されています。
  4. 最後に、サイドバーには、選択した関数内で実行されているコードのカテゴリの内訳が表示されます。

ここで言っている「スタック」というのは、測定データの内、メモリに関する値、CPU 使用率、機械語コードの使用率、JavaScript コードの使用記録に関する値など、「種類別に振り分けたデータ」のことを言っています。

「共通の祖先をマージ」とは、たとえば、測定データAが使用率20%、測定データBが使用率5%で、それらが同じ関数から呼ばれているなら、その関数の使用率は25%である、という意味だと思います。その関数は25%の頻度で実行されているというのが分かるということです。

「サイドバーのカテゴリの内訳」とは、

たとえば、frame という関数は全測定時間の 51 % を占めていて、frame 関数の処理を構成する要素としては、JavaScript や DOM 処理、メモリのごみ処理(GC/CC)でありそれらは 51 % を 100 % としたとき、それぞれ 32 % とか 0.9 % を占めている、という意味です。

またその中で、この測定のために要した時間は Profiler 17 % です。

全 204 ms のうちの 17 % なので、204 ms × 0.17 = 34.68 ms ということです。

なので、frame という関数は実際は 204 ms - 34.68 ms = 169.32 ms かかっているということになります。

…そういう情報を知って、何をしようと言うのか。

正直分かりませんが、orz、たとえば DOM が 80% になっていたら、その関数は「DOM 操作をいっぱいやっている」というのが分かります。。

名探偵コナンみたいに、細かい情報に手が届くと、意外な真実が見えてくる…ということだと思います。orz

orz は象形文字 "列" で、四つん這いになってうなだれている人を表しています。


この画面でツリー状の項目の一つを選択すると、画面上部のグラフ盛り上がりで色が濃くハイライトされます。

CPU 使用率の盛り上がりを「構成する部分をハイライトしている」ので、その盛り上がりを崩したいなら、その処理を外せばよいという理屈になります。

2. Flame Graph タブ

英文公式サイトこれの翻訳:

  1. Flame Graph は、全く同じ Call Tree 構造をより視覚的に表示します。
  2. 長方形が大きいほど、実行時間が長いことを意味します。
  3. 上部の長方形は、セルフ実行時間に影響するスタック、つまりプログラムが実際に時間を費やしているコードです。
  4. 順序は常に同じなので、異なる範囲選択間だけでなく、異なるプロファイル間でも比較しやすくなります。
  5. 視覚的にわかりやすいため、Call Tree よりも Flame Graph を好むユーザーもいるでしょう。

前のタブの「Call Tree」をそのまま視覚化したものだ、と説明されています。

「上部の長方形」とはその関数を内部的に構成する処理のことを言っていて、関数の実行時間はその内部の処理の時間の合計だと言っています。

このタブでも長方形(項目)をクリックすると、画面上部のグラフ盛り上がりで色が濃くハイライトされます。

感覚的に分かりやすいということですね。

3. Stack Chart タブ

英文公式サイトこれの翻訳:

  1. Stack Chart パネルにもサンプルデータが表示されますが、これはサマリー(要約)ではなく、タイムラインに沿った時系列ビューです。
  2. 上のスクリーンショットでわかるように、同じカテゴリが同じタイムスタンプで表示されます。そのため、以前のパネルとは異なる視点で同じデータを表示できます。
  3. ただし、注意が必要です。このパネルでは関数呼び出しのシーケンスを再構築しようとしますが、サンプルデータの性質上、短いイベントが欠落する可能性があるため、短い呼び出しのシーケンスであるはずの呼び出しが長い呼び出しとして表示される場合があります。

前のタブの Flame Graph が要約だったのに対して、こちらは時系列に沿ったグラフだと違いを説明しています。

同じカテゴリは同じ色で色分けされていて、それらが集まって同じ時間に表示されている、と説明されています。

しかし、設定でサンプリング間隔が長く設定されていると、取りこぼしが発生するので、取りこぼしにより欠落した分 A は、欠落しなかった分 B で埋められ、その結果その欠落しなかった分 B が通常よりも長く表示されてしまう、と言っています。


グラフの部分をクリックして選択すると、それは画面 上半分の時系列に沿ったグラフにきちんと対応します。

▼ RefreshDriver tick というグラフをクリック。上部の 10 個がハイライト。

▼ その構成である Update the rendering Animat... というグラフをクリック。上部の 3 個がハイライト。

▼ となりの構成である Update the rendering Paint というグラフをクリック。上部の 7 個がハイライト。

Flame Graph タブと比べると、「時間の流れとしてはどうなのか」という視点でその項目の処理の重さを客観的に見られる、、というのかなぁ。

4. Marker Chart タブ

英文公式サイトこれの翻訳:

  1. Marker Chart は、プロファイリングセッション中に発生したすべてのマーカーを時系列で視覚的に表示します。マーカーデータはソースコードインストルメンテーション(C++、Rust、JavaScript、Java)から取得されます。
  2. マーカーにマウスオーバーすると、そのデータを確認できます。例えば、CSSアニメーションマーカーの場合、アニメーションが実行される要素が表示されます。
  3. マーカーを右クリックするとコンテキストメニューが表示され、データのコピーなど、これらの情報をさらに操作できます。
  4. Web開発者は、performance.markperformance.measure を使用して独自のマーカーを追加できます。Gecko開発者は、この包括的なドキュメントを参照して新しいマーカーを追加できます。
  5. マーカーは、コンマ区切りの検索語句リストでフィルタリングできます。
    各検索語句は、
    • 一致する部分文字列、
    • より具体的に一致する [key]:[substring]、
    • 一致するものをすべて除外する -[key]:[substring]
    のいずれかで指定できます。
    (-[substring] は機能しません。否定一致には -[key]: が必要です。)
    有効なキー key 値は、
    name、cat(マーカーカテゴリの場合)
    type(ペイロードオブジェクトを持つマーカーの場合)
    およびマーカーのスキーマで宣言された任意のマーカーペイロードフィールドキーです。
  6. 例:DOM,cat:GC,-name:CSS は、
    カテゴリ、名前、タイプ、または任意のフィールドに DOM が含まれるもの、およびカテゴリに「GC」が含まれるものに一致しますが、名前に「CSS」が含まれるマーカーは除外されます。

1行目の、「ソースコードインストルメンテーション」のインストルメンテーションとは、英単語としては、「Instrumentation楽器法、管弦楽法、器具使用、器具類」という意味ですが、IT 用語としては、「遠隔より管理するための技術」という意味のようです。

つまり、「ソースコードインストルメンテーション」とは、「ソースコードの中において、プロファイラに対して指示を与えるしくみ」ということのようです。

3DCG 人体モデルのプログラムの中で、performance.mark( マーカー名 ) で「マーカー」を置き、

performance.measure( name, 開始マーカー名, 終了マーカー名 ) で、それらのマーカーの範囲に名前を付けます。

測定したい開始部分と終了部分にマーカーを置き、その後で範囲に名前を付けます。

それで同様に測定すると、、

このように Marker Chart のなかで UserTiming として測定してくれます。

ヌンチャク2つ分のポリゴン作成で、22.8us かかっていることが分かりました。

これでポリゴン作成部分について集中して高速化の結果が見られるようになったわけです。

この範囲を右クリックすると、

Set selection from marker's duration マーカーの継続時間から選択範囲を設定
Start selection at marker's start マーカーの開始位置から選択範囲を開始
Start selection at marker's end マーカーの終了位置から選択範囲を開始
End selection at marker's start マーカーの開始位置から選択範囲を終了
End selection at marker's end マーカーの終了位置から選択範囲を終了
Copy description 説明をコピー
Copy page URL ページURLをコピー
Copy as JSON JSONとしてコピー

たとえば、一番上の「マーカーの継続時間から選択範囲を設定」を行うと、

画面下部はそのマーカー範囲を全開にして表示して、

画面上部はその範囲のみハイライトし、範囲外はグレーアウトされます。

ハイライト中の虫眼鏡アイコンをクリックすると画面上部もその範囲で全開(拡大表示)します。

その他:

5. Marker Table タブ

英文公式サイトこれの翻訳:

  1. このパネルは、Marker Chart と同じデータを表形式で表示します。
    検索機能により、複数のマーカーのペイロード情報を一度に表示できるため、より高速に表示できるという利点があります。
  2. フィルタリングは Marker Chart と同様に機能します。

ペイロードというのは「有効荷重、積み荷」という意味のようです。

各マーカーには、名前、カテゴリ、共通のオプション情報(タイミング、バックトレースなど)、および特定のタイプのオプションペイロード(そのタイプに関連する任意のデータを含む)が含まれます。

というような記載があるので、おそらくペイロード=情報だと思います。

ペイロードオブジェクトと言ったら、「情報を積んでいるオブジェクト、そのためのオブジェクト」という意味合いだと思います。

6. 2つの測定を比較する

比較の機能はあるにはあるのですが、最終的な画面でうまく比較できない感じがしています。

というのも、同じ処理でも JavaScript は臨機応変に処理を変えているみたいで、比較しづらいという結果になっているのかな、、と思います。

私のやり方が何か間違っているのかもしれませんが…

結果はあまり良くない、とあらかじめ言っておきますが、この機能を試す手順を以下に示します。


6-1. 比較のための準備

6-2. 改善前のページについて準備する

6-3. 改善後のページについて準備する

6-4. 比較を行う

6-1. 比較のための準備

F12 キーを押すなどして「開発ツール」>「パフォーマンス」タブを表示します。


そしてボタンのすぐ下のリンク profiler.firefox.com をクリックします。


すると「Firefox Profiler」の公式サイトが表示されます。


スクロールして一番下の、

You can also compare recordings. Open the comparing interface.

のリンクをクリックします。

これは「記録を比較することもできます。比較インターフェースを開いてください。」と書いてあります。


すると図ようなページが表示されます。

翻訳は以下の通りです。

Firefox Profiler
Firefox のパフォーマンス分析用 Web アプリ

比較したいプロファイルの URL を入力してください。
このツールは、各プロファイルについて、選択したトラックと範囲からデータを抽出し、同じビューに表示して簡単に比較できるようにします。

プロファイル 1: http://
プロファイル 2: http://

[ プロファイルを取得 ]

この画面に、

改善前のページの測定プロファイルの URL

と、

改善後のページの測定プロファイルの URL

を記入することになります。

今はまだ記入しません。


測定の前に、設定の画面を開いて、

  • サンプリング間隔: 0.2 ms
  • スクリーンショット:チェックを外す図)

という設定にした方がいいかもしれません。

関数の検知を取りこぼしてしまうと、比較しづらくなりそうなので、サンプリング間隔は細かくします。

また、慣れるとスクリーンショットはあまり見ないので、オフにして、CPU の力を測定のほうに注力させます。


6-2. 改善前のページについて準備する

改善前のページの測定を行います。

測定結果が表示されたら、

まず「Stack Chart」タブを表示します。


ツララのようにグラフが並んでいますが、この中で、下方向に十分に伸びているグラフを探します。


このグラフをとらえるように、画面の上半分のグラフ上でドラッグして範囲選択します。


すると、上半分では範囲選択され、下半分ではその範囲が拡大されて表示されます。


次に上半分の範囲選択の中の虫眼鏡のアイコンをクリックします。


すると、上半分のほうも範囲が拡大されて表示されます。

下半分のツララに合わせて、上半分で盛り上がりがあります。


この部分を範囲選択します。


すると、下半分の方でグラフが大きく見えます。


次に「Marker Chart」タブをクリックします。


すると、このようになります。

この中で、比較するのにちょうど良さそうな処理を選びます。

ここでは requestAnimationFrame callb... を選ぶことにします。


requestAnimationFrame callb... の行にある青いグラフを右クリックして、表示されるメニューの一番上の Set selection from marker's duration を選びます。


すると、下半分の画面が、

requestAnimationFrame callb... 青いグラフをめいっぱい表示するように拡大表示されます。


次に上半分の範囲選択の中の虫眼鏡のアイコンをクリックします。


中途結果として最終的にこのような画面が得られます。

このブラウザ画面はこの状態でひとまず置いておきます。


次に、改善後のページの測定を行います。

繰り返しになりますが同じことをやります。


6-3. 改善後のページについて準備する

改善後のページの測定を行います。

まず「Stack Chart」タブを表示します。


ツララのようにグラフが並んでいますが、この中で、下方向に十分に伸びているグラフを探します。


このグラフをとらえるように、画面の上半分のグラフ上でドラッグして範囲選択します。


すると、上半分では範囲選択され、下半分ではその範囲が拡大されて表示されます。


次に上半分の範囲選択の中の虫眼鏡のアイコンをクリックします。


すると、上半分のほうも範囲が拡大されて表示されます。

下半分のツララに合わせて、上半分で盛り上がりがあります。


この部分を範囲選択します。


すると、下半分の方でグラフが大きく見えます。


次に Marker Chart ボタンを押します。


すると、このようになります。

この中で、改善前ですでに選んだ処理の行を見ます。

改善前では requestAnimationFrame callb... を選んでいました。


requestAnimationFrame callb... の行にある青いグラフを右クリックして、表示されるメニューの一番上の Set selection from marker's duration を選びます。


すると、下半分の画面が、

requestAnimationFrame callb... 青いグラフをめいっぱい表示するように拡大表示されます。


次に上半分の範囲選択の中の虫眼鏡のアイコンをクリックします。


中途結果として最終的にこのような画面が得られます。

このブラウザ画面はこの状態でひとまず置いておきます。


改善前と改善後とで準備が整ったので、次の作業に移ります。


6-4. 比較を行う

改善前のページを表示します。

そして、右上の「Upload Local Profile」をクリックすると図のような小さな画面が表示されます。

翻訳すると、

パフォーマンス プロファイルを共有

プロファイルをアップロードし、リンクを知っているすべての人がアクセスできるようにします。
デフォルトでは、個人データは削除されます。

より詳細にするための追加データを含める
□ 非表示のスレッドを含める
□ 非表示の期間を含める
□ スクリーンショットを含める
□ リソースの URL とパスを含める
□ 拡張機能情報を含める
[ Download(800KB) ] [ Upload ]

と書かれています。

今回はどれもチェックしないで説明を進めます。


青い Upload ボタンを押しましょう。


すると、右上に URL が表示されます。

これをコピーして先ほどの Profile 1: と Profile 2: の記入欄が縦に並んだ画面

を表示させ、画面の Profile 1: に貼り付けましょう。


改善後についても同様に、右上の「Upload Local Profile」をクリックし、「Upload」を行い、表示された URL をコピーし、この Profile 2: に貼り付けします。


そして、Retrieve Profiles ボタンを押します。


これで、やっとで比較の画面が表示されます。

表示させたのは良いのですが、先にお伝えしたように、この画面の活用方法がよく分からないんですよね。


そのほか、frame や draw などの関数のグラフに注目して、かかっている時間を見ると、

frame 1.4 ms → 0.79 ms

draw 1.2 ms → 0.79 ms

という速度短縮のかたちで改善が見られます。

…でも正直、ちょっとうまく比較できない感じがしていて、私のやり方が悪いのか、この比較機能は謎の状態です。


改善前と改善後の測定結果をウィンドウで左右に並べて比べたほうが早いかも。


▼requestAnimationFrame の処理内容の違いを大まかに確認できる。


▼ツリーを並べて、各項目の処理時間を右端に表示させて比べることができる。


▼改善前(左)と改善後(右)のツララを見ると改善後はツララが少なく見える。(改善されてる)


▼フィルタで requestAnimationFrame call.. のみ表示。ざっと一覧し右のほうがかかる秒数が少ないのがわかる。


▼frame() 関数の前後に置いたマーカーで一覧表示。

プロファイラを使って、秒数を手軽に見ることができるのでオススメ。

(用意された比較機能はまた今度使えるようになったら使えば良いでしょう)


(訪問者のどんなニーズと この記事がつながるか)

  • プロファイラの使い方
  • プログラミングの話
  • 日記を読みたい