FujisakiHaskellで書いたウェブサービスの習作です。ウェブサービス、習作ばっかりつくってる気がしてならない。画像をぴったりサイズ指定して印刷するコミケのサークルカットを印刷するのには毎回毎回苦労しています。5.7cmに印刷して所定の用紙に貼り付けて申し込む必要がありますが、そのサイズを指定して印刷するのが結構めんどくさい。プリンタへUSBで接続したプリンタへ、直接印刷できるときはGIMPを使えばサイズを指定して印刷できます。コンビニのプリンタなどのプリンタの場合はそのような指定を行う事が全くできません。画像を印刷すると固定の解像度で出力されたり、拡大されて出力されたり...。この問題を解決しない限りコミケに申し込むのが億劫なので、いい加減システムで解決することにしました。このサービスを使うと、画像を指定したサイズに印刷するためのpdfを生成してくれます。このpdfを共用プリンタで「等倍指定」で印刷すると、ちょうどぴったりのサイズで印刷してくれます。少なくともセブンイレブンの印刷機ではうまくいくのを確かめました。同じことなので、今回は言語を変えてHaskellで書いてみました。JavaScriptコンピュータ上で浮動小数点を表現する方法はいくつか存在する。だいたいのアーキテクチャではIEEE754標準を使っている。float32)を32bitで表している。名前が示す通り、float64はfloat32よりもっと正確なんだ。パフォーマンスがあんまり重要視されないときは大体こっちを使おうねってアドバイスされる。float32を使うことにもこんな利点がある:float32は計算操作をするのにfloat64よりCPUサイクルがしばしば少ない。float64の計算をfloat32に対して行うには、いくらかオーバーヘッドが存在する。float64に変換しないといけないから。これは、Float32arrayを使ってデータを格納しているときにしばしば発生する。WebGLを使ったコードや、EmscriptenでコンパイルされてC++のfloatがfloat32として格納されいるけど計算はJavaScriptのfloat64として行っている場合ではよくあることだ。float32は半分のメモリしか使わない。プログラムのメモリ総使用量を減らすし、メモリーがボトルネックになるような(memory-bound)計算では速度も速くなるだろう。float32に特化した標準C関数は、我々の調査したいくつかのライブラリではfloat64のバージョンより速かった。JavaScriptにおいて、Number値はfloat64として定義されていて、すべての計算もfloat64として行うことが定義されている。JS処理系がいままで長い間高速化のために利用していたfloat64の特性が、float64の値が十分小さい整数のとき、float64での計算結果は整数での計算結果と全くおなじ、ということだ(桁溢れが発生しなければ!)。JS処理系は、この特性を、整数の値を(floatでなく)生の整数として保持しておいて、整数演算で計算することで(そして桁溢れをチェックすることで)利用してきた。Wingoの書いたこの値の表現に関する記事を見てみてね!)。これらを全部踏まえると、一ついい感じの疑問が湧いてくる:似たような事を、float32で出来ないかな!?答えは「しばしば可能」で、ES6で追加される新しいMath.fround関数を使うとプログラマ側がいつfloat32演算を使うかをコントロールすることが出来る。21-26)で書いているんだけど、float32とfloat64で交換がなりたつ恒等式がいくつかある。入力がfloat32であるかぎり、float32での演算とfloat64での演算、どちらを使ってもよくて、同じ結果を得られるんだ。正確に言うなら、{+,-,*,/}のそれぞれの演算について、float32版の演算を行う関数であるop_f32と、float64版の演算を行う関数であるop_f64があったとすると、次の恒等式が成り立つんだ。));似たような恒等式が、sqrtとかその他の数学関数のような単項演算にも成り立つ。この性質は、それぞれの演算の前後でfloat32にcastした場合に成り立っている。x=1024でy=0.0001で、さらにz=1024であるときに、(x+y)+zはfloat32の二回の足し算として見た時とfloat64の二回の足し算とみた場合では、もはや同じ結果にはならない。この恒等式は、コンパイラがfloat64の演算のかわりにfloat32の演算を使って最適化を行うための、前提条件を与える。実際に、gccはこの恒等式を利用していて、さっきの恒等式の右辺のような式(float64のコード)があったら、左辺のような式(float32の式)を生成する。APIが使えるHTML5では、答えはこうなる:Float32Arrayから読む時と、書く時、だ。1;ループの中の足し算のコードは、たしかにさっきの恒等式と一致してる:f32配列からfloat32の値を持ってきて、float641に変換され、float64同士の足し算演算が行われ、そしてf32に書き戻すためにfloat32に再度キャストされる。(足している”1′′というリテラルも、float32からfloat64にキャストしていると考えた。float32で1は正確に表現できるからだ)でも、もっと複雑な式を書きたいときはどうしよう?不要なFloat32Arrayへの出し入れを式のそれぞれの式の一部分に追加して、計算した式の一部分の結果がつねにfloat32にキャストされるようにすることも出来る。これらの追加の(不要な)出し入れはプログラムを遅くするし、我々の目標はプログラムを速くすることなので、それでは意味がない。うん、十分に頭のいいコンパイラーさんなら確かに不要なこの手の出し入れを除去することができる。性能の予測可能性は重要だから、もっとこの最適化が行われることを確実にしたい。6規格で取り入れられた:Math.froundだ。Math.froundMath.froundは来るべきES6標準で提案されている、新しい数学関数だ。この関数は入力された値をいちばん近いfloat32の値にまるめて、そのfloat32をNumber値として返す。}いくつかのブラウザではTypedArraysに対応していないけど、そういう時はまた別の複雑なpolyfillがあるよ。TypedArraysを使わなくても、SpiderMonkey(FirefoxのJSエンジン)とJavaScriptCore(SafariのJSエンジン)の両方ですでにMath.froundが実装されているよ。実装されてるっぽい)。1;プログラマに速いコードを書くのを許すだけではなく、EmscriptenみたいなJSコンパイラにも、float32のコードをよりbetterにすることを許す。Emscriptenは現在C++のfloat演算をJSのNumberを用いたコードにコンパイルする。技術的には、Emscriptenは原理的にはFloat32Arrayへの出し入れを全ての演算ごとに行うことで余計なfloat64精度を取り除くことは出来るけど、とても遅くなってしまうだろう。忠実性はパフォーマンスのために犠牲にされた。この違いが何かを壊すことは非常にレアなんだけど(もし壊れてしまうなら、そのプログラムは「未定義動作」に依存してるということだ)、この違いがバグを起こした所を実際に見たことがあって、しかもそういうバグを追跡するのは全く楽しくない。IonMonkeyわたしのインターンシップでのプロジェクトは、Firefoxにこれらの最適化を組み込むことだった。JITバックエンドに追加した。つぎに、Math.froundを一般的な(最適化されていない)ビルトインとしてJavaScriptエンジンに追加した。最後に、Float32Array/Math.froundを使っているのを認識してfloat32演算を出来る限り使うように最適化する最適化パスをコンパイラに追加した。StableでもVersion30ですね)で、どれくらい早くなるんだろう?ちいさなベンチマークでは(C++とJavaScript両方で)、50%くらいの大きなスピードアップを示した。いうちいさなベンチマークはしばしばミスリーディングを引き起こすから、わたしは次のようなもっと現実的なベンチマークを書いて、float32の大量の演算がどれくらい速くなるか、実際にありそうな問題で調べた:逆行列計算:このベンチマークでは、いくつか行列を作って、その逆行列を計算し、float64とfloat32でどれくらい精度が失われるかを比べるために、もとの行列と掛け算した。このベンチマークには、gl-matrixの改造したバージョンを利用した。gl-matrixは、WebGLの行列計算を使ったフレームワークで、実世界のアプリケーションでも使われている。:このベンチマークでも行列をいくつか生成して、画像処理でよく使われる行列の計算を行う。移動・回転・スケーリング、などなど。このベンチではたくさんの基本操作や複雑な操作(例えばMath.cosやMath.sinを回転操作で呼び出す)をおこなう。float32の演算を最適化したとき、このベンチではすごい改善が見られた。このベンチマークも、gl-matrixの改造したバージョンを使っている。指数関数:このベンチマークでは、大きなFloat32Arrayを予測可能な値で埋めて、つぎにそれぞれの要素の指数関数を、テイラー展開をつかって求めた。このベンチマークの主な目的は、足し算と掛け算の重さを測ることだ。高速離散フーリエ変換:このベンチマークでは、適当なニセのバッファを作って、高速離散フーリエ変換(FFT)を行った。このベンチマークには、他にも基本操作と数回のMath.sqrtへの呼び出しが含まれている。FFTのコードは、既存のライブラリ、dsp.jsから取ってきた。Nightlyを実行したときの結果を示している。それぞれの数値が、Math.froundの呼び出しを追加してfloat32最適化を許すことで得られたスピードアップを示している(だから、高大きい方がより良いことを示している)。NightlyのAndroidバージョンの事をしめしている。この結果をひと目みたら、自分でもベンチマークを取ってみたいって思うんじゃない?ここで自分で実行できるよ!(あ、でも忘れないで。一度他の関数に代入することによる?))違いをうまない。すべてのモダンなJS処理系はだいたい小さな関数をハイエンドなJITでインライン化するので、このplyfillはパフォーマンス上のペナルティになることはない。それを確かめるために、例えばChromeの開発版で実行することもできる。いくつかの巨大なソースコードではインライン化が実行されず、plyfillのせいでパフォーマンスが悪くなったこともあった(たぶん、インライン化の深さ制限か、関数が何かしらの理由でJITコンパイルされなかったんだろう)。簡単に言うと、「試してみる価値はある。結果は私達を元気づけてくれるものだった。Math.froundはES6の標準にはいるから、ほかのJS処理系も同じ最適化を行うようになってくれることが期待される。Webをゲームプラットフォームにしたいという観点からすると、低レイヤの今回のような最適化はより重要性を増してきていて、Webをよりネイティブ性能に近づけてくれると言えるだろう。FirefoxのNightlyかAurolaでこの最適化をテストして、何かバグを見つけたらぜひ教えてね4。校正とフィードバックを、そして、JavaScriptチームのみんなにヘルプとサポートを。感想JSの最適化もここまで来たかーって感じがします。今後はこういう「プログラマと処理系が手を組んで行う最適化」が増えてくるのでしょうかね。いう最適化はたぶんまだ沢山できて効果がありそうな一方、プログラムを書いている人が知ってるか知ってないかでかなり速度が変わってくるわけで、JSを書くのがどんどん大変になっていきそうな...。JSはアセンブリなんや...(何)。#1に行って発表してきましたっ![全画面/表示されない場合]タイムシフトがまだ見れます!わたしが喋ってるのはここからです。最初、「コンパイルの話ばっかりなのかなぁ、わたし、そこまでコンパイル時処理は書いてないんだけど...」と思って普通に聞く側で行こうかと思っていました。requestとか送ってたなあ、結構いろいろ...」と思ったので、その時に活躍したvalgrind(ヴァルちゃん)の話をしてLT枠で参加することにしました!ぶっちゃけcocos2d-xがC++製だったのを忘れていたのは秘密だ!valgrindも色々制限があったり(たとえばリークの検出は、ちゃんと最後まで実行できないとうまくいかない)、遅かったりと万能ではないのですが、それでも最終手段として使ってみるのはアリだと思います。その後に知ったのですがLLVMにも同じようにメモリのアクセスをチェックしてくれる機能があるらしいです。cocos2d-xは...もう何も言うまい。UnityもUnityで☆闇☆ですけどね...。コンパイル時の話が多かったので(?)家からドワンゴに行くまでもこの格好で行ったのですが、ガン見する人とさらっと流す人が明確に分かれてたので面白かったです。異性装みたいな感じです。白衣着てるのがこの社会では一番変な人扱い、らしい。その後は妖怪ハウスで江添さんのピザを頂いたりしました。FujisakiFortranが書きたくない!!わたしは大学で天気とか地震とか海流といった「地球物理」と呼ばれるジャンルを学んでいるのですが、このジャンルではコンピュータをガンガン回して計算しまくります。気象庁が毎日やってる天気予報もそうですし、「地球シミュレータ」でエルニーニョや地球温暖化の予測とかをやっているのもそうです。いう学科なので、コンピュータの実習があるのですが、なぜか今でもFortranを教えています。たしかにFortranには、LAPACKのような優れた数値計算ライブラリがあったり、スパコンで書く時はコンパイラのサポートが充実してたりします。いう特定の用途には悪くない言語だと思うのですが、デスクトップPCで十分扱えるようなデータサイズで、そもそも計算速度はさして重要ではなくてデータ解析手法を学ぶ実習で使うのには辛すぎます。陽には書きたくありません。1でも書かないと単位くれないんだってさ!なので、Fortranを書かずにFortranを書く方法を検討して、実際に実装しました。今回のソースコードも、すべてgithub上に上げています。Fortranを書かずにFortranを書くには?最初、Fortran上で小さなVMのインタプリタを実装して、そのVMの仮想機械語にコンパイルされる言語のコンパイラを実装しようと思いました。VMを書くのが普通にしんどいのと、言語とコンパイラをこのためだけに実装するのもかなりつらいので、もっとラクな方法は無いか考えてみました。冷静に考えると、別にVMのインタプリタを実装しなくとも、コンパイルされたFortranのコードは既にCPUという名前のx86機械語のインタプリタ機械(?)で実行されています。このインタプリタ(?)を利用できないでしょうか。(*ptr)(double*)のポインタにキャストする必要があります。Fortranは割りと型に厳しく、Cのように簡単にキャストしたり出来ません。撚(ひね)る必要があります。typeこのように2つ構造体(IPointer,FPointer)を作っておき、この2つの構造体のポインタをC言語のポインタを経由することで無理やりキャストします。645番目の要素がC言語側エントリポイント関数の頭なので、そこへのアドレスをセット。構造体へのポインタを一度Cのポインタ(いわばvoid*)にキャストし、それを再度void*からFPointerのポインタにキャストしています。構造体の中身の型が「intへのポインタ」と「関数へのポインタ」で違うので、構造体を通して、その中身についてキャストすることができました。なんでこんな七面倒臭いことをしているかというと、intのポインタと関数のポインタを直接キャストすることができないからです。ンタにするような操作もできません。一回構造体でくるんでしまえばどちらも値のポインタとして扱い、キャストすることができます!さらに、現在のLinuxでは、通常データ領域をプログラムとして実行することはできない(DEP)ので、コンパイルする時にその制限をはずす必要もあります。execstackというオプションスイッチが使えます。これで、任意のx86機械語の列をFortranから実行することがきました。あとは、その機械語の列をどうやって作るかという話になりますね。原理的には自分で書いてもよいのですが、一応課題をやっているコードなので、結構複雑です。C言語で書いたコードをコンパイルして、その結果を利用したいところです。SideコードのコンパイルにはGCCを使うことにしましょう。この時問題になるのは、Fortranの動的に配置した配列の上に機械語を置くので、機械語の置かれるアドレスが実行時になるまでわからないことです。機械語がどこに置かれるかがわかっていることが前提の通常のコードはうまく動いてくれません。そのようなケースは実は珍しくなく、共有ライブラリもアドレス上のどこにロードされるかは分からないので、ちゃんとそのためのコードをコンパイラが出力します2。Code)と呼びます!gccでは、-fPICというコンパイルオプションをつけると、PICとしてコンパイルしてくれます。Codeところがどっこい、これではうまく行きません。なんでかというと、一個前に翻訳した記事で解説されている、PLTとGOTがあるからです。こんな非常に簡単なC言語コードを考えてみましょう。test.oコンパイルした結果が、こんな感じでーす。retこんな感じで、ばっちりpltを経由して実行してしまいます。この機械語をFortran上に載せて強制的に呼び出したとしても、PLTがちゃんと解決されないとうまく動いてくれません。デフォルトのPLT解決処理は共有ライブラリとして実行されていることが前提になっているので、今回は利用することはできません。integer配列のアドレスから関数のアドレスを解決することは原理的には可能なので、Fortran上でPLTのアドレスの所を埋めてちゃんと実行できるようにすること自体は可能(なはず)です。意味もなく複雑になってしまうので、できればPLTにジャンプするのではなくて、呼びたい関数に直接ジャンプするようなコードを出力して欲しいです。PLTを経由して関数を呼び出す時に使ってる0xe8命令は相対アドレスによるcall命令なので、可能なはずです。リンカースクリプトを自分で書けばPLTとGOTを使わない...!?リンク時にPLTを経由するようにリンクしているので、じゃあすごく単純なリンカースクリプト自分で書けばPLTみたいな便利機能使わなくなるんじゃない?ということで、実際に書いてみました。./linker.scriptこんな感じにすると指定できます。実をいうと、リンカースクリプトなんて書かなくても、-pieというオプションを付けて、通常の実行ファイルとしてコンパイルすればplt/gotを使わないコードを生成してくれます。executableということで、実行時にどのアドレスに配置されても実行できる実行ファイルを生成してくれます。実行ファイルは共有オブジェクトとは違って外部の他のプログラムからその中の関数が呼ばれるようなことはないので、内部のそれぞれの関数のアドレスを動的に解決する必要はありません。PLTを経由せずに直接callするようなコードにしてくれる...のでしょう。...でも冷静に考えると、共有ライブラリの中でならPLT経由せずに直接ジャンプでよくない?全てが終わったあとに気づいたので時既に遅しという感じでしたが、一応書いておきます。プログラムセクションを抽出する残るところは、出来上がったファイルから機械語を抽出して、数字の列にするだけです。そのために、機械語が書いてある部分はファイルのなかでどこなのかを調べましょう。READONLY機械語本体が置かれているのは、.textというセクションです。この中のデータをFortranに書きましょう。offの項目が、ファイルの先頭からの位置で、Sizeという項目がセクションのサイズで、今回の場合、要はプログラムコード全体のサイズです。関数呼び出しするには、呼び出したい関数の、プログラムコード内での相対位置も必要です。これには、nmコマンドが使えます。_startセクションの所のVMAもしくはLMAというのがメモリ上での位置なので、機械語列の中での相対位置を調べるにはここでのアドレスから.textセクションのVMA/LMAの値を引けば求めることができます。シェルスクリプトを使うともちろんこの作業は自動化できます。呼び出す関数名は”ep”としました。}')Cのソースをコンパイルする所から、命令列を抽出してFortranのソースに変換する作業まで、これらの作業はすべて自動化しているので、よかったらgithubのソースを見てね!今回書き込んだinteger型は4バイトなので、エントリポイントのオフセットが4の倍数でないとき(よくある)には、その整列条件を満たすために数バイトズラしたりしないといけません。そのコードとかも入ってます。外部にあるライブラリ関数などは呼び出すことができないという、非常に大きな制限があります。実行時に決定されるライブラリ関数の実際のアドレスを知らないし、知るためにはライブラリ関数を呼び出さないといけないので、「服を買う服がない」状態なんです。Fotranでなら調べられるので、調べた結果をC言語の関数に投げればいいんですけど、Fortran書きたくないし(ぉ)。科学計算すればいいだけなので、sin/cosとsqrtくらいが使えればなんとかなります。sin/cos/sqrt/absは、FPU命令を使って計算できるので、ライブラリは必要ありません。そのためには、インラインアセンブリで陽にFPU命令を実行する必要があります。v;}こんな感じのを、FPUの命令ごとに実装していきます。関数だけでなく、piもFPUを使って取得しています。なんでかというと、今回は機械語だけを埋め込んで、それとは別にある定数の領域をFortranのソースに含めていないからです!Cのソースに円周率の値を直接書いていてもすこし複雑な処理になると定数領域に飛ばされてしまって、.textセクションに含まれなくなってしまい、結果としてプログラムがおかしくなってしまいました。定数領域もFortranに埋め込むだけでその問題は回避できるのですが、その改修がちょっとめんどくさかったのでやめて、FPU命令を毎回呼ぶことで回避しました。入力と出力ですが、実際のデータの読み込みと結果の出力のみ、Fortranが担当することにしました。*)とし、doubleの配列を処理してその結果をもとの配列の領域に書き込むことだけに専念していただきます。他の言語でやってみても面白いかもね!と思った。Fortranはなんだかんだ任意の機械語を実行する難易度は低い言語だったと思います。ここまでやるならFortranで書いたほうが早くね?なんでFortranせなあかんねんと。その時間を使ってね、バイナリおじさんをする。いう考え方もできると思うんですよ。スパコン向けでもないのにFortran書いてもこころぴょんぴょんしないでしょ。2014ひさびさに色々バイナリが触れてたのしかったです。2014っていうか、いままでプログラム書いたことがない人にいきなりこれ教えるのってどうなんでしょう...。[↩]Windowsでは、64bit版からそうなりました。32bitでは固定だそうです。Symbolsの翻訳です。SRの人ありがとう!訳はがんばりましたが、間違ってる所もあるかもしれません。そこはご了承ください...。Table)に飛ぶよ。PLTを使うと、関数の実際のアドレスを実行時に解決することができる。言い換えると、共有オブジェクトが実際にどこにロードされるかは実行時にならないと分からないので、PLTを使って実際のアドレスを解決している。804833cそれで、次がこのサンプルファイルのPLTだ。さっきの命令で呼んでたアドレス0x804833cに注意してね。ここには実際には単なる*0x8049684へのジャンプ命令が並んでいる。(GOT)を探せばいい。"...2...B...R..0x8049684には、42830408(リトルエンディアンで0x08048342)が書いてあった。$0x18′′という命令を実行することが分かる。0x18=24っていうのは、再配置テーブルへのオフセットアドレスだ。80482fc”が実行される。この80482fcに書いてある命令列は、PLTの他の部分と大分ちがっているのはすぐ分かるだろう。*0x8049674′′は結構重要だ。最初push命令はアドレス0x8049670をスタックにpushしていて、これはGOTを指している。次の(*0x8049674)へのジャンプだけど、このアドレスもGOT内のアドレスだ。この2つのアドレスは、ELFファイルの中ではどちらも”0′′が書いてある。いうのも、これらは実行時に動的に埋められるからだ。実行時には、最初のアドレスの値は特定のライブラリが使われているかを識別するための番号になり、次のアドレスにはリンカーのシンボル解決ルーチンのアドレスが入る。これらのルーチンは前にスタックにpushされた0x18を、正しい場所を解決するために使う。linking)」と呼ばれている。再配置された関数のアドレス解決が、実行時に、しかも必要な時に、実際に関数が呼ばれた時だけ行われるからだ。$0x18をするコードのアドレスではなく、解決された関数アドレスに直接ジャンプするようになる。それじゃあ、どうやって関数のアドレスを解決するためのシンボル名を取ってくるんだろう?関数のアドレスを解決するには、’0x8049688’ではなくて、’sprintf’という文字列が必要だ。Elf32_Rel;さて、readelfでELFファイル内の再配置エントリを見てみよう2。putcharこれらの情報はいったい何処から来たんだろう!?Offset/Info/Type/Value/Nameのそれぞれの情報、さっきの構造体に無かったよね?えっとね、これらの値のうち、殆どは実はr_infoフィールドから直接もってこれるか、r_infoフィールドを間接的に使うともってこれるんだ。offsetの値はPLTの中を指していて、ここに再配置エントリの実体が置かれる。バイナリを逆アセンブルすると、PLTの中の正しいオフセットにある、このアドレスへjmpする命令が見えるはずだ。今注目したいのは、r_infoフィールドだ。0xff)これらのマクロはr_infoからそれぞれ別の値を得るためのマクロだ。いっこずつ見てみよう。SHT_RELタイプをもつセクション(さっきからみてる、再配置セクション)は他にsh_linkっていうメンバを持っている。このsh_linkは重要で、これらの再配置情報のシンボル情報(関数名とかね)を持つ、ほかのセクションを指しているんだ。このsh_linkは、gccでは大抵「dynsym」というセクションを指している。...セクション7と8のsh_linkメンバを見て。table)セクションを指している。この第4セクションが、再配置エントリに対応するシンボルネームが書いてあるセクションだ。ここまでやってきたことをまとめておくと、(1)SHT_RELタイプがついてるセクションを探して、(2)sh_linkメンバーの指し示すセクションをたどった。よし、色々数字があるのは分かったから、あとはそれをどう組み合わせていけばいいんだろう。再配置テーブルのエントリを一個一個見ていって、r_infoフィールドの値を持ってきて、ELF32_R_SYM(val)マクロを使って値を取り出していけばいい。この数字はdynsymセクション(か、sh_linkの指している他のセクション)内のエントリに対応している。dynsymテーブルをパースしてエントリを調べれば、シンボルネームが解決できる。他のマクロは何なんだろう?ELF32_R_TYPE(val)は再配置がどんなタイプなのかを教えてくれる。その値はelf.hにR_386_GOT32とかR_386_JMP_SLOTみたいなのが定義されている。この定義を使うと、再配置テーブルが関数のためのエントリなのかどうかとかが分かる(でも、rel.pltセクションの中のはだいたいそうだ3)。全てのELFファイルがセクションヘッダを持っているわけじゃない。いう時は、プログラムヘッダを使うと、動的なセグメントがどこかわかるし、再配置テーブルのアドレスとか、再配置テーブルのシンボルテーブルがどこかとかが分かる。Fujisakiいろいろあって大学5年目ですが、先週末の学園祭には元気に参加しました!今年の学園祭では、好きすぎて京都まで聖地巡礼に行ってきたアニメ、「いなり、こんこん、恋いろは。」のコンちゃんのコスプレをしてみました!しっぽも生えてるんですが、この写真だと見えないですね~。この格好でお客さんにエルニーニョの説明をしました。うまく説明出来ませんでしたけど!たぶん「いなこん」を全話見た皆様でも「こんなキャラ居たっけ...?」みたいな感じかと思われますが、OPで一瞬だけ出てきます。いなりちゃんといつも一緒にいる子狐の神使・コンちゃんが人間の姿で顕現する時の姿で、漫画ではみんなで一緒に海に行く話のあとの回で登場します。フサ)この話は殆ど他の話のフラグに依存しないし影響もしない、いわゆる「エピソード」なので、10話しかないアニメではカットされたのだろうと思います。漫画8巻の付属BDにはこのエピソードのアニメが入るらしいですよ!買うしか無い!買うしか無い!(回し者)アニメには登場しないキャラなので(狐の姿なら毎回出てるけど)、当然衣装とかは売ってません。いうわけで、人生初のコスプレは衣装から全部自作です!何故人はマイナーなキャラクターのコスプレをしたくなってしまうのか!!洋裁をするのは小学校以来で、その時は間違えて縫って履けないズボンを作って、そのまま心折れて提出もせずに卒業したような気がします。あと3年くらい前に女装してニコ生でiPadケース縫った気がしますが、ありゃノーカン。作る時に考えたことを、断片的にメモっておこうと思います。服の構成要素は既成品改造レベルコンちゃん(人型)の可愛さに気づいて現在パソコンの背景にしている画像がこちらです。この画像をよく見ると、全体の衣装はファンタジーな感じでとてもかわいい1のですが、個々の要素は既存の服をすこし変えたものであることが分かります。マントとフードは、レインポンチョのマントの形を変えたりフードに耳生やしたりすればいけそう上着は、和服から袖を取ったもの。ズボンは普通の半ズボンの上の部分を袴みたいにすればそれっぽくなりそう。この時点で、既存の洋裁の型紙を改造すれば、無理をせず十分それらしい衣装が作れそうだと分かります。あとさらに洋裁初心者には大変うれしいと思ったポイントがとりあえず赤い布と白い布さえあれば作れるというところです。材料があると混乱してミスをしてしまいそうなので、とりあえず白い布と赤い布を大量に買っておけばあとは形をどうしてどう縫うかの問題に落とし込めるのは大変好都合に見えました。ミシンは適当で良かったっぽいミシンすら持ってなかったので、適当に買いました。ミシンもたくさん種類が出ていてどれ買えばいいの~!?という感じだったのですが、ふなっしーのコスプレをしている超上級者の友人に「3万くらいのコンピュータミシンならどれでもOKじゃない?」みたいなアドバイスをしてもらったので、適当に安いのを選びました。実際使ってみて過不足なかったので、大丈夫だったっぽい。フットペダルはやっぱり便利ですね。あと最近の機種はみんなそうなのかもしれませんが、この機種は糸通しがミシン本体に付いてて、糸を針に通すのがめっちゃ楽でした!ちなみにこのミシンが来たのは木曜日で、学園祭は土曜日からだったので非常にタイトなスケジュールでした。)既成品の型紙を集める大学近く(といっても徒歩20分)にユザワヤが最近できたので、型紙含め材料全部買ってきました。麻の入った朱色の布がたまたまあったのがすごいラッキーでした。余ったので、巫女装束とか作っても良いかな~?制作しっぽ⇨袋を作ってフェイクファーをはりつけるしっぽに関しては、四角い袋を適当にぬって先端を丸くしたあと、フェイクファーの帯をボンドで貼り付けまくりました。それっぽくなったものの、ボンドが固まっているので、触ると硬くなってしまってモフれないのが難点です。布状になってるフェイクファーがあれば、直接それを袋にすることも可能だったかも...。マント⇨ほぼそのままだけど裏地大変マントは今回の衣装にいらなさそうなディテールを適当に削りつつ、マントの形を型紙レベルで書き換えただけです。大変だったのは赤い裏地を付ける所で、そのために赤い布と白い布の両方で同じ型紙をカットして縫い合わせないといけないので、作業が普通に作る時の倍以上になります。装飾とかは全部ボンドで貼り付けました。ボンド便利!!上の写真だと頭にちゃんと葉っぱがついてますが、この葉っぱはリアル葉っぱです(つくりものの葉っぱを買い忘れた)ズボン⇨袴風にするためにサイドをカットする袴は昔、高校(ほとんど行ってない)で剣道の授業があって着たことがあったので、それを思い出して、適当に腰のところをカットして、ゴムじゃなくて帯で腰を縛るようにしています。本当の袴はさらに前のほうは5本のひだが付いていたりするのですが、コンちゃんの衣装はそうではなさそう。結局上着のおはしょりで隠れるのであまり意味はありませんが、しっぽがズボンに固定されていて重いので、それをしっかり持ち上げるのにはちょうど良かったです。足が出てくるところ(ボタンがついてるところ)がすこしくびれているのがとても可愛いので、再現しようと先の方だけ細くなるように、足先につれて太くなっていた所を途中から細くなるように型紙を改造したのですが、あまりそれっぽい感じにはなってくれませんでした...。上着⇨おはしょりを作るために長くした上着は二部ゆかたの上の部分だけ利用することで制作しました。この二部ゆかたの型紙では、「おはしょり」は浴衣が上下に分かれていることで表現されているのですが、今回は普通に「おはしょり」を作りたかったので、上着の型紙を30cmくらい延長して、長めにしました。今回やってみて感じたのは、ミシンを使うからついつい「縫う」のが大事なように思ってしまうけど、それよりもちゃんと綺麗に型紙を布に移して、綺麗にカットするのが大事だったということです。華々しさがない作業なのでついつい手を抜きたくなってしまうのですが、ここで手を抜くと後の作業全てに響きます。小学生の時の私はまち針とかもめんどくさくて殆ど使わなかったような記憶があるのですが、これもちゃんとまち針さして布を固定するのは非常に大事でした。反省点もいくつか有ります。子供用のレインポンチョの型紙を使ったせいで、頭とマントの接合部が首より短くなって着れなくなってしまいました。構造を理解せず作っていたので、襟の縫い代が外に出ています。襟が胸のところまで続いてるなんて知らなかったんだもん!ズボンは長すぎたと思います。足が出てくるところのくびれもとてもかわいいと思うのですが、ここがうまく再現できなかったのは残念です。あと中の上着に袖が全くありませんが、本当はあります。2014今まで、「作る」って周りに宣言しちゃうと作った気になるからあんまり言わないようにしてたのですが、今回はこの一言で「ぜったい作るぞ~」みたいな気分になってきたので、「作ろうかな」という思いつきを具体的な言葉にしてつぶやきまくってみるのは結構有用かもな~と思いました。32ビットの環境で)下位32ビットしか見てない」みたいな事を書きました。これが本当にそうだとすると、この挙動に依存しているunordered_mapに、下位32ビットが同じ値のキーを送り続けるとアルゴリズムの教科書通りの最悪ケースになって悲惨なことになるのでは...!という事が容易に予想されます。いうことで、やってみました。4.8.1ソースコードはこちら。}}シフトする値だけ少し変更することで、他の要素の影響を出来る限り減らしました。67825というわけで500倍くらい遅かったので、本当に線形探索してるっぽいです。本当にありがとうごいました。2790Finishというわけでほぼ同じ時間で終わります。Fujisaki最近諸事情でアルゴリズムイントロダクションを読んでおります。O(1)である謎のテクノロジー、ハッシュテーブルの章を読んだので、せっかくなのでSTLの実装を調べてみました。コンパイル時の条件によって色々実装が分岐するようですが、面倒臭いので適当に目についたコードを読んでいます。組み合わせとして一緒に呼ばれないコードを読んでいる可能性がありますが、ご了承下さい。挿入を足がかりにstd::unorderd_mapの実装は、いろいろたらい回しにされたあとbits/hashtable.hに記述されているらしいことが分かります。挿入っぽい処理を調べてみると、こんな感じになります。}どうやら、衝突の解決には片方向リストを使った方法を使っているようです。アルゴリズムイントロダクションには「削除が必要な時は片方向リストを使った方法がいい」みたいなことが書いてあったような気がするのですが、そのとおりっぽいです。あと番兵は使ってないみたいですね。メモリの節約が優先なのでしょうか。バケットのインデックスは?で、この__bktはどこから来てるのかを調べてみると、_M_bucket_indexという関数の値が実際には使われており...bits/hashtable_policy.hに次のようなコードがあります。}というわけで、h1に値を突っ込んでハッシュを得た後、さらにh2にそのハッシュとバケットの総数を入れてハッシュをバケット数以下にして、実際のバケットのインデックスを求めているみたいです。__nはバケット数の最大値です。int>ならstringの方です。key)で、これは後で調べますが、とりあえず色々な型についてhash値を返してくれるSTLの公開の標準ライブラリです(皆様もお使いになれます!)h2は__detail::_Mod_range_hashingで固定で、ユーザーが指定したりは出来ないようです。};というわけで、散々たらい回しにされた挙句ただのmodでした。万能ハッシュとか使ってるのかと思ったのですがそんなことはなく、h1の実体であるstd::hashに衝突の回避とかの仕事はほぼ丸投げして、バケット数とmodをとっているという感じになります。};となっております。ほかのstd::wstringとかutf16とかも全部同じ実装です。hashの先を見れば分かるのですが、この関数はvoid*を取る関数なので、文字列としての特性はとくに使わず、バイト列として解釈してhashを計算しているようです。_Hash_impl::hashはfunctional_hash.hの関数です。このseedの値の意味がよくわかりません...。}この関数もなんとなくバイト列中のたくさんのビットを反映させていることはなんとなく分かるのですが、謎のマジックワードもあるし、謎です。本と実装はすこし違いますね~とおもったのでした。long)という感じです。longまでstatic_castしちゃうのは本当にそれでいいの?感があります。→実際に実験したら遅かったちなみにこのコードのすぐ下にありますが、double/floatに関してはバイト列として文字列と同じハッシュ関数を使っています。Bash3Dbashプログラミングテクニック今回つかったbashのプログラミングテクニックについて、記録のついでに少しまとめます。シェルスクリプトの基本的な所は書きません。あと、3Dプログラミングに関しては本に書いてある事以上のことが書けそうにないので、(今は)書きません。bashのデータ構造bashでそこそこ本格的にプログラミングする事を考えた上でまっさきに「辛そう...」ってなるのはデータ構造ですよね!今回のソフトでは、ベクトルや行列を綺麗に扱えないと困ってしまうでしょう。bashの「値」は基本的に全てただの文字列ですが、「リスト構造」にだけはネイティブで対応しています。${lst[3]}#空行データにアクセスする時に、必ず${}でくくらないといけないのが少し面倒です。ちなみに添字を付けないとリスト全体...と思いたいところですが、最初の要素が出てきます。$lst1全体を表示するには、${lst[@]}とします。3リストを定義するときに使ったカッコが出てこないので、リストをコピーしたい場合はそのカッコを補わないといけません。3慣れればなんとかなるレベルですが、面倒です。3)”という文字列を返す関数を$(command)で受けて変数に代入してもリストになりません。3)ただの文字列として代入されます。してもしたい場合は、evalを使わないといけません。3通常の用途ならそれで済むのですが、今回は出来る限り高速に処理したいので、こうやってコマンドが沢山必要になるのは避けたいところです。後に言うダイナミックスコープとevalを組み合わせると連想配列みたいなことも出来るのですが、今回のプログラムでは基本的にこのリストでデータ構造を管理しています。インデックスを指定したアクセスをするためにコストが高いevalを行わなくてよいこと、ベクトルと行列が主役なのでリストで十分な事が理由です。1)最初の要素に型の名前を入れて適宜assertすることで、動的型検査みたいなことをしています。変数のスコープ名前空間は基本的にひとつしかありません。関数のネストは一切関係なく上書きされていきます。#オフサイドルールではなく、存在しない変数はグローバルに作られるみたい一種のダイナミックスコープみたいなものだと理解しておけば大体書けます。呼び出し元の変数がうっかり書き換えられてしまう可能性があるのですごく不便ですが、逆に利用することも出来ます。「関数の引数に変数名を渡すと、evalと組み合わせることで指定した変数に結果を格納する関数」が作れます。$val21これを使って「最初の引数は入力、最後の引数は出力」と規約を決めて関数を設計しています。行列の計算はこちらにあります。見た目がアセンブラみたいになりますネ。「こんなスコープ再帰が出来ないのでは?」という気がしますが、案外なんとかなります。いうのも、$(command)はサブシェルとかいうので実行されるらしくて、さらにサブシェルは別の変数空間で動作するようです。このスコープの問題は起こりません。3628800ついでにevalとこのスコープの仕様を使うと、擬似的な連想配列を作れますが、値のコピーが難しいこともあって使いませんでした。val123固定小数点とexprコマンドシェルスクリプトで計算をするには、exprコマンドを使う必要があります。このコマンドは整数の計算にしか対応していません。argument3Dソフトでそれでは困るので、整数だけで小数を計算します。それにはどうするかというと、扱う小数を適当に1000倍とかして、それを小数に見立てます。1000は1、1001は1.001、3140は3.140だと思うことにします。その場合、計算する時にちょっとした手間が必要になります。#足し算・引き算はそのまま足し算で大丈夫です。#割り算するときは、倍率で掛けてから割ります。#掛け算するときは、掛け算してから倍率で割ります。9859倍率を1000じゃなくてもっと高くすれば、もっと小数演算の結果が正確になります。私の環境だけかもしれませんが、exprはどんな大きな数でもちゃんと扱ってくれるようなので、ガンガン倍率を上げていけば誤差がまったく気にならなくなります。これはすごい楽...(楽すぎた)なお、sin/cosは(テイラー展開を自分で展開しない限り)計算できないようだったので、0~360度について事前に求めておいて、これを先ほどのリスト構造にいれて使っています。平方根も使いたかったんですが、どうやっても無理そうなので諦めました。表示:オフスクリーンレンダリングもちろんターミナルにはピクセルがないので、ターミナル上に表示する文字で代用します。(col)を使うとターミナル上の任意の位置に移動できるので、これを使いたいところです。donedoneが、この方法では線を引く様子が目で分かるぐらい時間がかかります。これで最初レンダリングしたみたのですが、レンダリング中に線を引いている様子がバッチリ見えてしまい、あんまり回転アニメーションしてるように見えませんでした。ほんもののゲームでもそういう、更新途中の絵が表示されてしまってちらつく問題があります。ほんもののゲームでは、そういう時は、書いてもすぐには表示されない「裏の画面」を用意しておいて、そちらに時間を掛けてレンダリングして、最後に表示される「表の画面」と入れ替える「オフスクリーンレンダリング」と「ダブルバッファリング」をしているのでした。私も同様にそのテクニックを使いました。具体的には、こんな感じ。'_')今回の「ピクセル」は文字ですから、「裏の画面」はただの文字列になります。画面のサイズを取得しておいて、そのサイズ分何もない文字列を意味する「_」をtrを使って入れておきます。空白でいいじゃないかと思うかもしれませんが、シェルスクリプトでは空白はかなり重要な意味を持っていて面倒なのでこうしています。「ドットを打つ」時は、対応する文字列中のインデックスの文字を*に書き換えます。s/./*/<置き換える文字の位置>)zshだとstr[12]=\*ってやると文字列を更新できるのですが、bashでは使えなかったので、sedを使って書き換えています。この処理が非常に重く、今回のレンダリングエンジン(?)の処理時間の殆どはこのドットを打つ処理に費やされています。上手い方法を知ってたら教えてください!最後に表の画面に更新する方法ですが、単純にechoするだけです。この時、何も入っていない所には_が入っていたので、空白に直します。'ちゃんと幅に合わせて改行されるので、うまく表示できます。プロセスforkを抑える当初綺麗に関数を使って書いた所、行列の掛け算に1秒近く掛かってしまい、さすがにレンダリングは出来ないのかな...と諦めかけたのですが、行列やベクトルの掛け算や足し算を全てインライン展開したところ、座標計算に関しては十二分すぎる程の速度が出せるようになりました。前述した通り、今回遅いのはオフスクリーンにピクセル(文字)を書き込む処理が異常に時間が掛かっているからです。プロセスの起動はものすごい重い処理のようで、可読性をそこまで損なわない範囲でプロセスの呼び出しを一箇所にまとめています。線を引くのにえげつない時間が掛かるのを見た時、初めて触ったBASICの処理系を思い出しました。ところで、なんで3Dライブラリを作るとOpenGL風の名前にしちゃうんだろ。ニコ動に「次はシェーディングだな!」というコメントがついているのですが、テクスチャの表示をやってみたいです!7色くらいしか使えないので、たいへんそう...。影は「暗い色」が基本的に無いのでかなりつらい気がします。FujisakiC++から動画を直接出力する自作ライブラリ「Dolly」の上で、フルスクラッチで3Dのレンダリングを行なっています。ワイヤーフレームの正方形をぐるぐる。https://7io.org/wp/wp-content/uploads/2017/12/dbabc418c8a0e36ab3d373369b832cc3.mp4テクスチャもなんとか実装しましたが、三角形の継ぎ目が出ているのが気になります。