たまりば

  パソコン・インターネット パソコン・インターネット  三鷹市 三鷹市

【お知らせ】記事の変更箇所の書式を変えます
2020年08月30日 18:55

ネット上で公開した情報が間違っていて訂正するときなどに、変更前の内容を消して変更後の内容だけにする行為が嫌いなので、今までここでは
誤った文章。直した文章。(yy/mm/dd訂正)
のような表示にしていた。
具体例を挙げればこのようなものだ。(ベースラインPICの注意点より)
今までの編集表示
しかしこれはこれで、内容を知るためには変更後の文だけを読めばよいので、いちいち変更箇所が示されているのは邪魔だ。
間違った文を見てしまった人にはどこが間違っていたのかを伝えたいが、単に記事の内容を知りたい人にかつて間違っていたことを伝える必要はない。

そんなわけで、これからはCSSで変更前を消し、変更後の内容だけが見えるようにする。
上記の例ではこうなる。
これからの編集表示
変更前の内容はソースを見れば分かるようになっている。
変更箇所のソース
暇を見て過去の記事の書式を変更していく。

ただ、修正直後は修正箇所が見えていたほうが良いかもしれない。
JavaScriptで経過時間で表示を変えるようにしたい。あとインタラクティブに選んだ版間の差分を表示するようなのもやりたい。  

  • PIC16のDhrystone MIPSを測ろうとしてみた
    2020年04月29日 02:23

    PIC、中でも昔ながらのミッドレンジは演算性能が低いことで有名だ。
    ベンチマークでどれほど低い値が出るか気になる。マイコンの性能比較に広く使われているDhrystoneは整数演算だけなので低性能なCPUでも走りそうだ。

    そう思ってDhrystoneのソースコードを見てみると…
    int    Arr_2_Glob [50] [50];

    ミッドレンジPICに5000バイトもメモリは無い。最大でも368バイトだ。

    -完-



    …とここで諦めてしまうのはつまらない。
    よく見てみれば2500個の要素を全て使っているわけではない。アクセスするのはわずか4箇所。これならなんとかなるのではないか。
    なんとかなるというのはつまり、Dhrystoneをそのまま走らせることはできないにせよ、なるべく結果に影響の出ない範囲でコードをいじったり、もし本来のコードが走ったらどの程度の速度が出るかを推測することで、それなりに意味のある値を出せるのではないか。

    というわけでやってみた。
    使うコンパイラはMPLABの標準のCコンパイラ、XC8(v1.44)。無償版なのできっと最適化とかに制限があるだろうが気にしない。

    まずは何はともあれコンパイルが通るようにしなければならない。適当に配列要素の数を減らそう。50×50要素を7×6要素に。
    int Arr_2_Glob [7] [6]; //Arr_2_Glob [50] [50];
    typedef int     Arr_2_Dim [7][6]; //[50] [50];
    これが妥当かどうかは後で見ていく。
    ついでにもう1つこっちも減らさないといけない。50要素を25要素に。
    int Arr_1_Glob [25]; //Arr_1_Glob [50];
    typedef int     Arr_1_Dim [25]; //[50];
    int 50個=100バイトはあるんではないかと思うかもしれないが、コンパイラの仕様上1つのオブジェクトは1バンクに収まっていないとならない。
    また総メモリ量もわりときつく、ちょっと増やすとすぐ割り付けに失敗する。

    配列の読み書きのコードは変えていない。
    範囲外を読み書きすることになるがそこは自分の足を撃てるC言語。何の問題もない。

    他数箇所変更した。
    ・エラーが出るたび適当にそれっぽいヘッダファイルをinclude
    よく分からないまま追加しているがまあ多い分には問題ないだろう。

    ・NOSTRUCTASSIGN
    structassignで「can't generate code for this expression」が出たので、それ用に用意されたコードが使われるよう
    #define NOSTRUCTASSIGN
    したら動くようになった。
    これにともないmemcpyがコードに書かれたものが使われるようになったが、ライブラリ側のものと衝突したのでコメントアウトした。

    ・時間計測のためのTIMEだのなんだのを削除
    結果に影響の無い部分なのでバッサリと。
    このへんをPICで動くように変更するのは手間なので時間計測はシミュレータで行うことにする。
    PICは実行クロック数が確定的なので問題ない。

    ・mallocができない
    のでユーザーズガイドを見ると、
    Dynamic memory allocation, (heap-based allocation using malloc, etc.) is not supported on any 8-bit device.
    とある。
    幸いmallocが使われているのは初期化の部分、ベンチマーク本体のループの外だ。直接宣言してしまおう。
    ポインタが指す先のメモリがどう用意されたものかはプログラムを走らせる上で違いはないだろう。
    おそらく最適化を防ぐために持ってまわった定義をしているのだろうと思われ、そこまでの最適化はきっとされていないだろうと判断する。少なくともポインタ参照はされている。
    /*
      Next_Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
      Ptr_Glob = (Rec_Pointer) malloc (sizeof (Rec_Type));
    */
      Rec_Type temp1;
      Rec_Type temp2;
      Next_Ptr_Glob = &temp1;
      Ptr_Glob = &temp2;

    なお、これで測り終えた後見つけたのだが、別の対処法として、呼び出されると事前に用意したポインタを返す疑似mallocを使っている人がいた。(後述のAVRのコード)
    こちらの方がより安全そうなので真似してみたのだが、後述のポインタ幅違いがまた出てどうしてもうまくいかなかった。
    諦めて直接宣言のままにした。

    ・よく分からないエラーが出た
     *Ptr_Ref_Par = Ptr_Glob->Ptr_Comp;
    の部分で、「error: (1466) registers unavailable for code generation of this expression」というエラーが出た。
    PICでレジスタってなんだろう。RAMとは違うのか。
    なんだかよく分からないが2分割したらエラーが消えたのでこれでよしとする。
    struct record *temp;

        temp = Ptr_Glob->Ptr_Comp;
        *Ptr_Ref_Par = temp;
    …が、後にこれは必要なくなる。後述。

    これで先へ進むようになり、コンパイルが通るか…と思いきや意外なことにプログラムメモリが足りない。結構食うんだなあ。
    RAMがミッドレンジ最大の368バイトあるものとして手持ちからPIC16F88を選んだのだが、こいつのプログラムメモリは4kワード。
    どうせシミュレータで動かすのだし最大の8kワードある石を適当に選ぶか…と思ったが、コードを見ると大量のprintfがあって容量を食っていそうだ。
    状況説明のための出力は不要なので開始と終了を1文字だけ残して消す。
    結果出力部分に未使用の変数が消されることを防ぐためのprintfがあるがこちらも極力切り詰める。
    例えばこれを
      printf ("Int_Glob:            %d\n", Int_Glob);
      printf ("        should be:   %d\n", 5);
      printf ("Bool_Glob:           %d\n", Bool_Glob);
      printf ("        should be:   %d\n", 1);
    こうする感じで。
      printf ("a:%d\n", Int_Glob);
      printf ("b:%d\n", Bool_Glob);
    こうして切り詰めたら余裕で入った。3kワード弱にまでなった。
    なおprintfから呼ばれるputchの実装は、意味のある出力をするものではなく、シミュレータで適宜止めて出力内容を見るための、引数が消されない最低限のコードにした。

    これでビルドが通り、(内容はともかく)動作をするようになった。

    出力を確かめると、配列の部分の値が違うのは想定通りだが、他に構造体のポインタ演算あたりの部分も違っている。
    調べてみるとおかしいのは例の2分割で代入したところで、16bitのポインタに8bitのポインタを代入している。
    この際データが不可解な変化を見せており、その影響でそのあとif文で通らないはずの方を通っている。

    XC8ではポインタが8bitと16bitの2種類あるのだが、使い分けは手動ではできず、コード中の全ての代入を認識して自動で最適なものを使い分けてくれるという。
    これが判定に失敗して間違った代入をしてしまっているように見える。つまりコンパイラのバグではないか。

    ループ外に適当に代入文を書いてみたところ今まで8bitだったところに16bitのポインタが使われるようになり、出力が正しくなった。
    そして例の2分割代入部分も元のコードでコンパイルが通るようになった。

    配列部分も正しく動作しているか調べよう。
    配列のアクセスの部分の数値を書き換えて配列内をアクセスするようにし、正しい結果が出るかを見る。
    出た。
    また、ここで時間計測し、配列アクセス部以外にかかる時間が変化していないことを確認した。
    よって、不正なアドレスをアクセスすることで他の実行結果に影響を与えていることは無いと考えられる。

    これで多分配列部分以外は本来のDhrystoneの想定通り動いているだろう。



    さて配列の量をいじった事によりベンチマーク結果にどれほどの影響が出ているか考えてみよう。

    1次元配列の方の要素数は、(2次元配列を[2][3]にしてメモリを空けた上で)[25][30][35][40][48]を試し、実行時間は一切変化しなかった。
    こちらの影響は無いものとみて良いだろう。

    2次元配列の要素数を[7][6]から変えて変化を見てみる。
    配列の宣言の部分と、typedefの部分。
    宣\def[50][50][50][6][7][50][7][6][2][2]
    [7][6]58005693580056935571
    [4][6]58005693580056935571
    [8][3]58005693580056935571
    [2][3]58005800580058005571

    これを見ると、typedefの方の2次元めを変えると実行時間にやや変化が見られるのに対し、
    配列の宣言の方は[2][3]でのみ値が変わるが他は要素数によらず一定である。

    本来の[50][50]に近づけられないのが気がかりだが、現状の判断として、配列宣言の要素数を大きくしてもベンチマークの実行時間には影響を与えないと考えることにする。
    typedefの方は折角なので本来の[50][50]にしておく。

    なおこの要素数だが、変えるとコンパイルに失敗することがかなり多い。
    要素数の積が48(メモリ量が96バイト)を越えるとメモリ不足でコンパイルできないのは分かるのだが、次表のようにそれ以下でも値に応じて不規則に失敗する。
    [*][2][*][3][*][4][*][5][*][6][*][7][*][8][*][9]
    [2][*]×××××
    [3][*]××××××
    [4][*]×××××
    [5][*]××××
    [6][*]××
    [7][*]
    [8][*]
    [9][*]??
    [10][*]??
    [11][*]??
    [12][*]??
    (○: コンパイル成功 / ×: コンパイルエラー / ?および表範囲外: 未チェック / 空欄: 96バイト超)

    この時のエラーは次のようなもので、調べるとメモリのバンクへの割り付けに失敗しているエラーで、PICにはよくあることのようだ。有料版だと出なくなることもあるらしい。
    D:\P\xc8\v1.44\sources\common\lwdiv.c:30: error: (1357) fixup overflow storing 0x8E in 1 byte at 0x950/0x2 -> 0x4A8 (dist/default/debug\Dhrystone88.X.debug.obj 120/0x26)

    配列の宣言の方はこれでいいとして、実際にアクセスする部分はどうか。この分をどう考えるかは難しい。

    何らかの計算をしてアドレスを求め、配列の範囲外とはいえどこかしらをアクセスしてはいる。
    しかし、ここで生成されているコードはどうあっても2500の全ての要素にアクセスすることはできない。
    そのような不完全なコードで時間を計ってよいものか。

    とはいえ2500要素5000バイト分のアドレスというものはミッドレンジではそもそも存在し得ないのだから、それを計算するコードとしてどのようなものを想定するのかの正解は無い。

    考えた末、以下2点を補正することにした。

    ・乗算
    まず、アドレスの生成部分を見ると、1バイト×1バイト=1バイトの乗算ルーチンが呼ばれている。実行時間は100サイクル程度。
    これはまずい。50×50要素の配列にアクセスするには積を2バイトで持たなければならない。
    試しに2つのchar型の変数を乗算してみると実行時間は値によって変わり、124や178サイクル。
        char temp01 = 8;
        char temp02 = 100;
        int temp03 = temp01*temp02;
    →124
        char temp01 = 38;
        char temp02 = 100;
        int temp03 = temp01*temp02;
    →178
    この分を適当に補正することにする。1回あたり50サイクル、配列アクセスは4回あるので計200サイクル追加することにする。

    ・バンク
    5000バイトのメモリは存在しないが、せめて複数バンクにわたるメモリのアドレスを算出するコードは考慮しておきたい。
    1バンクあたり96バイトという中途半端な値を使うのはさすがに大変すぎる。
    仮に大量のメモリを積むならもっとまともなアクセスができるようにするだろう(というか、実際にPIC18やEnhancedミッドレンジでされている)
    悩んだ末、「80バイトや96バイトの容量があるバンクが十分に大量にあり、バンクごとに切りの良い64バイトだけを使う」「バンク選択にかかる時間は考えない」ことを仮定し、以下のコードを書いた。
    int address = 0x1234
    char addrh = address >> 6;
    char addrl = address & 0x3F;
    これに50サイクル掛かった。2次元配列のアクセスは4箇所あるので200サイクルを追加する。

    以上より、実測値5800サイクルに2次元配列アクセスの分を想定した補正値400サイクルを加えた6200サイクルを、ミッドレンジPICにおけるDhrystone1回分の実行時間に相当する値と考える。

    これをクロック数に直して24800クロック、1MHz・1秒あたりDhrystone相当値は40.3、1757で割りDMIPS/MHzの推定値は、
    0.0229 DMIPSっぽい値/MHz
    と得られた。



    比較しよう。

    16bitや32bitのマイコンの宣伝にはよく公式にDMIPSが書かれている。見ると、比較的性能の低い(インオーダー・シングルコア)のものでも1DMIPS/MHz程度はある。
    CPUDMIPS/MHz情報ソース
    PIC32MM 1.53http://ww1.microchip.com/downloads/en/DeviceDoc/60001324b.pdf
    RL781.39https://www.renesas.com/ja-jp/products/microcontrollers-microprocessors/rl78/rl78-features.html
    ARM Cortex-M00.89 (最適化条件を変えれば1.02や1.27)https://developer.arm.com/products/processors/cortex-m/cortex-m0

    何十倍も違うとあまり比較にならない。
    もっと性能の低い、8bitCPUの値を探そう。
    探してみるとそれなりに見つかる。

    http://oneweekwonder.blogspot.jp/2013/12/z80-dhrystones.html
    Z80。定番だ。
    3.5MHzで0.142DMIPSというので、0.04DMIPS/MHz。

    http://www.homebrewcpu.com/new_stuff.htm
    65(C)02とZ80。6502はファミコンで有名だ。
    6502 - 1.02MHz: 37Dhrystone
    Z80 - 2.5MHz: 91Dhrystone
    というので、
    37/1757/1.02 = 0.02DMIPS/MHz
    91/1757/2.5 = 0.02DMIPS/MHz
    Z80の値が上の半分だ。まあ環境やコンパイラでこの程度の誤差は出るものだろう。

    http://ww2.tiki.ne.jp/~maro/AVR/project/bench1/index.html
    AVR。PICとよく比較されるマイコンだ。
    at90s8515・8MHzで、最速の結果で1.938MIPSとある。0.24DMIPS/MHzだ。
    ただしこの人、律儀に2500バイト(?)のために読み書きに3クロックかかる外部RAMを接続している。
    (*2500バイトと書いてあるのはたぶん間違いだと思う。C言語的にIntは16bit以上のはず。)
    自分のやったように必要な分のメモリだけにするなら外部RAMは要らずもっと速くなる。
    4クロックから3クロックに変えるだけで1.659MIPSから1.933MIPSに上がったというのだから、同じ割合で1クロックになれば2.920MIPS・0.365MIPS/MHzに上がる計算になる。

    http://www.ecrostech.com/Other/Resources/Dhrystone.htm
    AVRもうひとつ。
    こちらは内蔵メモリに収まるよう配列要素を半分にするなどしているようだ。先述の疑似mallocはここの記述だ。
    ATmega64で、0.328DMIPS/MHz。
    上の0.24DMIPS/MHzより速く、推定0.365MIPS/MHzよりは遅い。
    調べてみるとat90には乗算命令がなくATmegaにはあるようなので、at90より速いはずだ。してみると上の推定0.365MIPS/MHzは少々怪しいか。

    これらの値と今回のPICの推定値を比較してみる。

    Z80: 0.02~0.04DMIPS/MHz
    Z80はゲームボーイのCPUしか扱ったことがないが、1命令のクロック数は4の倍数で、概ねメモリアクセスの回数×4クロックに一致する。これはPICと似た性能である。
    本来のZ80は多少クロック数が異なり、またレジスタが多かったりループ命令があったりするが、大勢に影響は無いだろう。
    クロックあたりに行う動作がPICと同程度で、命令がZ80の方が豊富であることから、DMIPS値が1倍~2倍というのはだいたい感覚と合っている。

    6502: 0.02DMIPS/MHz
    6502はメモリアクセスあたり1クロック+α(内部処理分)といった趣で、Z80よりクロックあたりの性能は高い印象がある。
    Z80と同等か低い値というのはちょっと感覚と合わない。まあZ80も2つで2倍の差があるくらいだし、この程度は誤差なのかもしれない。

    AVR: 0.24~0.365DMIPS/MHz
    Z80や6502と1桁違う。PICの10.5~16倍。感覚的にちょっと多すぎな気もするが、よく考えてみよう。
    まずAVRは大半の命令が1命令1クロックで動く。PICは基本的に1命令4クロックなので、命令数では2.6~4倍となる。
    ミッドレンジPICとAVRのアーキテクチャを比較すると、
    ・PICは汎用レジスタが1つのみであり何をやるにもメモリに値を置く必要がある
    ・PICはプログラムメモリのページ・RAMのバンク切り替えで時間を食う
    ・PICには乗算命令が無い
    ・PICは間接アドレッシング用のレジスタが1本しか無いことやメモリのバンク分けのため、C言語に向いていない
    ・一方AVRは間接アドレッシング用のレジスタが16bit×3本もあり、大変C言語向きである
    といったあたりの要因からして、2.6倍くらいは妥当だし4倍もまあありうるのかなと思えてくる。

    Conclusion


    独自の仮定のもとにそれなりにDMIPSに近いと信じる値を算出し、いくつかの8bit CPUの値と比較して感覚とそれなりに合う結果となったので満足である。



    結論に問題があるとすれば、まず一番怪しいのが2次元配列アクセスの補正が正当かどうか。
    そしてmalloc除去を始め各所で加えた変更が正当かどうか。
    あとはPICでまともにC言語を使ったのは初めてなのでなにか手順にとんでもない間違いがあるかもしれない。
    有料版で最適化をかけて別次元の速さになったりしないかも少し心配だ。(「(PRO版なら)最大で60%小さく400%速いコードができていたんだぜ」ってコンパイルするたびに言われるのだ)
    コードはgithubに上げたので、何か疑問があれば色々試してみるとよいだろう。
    https://github.com/Ikadzuchi/Dhrystone88.X

    なお、本稿をほぼ書き終えた段階で公開用にコードを整形したところ、ループ外の不要部を削っただけなのだが、ループの実行時間は5800サイクルから5824サイクルに増えた。面倒なので文中の数値は直さない。
    コードの位置が変わることによりページをまたいだりして実行時間がこの程度変化することは十分考えられる。
    他にもループ外にテスト用の乗算コードを書いたら7016→6996に短縮したこともあった。(不正な場所を実行していた時なので実行時間は大きく違う)  

  • マリオ3でのキノコの突然変異の考察
    2020年03月10日 09:37

    スーパーマリオシリーズの代表的なアイテムにスーパーキノコ(や1UPキノコ)がある。

    今は左のようなデザインで定着しているが初代スーパーマリオブラザーズでは右のデザインだった。
    現在と初代のスーパーキノコ
    初めて初代のキノコを見たときは何だこりゃと思った。
    変わりすぎだろ何があった。
    …と、ずっと思っていた。

    先日マリオ2を初めてプレイして知ったのだが、まず顔が付いたのはマリオ2だったんだな。
    マリオ2のキノコ
    マリオコレクション(今風のデザイン)やマリオDXのおまけ(初代のデザイン)でしかマリオ2は見たことがなかったので知らなかった。
    スーパーマリオシリーズの様々な物に顔が付いていることについてはこのように言われている。
    雲や草には顔があるように見えるが、これは立体感を出すために書き込まれた線が、顔に見えていただけだった。
    しかし、これが「怖い」と言われたことから、次回作の『2』では、背景の雲や草にはっきりとした顔が書かれた。
    https://www26.atwiki.jp/gcmatome/pages/3410.html

    ただちょっと不思議なのは、初代の時からパッケージイラストにはマリオの手に持つスーパーキノコと思しきキノコにしっかりと顔が書かれていることだ。地面の花をも差し置いて。
    初代スーパーマリオのパッケージのキノコの顔
    スターやジュゲムの雲には初代から顔があるし、これは物には顔を描くというスタイルは初代からあったのを2で徹底したということか。

    にしても。
    スーパーマリオ1・2・3・ワールドのキノコ

    顔以外も2から3で変わりすぎじゃないか。
    …と思っていた。
    実はそうでもなかったのである。

    まず、実は上の画像には1つ嘘がある。
    マリオ1・2の斑点は橙~茶色・マリオ3の斑点は赤だと認識していたのだが(そして実際上の画像もそうなっているが)、斑点の色はどれも同じである。ついでにマリオの服や赤ノコノコの甲羅、スーパー木の葉も同じ色である。
    マリオ2・3のキノコの色
    今回キノコの色について調べていて初めて気づいた。
    スーパーファミコン以降の真っ赤になった色のイメージや、空の色やキノコの地の色の違いで誤認していたものと思われる。
    そして同じ条件で撮られた別のソフトの画像はあまり存在しないことから、気づけなかったのだろう。
    実際、先の画像はMarioWikiから取ってきたのだが、見ての通り色は一定していなかった。ファミコンの色はかくも曖昧なものである。

    次に地の色が白になった事について。これはアートスタイルの変化に対するハードの制限である。
    マリオ3ではキャラクターの絵に(大半は黒の)縁取りが付くようになった。
    これにより、縁に1色とられることとなり、残り2色でキノコを描くことになった。
    地の黄色を残すか柄(え)の白を残すかの選択になるが、1UPキノコとの統一や他のキャラクターとの兼ね合いを考えると、白にするのが自然だろう。
    ちなみにマリオ3の通常の(地上面での)スプライトのパレットは
    ・プレイヤーキャラ用(色はパワーアップなどで変わる。標準マリオはベージュ/赤茶/黒)
    ・白/赤茶/黒 (スーパーキノコ、スーパー木の葉、赤ノコノコの甲羅、パタクリボーなど)
    ・白/緑/黒 (1UPキノコ、緑ノコノコの甲羅、蔓/葉など)
    ・ベージュ/黄土/黒 (クリボー、ノコノコの顔など)
    の4種である。

    とはいえ縁取りを付けただけで突然3のデザインになるわけでもなかろう。…と思っていた。
    試しに2のキノコに縁取りだけ付けてみよう。
    ところで、縁取りの付け方には4連結と8連結がある。
    4連結と8連結
    不思議なことに、マリオ3ではほとんどのキャラクターのほとんどの部分は8連結で縁取りされているのだが、キノコは4連結である。
    キノコだけ最初に描かれたとか、デザイナーが別とかだろうか。何にせよ4連結で描くことにする。
    まずは外形をそのままにして描くのが自然だろう。
    マリオ2から3のキノコへ_1
    すると、妙に痩せた印象になってしまった。縁取りが無ければ物と背景との境が外形だと認識するのに対し、縁取りをすると縁の1pxの中央を外形と認識するからだな。
    1pxだけ広げよう。
    マリオ2から3のキノコへ_2

    さて、ファミコンにはスプライトの左右反転機能があるので、左右対称な物体は必要なタイル数が半分で済む。
    タイル数は節約したいので、左右対称のデザインにしよう。
    とりあえずそのまま左右対称にしてみると、
    マリオ2から3のキノコへ_3
    左はあからさまに2つの大きな斑点が並んでしまって不自然だ。右の方が望みがありそうだな。
    くっついてしまった上の斑点を切って、中央のスペースが不自然に空いているので、消えてしまった左のものくらいの大きさの斑点を1個置く。
    マリオ2から3のキノコへ_4

    ほぼマリオ3のキノコになってしまった!

    比較してみよう。
    マリオ2から3のキノコへ_比較
    特に上部の丸みが完全に一致しているのと、斑点の形の大部分が一致しているあたりがポイントが高い。
    縁取りを付けてからここまで制限に従って不自然な部分を直していっただけだ。
    ここまできたら後は笠を1px下に下ろし、柄を1px太らせ、斑点を自然に調整するだけ。その程度の変化には特に不思議はない。

    マリオはハードの制限から生まれたデザインというのは有名だが、キノコもそうだったのだなあ。
    次作のワールドで色数やスプライト数の制限が無くなったにも関わらず3のデザインを(地と斑点の色は反転したが)踏襲、更にこの時左右非対称になったのがその後左右対称に戻り、一番制限のきつかった3のデザインに固定されたというのがなかなか面白いところだ。  

  • 【訂正・加筆】
    2020年03月09日 00:00

    2020/03/09 まぶしいものを見た後のような残像が常に見える症状に1ヶ月後の状況を追記。
    2019/09/03 ポケットプリンタ制御でコードの一部がHTMLタグ扱いで消えていたのを修正。
    2019/07/17 ファミコンで9×9ドット文字表示(ほか)に実機動作していただいたツイートを追加、およびコードのミスを修正。
    2019/07/17 ファミコンで全画面に任意の画像(ただしモノクロ)を表示のコードのミスを修正。
    2017/04/23, 2017/05/22, 2017/07/24, 2017/11/21, 2019/06/08, 2019/06/16, 2019/06/22, 2019/07/15, 2020/02/27ゼルダの伝説ブレスオブザワイルドプレイ日記(ネタバレ有) 更新。
    2016/12/06 漢点字一覧表で、別の元データとの間で不一致があったので追記。
    2016/09/10 ネット契約を1Mbpsにしてみたの読み込み時間の記述が間違っていたので訂正。
    2016/09/04 ファミコンの縦解像度224px説の考察 左8pxを隠す機能の用途が思っていたのと違ったので追記。
    2016/06/06 Windows7でのペイントの劣化具合に新しく気づいたバグ情報を追加。
    2015/08/29 ベースラインPICの注意点で、型番の間違いを訂正、OPTION2の書き込み方法の間違いを訂正。
    2015/08/28 Windows7でのペイントの劣化具合に新しく気づいたバグ情報を追加。
    2013/09/26 5×5 ひらがなフォント5×5フォント改 / JavaScriptフォント表示機から5×5ドットフォント完成版に、思えばリンクを貼っていなかったのでリンク。
    2013/05/05 最弱のPICマイコンで電子オルガン_前編の単純ミスを1ヶ所修正。
    2013/04/27 文字コード表示機が特定の環境で動かない問題を修正、RTL文字での表示崩れを修正。
    2013/03/03 5×5ドットフォント完成版が紹介されていたので少々加筆しました。
    2012/11/18 ハロウィー?ンの正規表現に訂正・加筆があります。

    【このエントリについて】
    (2012/11/18)
    今まで、記事の内容にミスを見つけた場合はその記事だけ修正していたのですが、最新の記事はともかく古い記事にミスを見つけた場合は直しても気づかれないだろうと思って直すのが億劫になっていました。
    これではいけないと、どうするべきか考えた結果、訂正を知らせるエントリを1つ作ることにしました。
    記事を訂正した際にはこのエントリを更新して最上位に持ってくるように運用しようと思います。
    (2013/03/03)
    訂正だけに限らなかったので、エントリ名を【訂正】から【訂正・加筆】に変更しました。  
    タグ :お知らせ

  • まぶしいものを見た後のような残像が常に見える症状
    2020年02月02日 01:07

    そんな症状が出た。

    太陽や電灯などの明るい光を見たり、同じ絵を数十秒注視し続けた後、目を閉じたり白い背景に視線を移したりすると、その形がしばらく残って見える現象があると思う。
    それとちょうど同じような見え方が、目を閉じたり開けたりする度に起こる。
    しばらく目を開いて白い背景を見た後目を閉じると、丸い残像が見える。視界にそのような形が無かったにもかかわらず。
    しばらく目を閉じていてから目を開いても同様に見える。
    残像は数秒でほぼ見えなくなる。そのへんもまぶしいものを見た後の残像に似ている。
    白い背景を見ながら高速でまばたきを繰り返すとよく見える。
    普通に生活していれば気にしなければ忘れる程度の見え方でしかない。

    位置は視野の中心のそば、7時の方向。
    片目づつつぶって確かめるとどうやら症状は左目に起こっている。
    形は真円。大きさは、手を伸ばした握り拳が視野角約10°と知られているのでそれと比較すると6~7°程度か、と思っていたらまた測ると8°程度に見えたりもする。測定誤差か実際に変化しているのかは分からない。少なくとも大きな変化は無いようだ。

    ネットで情報を探すと、なかなか検索語句が難しかったが、「目 残像 残る」あたりで2件の情報を見つけた。
    右目の残像が消えない!
    怖い!右目の視界の端に、常に残像っぽい丸いのが見える!
    どちらもたぶん同じ病状で、目の奥で何か漏れ出て溜まることで目の奥が膨らみ網膜が変形したことでこの症状は出るようだ。(結論から言うと自分の診断もたぶん同じもの)
    失明などの危険なものではなく放っておいても治る程度のもののようなので、だいぶ安心。自分の症状も急に悪化したりはしていないので、休日を待って眼科に行くことに。

    眼科。
    まず機械を覗いて平野の気球を見て(たぶん屈折率か何か測られた)、別の機械で風を吹き付けられ(眼圧測定)、視力を測り(裸眼0.06、矯正1.2とか言っていた。裸眼視力が低くてショック)、
    瞳孔を開く目薬をさされて効くまでしばらく待つ。瞳孔が開いてまぶしくなる他にピント調節にも影響するようで手元の携帯にメガネを掛けた状態でピントが合わなくなった。幸い近視なのでメガネを外せばよく見える。
    事前に乗り物の運転をしないか聞かれたがなるほど確かにこの状態で運転は危険だ。自転車と迷ったが徒歩で行っていて良かった。

    瞳孔が開いたところでまた別の機械を覗いて、フラッシュを焚かれまぶしい。
    ここで診察室に入り医師の診察。机の横に設置された機械を覗いて、斜めから光を照らしながら観察、また別の小さな手持ちの道具でも光を照らしながらいろいろな方向を見るよう指示されながら観察される。

    さて診断は…飛蚊症。
    網膜とかには異常はないということで、飛蚊症の一種だろうという。いやそれは違うんじゃないかなと、普通の飛蚊症とは大きさ・形が違うとか目をつぶっても見えるとかを伝えるも、「飛蚊症にも色々あるので」といった反応。
    先入観を持って見るのもまずいだろうとここまで言わないでいたが、上記のサイトで見た「目の奥が膨らんでいる」状態を疑っている旨伝え、そういうものは無いですかねと言うと、眼底の断層画像を見せてくれたので見てみると、なんだか膨らみのようなもの見える。これは何かと尋ねたところ、やはりそれであった。事前に調べていって本当に良かった。
    思えば病名は聞いていないが、状況としては血管から漏れ出た液体が溜まっているもので悪性ではなく、やはりストレスで起こるものという説明を受けた。上記サイトのものと同じようなものだろう。
    出た薬は「カリジノゲナーゼ」と「柴苓湯」。血液の流れをどうこうするようなもののようだ。

    なお画像は貰えるか聞いたところ、写メとかならいいですよと言われたので撮ってきた。
    眼底の膨らみ
    このはっきりした膨らみに気づかないものなのだろうか…。
    少々医師の診断能力に疑問を抱かないでもないが、考えてみるとこの症状は
    ・気にしなければ気にならない程度のもの
    ・放っておいても治る
    ・致命的なことにはならない
    というもので、この症状で医者に行く人は少ないのではないか。
    症例が滅多になければ診断は難しくなる。また、誤診されても放っておいて治る病気なら気づかれない。
    そういうことでなかなか気づかないものなのかなあと思ったりもする。
    とりあえず、きちんと時間を掛けて説明して患者の話も聞いてくれた点で良い先生だと思う。

    それにしても非侵襲で正面からの光だけでこんな断面が撮れるものなんだなあ。かがくのちからってすげー。
    調べてみるとどうやらOCT(Optical Coherence Tomography; 光干渉断層計)というもののようだ。
    反射光を干渉させて反射した点の距離が分かるという原理。そんな方法があるのか。

    (2020/03/09追記)
    1ヶ月後にまた来てくれという事だったので薬を飲みつつ待った。
    その間、見える像の形が主に上側で微妙に変わったような気がしなくもなかったがほとんど変化の無いまま3週間くらい過ぎて、少々心配だったのだが1ヶ月を目前にして急に薄くなってきた。

    1ヶ月(正確には4週間)後、再び眼科に行きほぼ同じ内容の診察(平野の気球と裸眼視力が無かった)を受け、例の画像を見る。
    眼底の膨らみ2
    おお、改善している。
    またもう1種類別の画像も見せてくれた。
    網膜の厚み
    網膜の厚みを図示したもののようだ。
    左の真ん中が分かりやすいが水平に線が見えていて、これを「ニボー」といい、立っている姿勢で見ているので水が下側に溜まっているのが見えているという。
    ということは時によって微妙に形が変わって見えたのも水のたまり具合が変わったからだったのか。逆さ吊りや横になったりすればそれに応じて形が変わって見えたのかもしれない。もう試せるほどの症状が出ていないので残念だ。
    誰か同じ症状が出た人は是非試してみてほしい。そして結果を聞かせてほしい。