憂鬱な午後のひととき


2月28日_

AVXとSSEの切り替えミスを検出する方法

27日のような状況はコンパイラがバグってなくても起こり得る. たとえばアセンブリ言語で書かれたSSE最適化されたコードと, それとは別にAVX最適化されたコードがあったとする. AVX最適化を開発してるときはそれに専念してるとvzeroupperを入れ忘れても遅くはならないので気がつきにくい. そしてSSEモジュールと一緒に使うとAVX側のモジュールを呼び出した後はSSEモジュールが遅くなる.

大きなプロジェクトでまれにしか呼ばれないvzeroupper忘れのモジュールがあるとたまに遅くなる現象になってしまうだろう. SSEモジュールを見ても何が悪いのか気がつきにくい. これは困りそうだ. というわけでIntelはそれを検出する方法を提供している.

一つはIntel Software Development Emulatorを使う方法. たとえばvzeroupper.cpp

sde -oast a.txt -- ./vzeroupper

とするとa.txtというレポートが生成される.

# ===================================================
         Penalty      Dynamic      Dynamic                                                          State
              in   AVX to SSE   SSE to AVX   Static             Dynamic         Previous           Change
           Block   Transition   Transition   Icount Executions   Icount            Block            Block
================ ============ ============ ======== ========== ======== ================ ================
   0x7fef3d9d1dc            1            0        6         44      264    0x7fef3d9d144         0x29d005
...
# SUMMARY
# AVX_to_SSE_transition_instances:        10000001
# SSE_to_AVX_transition_instances:        10000001
# Dynamic_insts:                          50902588
# AVX_to_SSE_instances/instruction:       0.1965
# SSE_to_AVX_instances/instruction:       0.1965
# AVX_to_SSE_instances/100instructions:   19.6454
# SSE_to_AVX_instances/100instructions:   19.6454

AVXとSSEの変換ペナルティが発生していることがわかる.

もう一つはLinuxのperfコマンドを使う方法. 手元のバージョン(3.8.0-19)ではperf listには残念ながら直接指定できるオプションはなかった. そこでマニュアル指定をする. 『Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3 System Programming Guide』の19章「Performance-Monitoring Events」を見る.

Table 19-2. Non-Architectural Performance Events In the Processor Core of 4th Generation Intel Core Processorをつらつらと見ていると,

event抜粋
Event Umask Event Mask Mnemonic 説明
C1H 08H OTHER_ASSISTS.AVX_TO_SSE Number of transitions from AVX-256 to legacy SSE when penalty applicable.
C1H 10H OTHER_ASSISTS.SSE_TO_AVX Number of transitions from SSE to AVX-256 when penalty applicable.

というのが見つかる. perfには-e r<Umask><Event>を指定すればよい. つまり

% perf stat -e r08c1 -e r10c1 ./vzeroupper 0
mode=0 153.828clk

 Performance counter stats for './vzeroupper 0':

          10000001 r08c1
           9999978 r10c1

       0.455858464 seconds time elapsed

% perf stat -e r08c1 -e r10c1 ./vzeroupper 1
mode=1   9.841clk

 Performance counter stats for './vzeroupper 1':

                 1 r08c1
                 0 r10c1

       0.029859031 seconds time elapsed
とする. これでそれぞれのイベント回数が表示される. こちらはエミュレータでないので測定しやすいだろう. VTuneにもその機能はあるようだ.
2月27日_

とってもややこしい発現条件だったVisual Studio 2013の/arch:AVXでコンパイルすると数学関数が遅くなることがある現象

19日の「VC2013で明らかに遅くなってる」を再現できる小さいコードができた. 今回はここまで削るのにかなり苦労した(slow_sin.cpp).

#include <stdio.h>
#include <math.h>
#include <time.h>

struct A {
    float a[8];
    A()
    {
        const float x = log(2.0f);
        for (int i = 0; i < 8; i++) {
            a[i] = x;
        }
    }
};

const A a;

int main()
{
    double (*f)(double) = sin;
    double begin = clock();
    double x = 1.0;
    double y = 0;
    x = 1;
    y = 0;
    for (int i = 0; i < 100000000; i++) {
        y += f(x);
        x += 1e-6;
    }
    double end = clock();
    printf("x=%f, %.2fsec\n", x, (end - begin) /double(CLOCKS_PER_SEC));
}

このコードを

cl /EHsc /Ox /Ob2 /fp:fast /Zi slow_sin.cpp
cl /EHsc /Ox /Ob2 /fp:fast /arch:AVX /Feslow_sin_avx.exe /Zi slow_sin.cpp

の/arch:AVXありとなしのオプションでビルドして実行時間をみる(x32/x64どちらでも発現).

slow_sin on Sandy Bridge
VS2012 VS2013
slow_sin 1.33 1.34
slow_sin_avx 1.33 6.27

ポイントがいくつかある. これらの条件を全て満たさないと発現しない.

一つ目はベンチマークをとるために関数を取り替えられるようにしていたので発現. 最初はexp(やsin)が単に遅くなったのかと思ったが, 単純呼び出しの時間を計ると遅くなってなかった.

二つ目, コードを削っていく際, 無関係と思われる使ってない関数をばさばさ消していくと急に速くなったので不思議だった.

ループさせて速度をとっている部分を見ても, 速いときと遅いときで同一コードにしか見えない. わけがわからない. どうやらmainより前で何か悪さしているのが影響している?

三つ目, これがやっかいだった. 二つ目の時点でmainより先に数学関数を呼べば遅くなるのかと思ったのだが, そうではなかった. 1回呼び出すだけでは発生しない. ループ内の関数呼び出しがiに依存すると遅くはならない. ループ回数が2回や4回では発生しない. 8回にすると発生する.

ここらでようやく原因に思い当たる. つまり, 丁度8回呼ぶとAVXのシャッフル系命令を使った最適化がされるのだろう. 実際出力コードをみるとclass Aのコンストラクタは

    vmovss      xmm0, DWORD PTR __real@40000000
    call        logf
    vshufps     xmm0, xmm0, xmm0, 0
    vinsertf128 ymm0, ymm0, xmm0, 1
    vmovups     [a], ymm0

というようにvinsertf128を使うコードが生成されていた. というわけでvinsertf128の呼び出しで速度が変わるか見てみよう. これが原因ならmainの前で初期化するのは関係なさそうだ.

const struct Code : Xbyak::CodeGenerator {
    Code()
    {
        vinsertf128(ym0, ym0, xm0, 1);
        ret();
    }
} code;

void (*call_vinsertf128)() = code.getCode<void (*)()>();

によって生成したcall_vinsertf128を同じベンチマークをとる前後に挿入して計測する(コードはslow_sin2.cpp). コード生成しているのはvinsertf128をコンパイラの知っているintrinsicで呼び出すと他に影響を与えてしまう可能性を排除するため. するとvinsertf128を実行した後ではsin関数が遅くなっていることを確認できた(36clk→181clk).

この原因はvinsertf128を呼ぶとymmレジスタが変更されてxmmレジスタの上位の128bitが0ではないと認識され, レジスタ間に余計な依存関係が発生しペナルティを受けるためだ. これを避けるにはvzeroupperを呼べばよい. vzeroupperについては11年3月2日の日記を参照.

実際, VCのintrinsicでvinsertf128を呼ぶと適当なところにvzeroupperが挿入されていた(したがって遅くならない).

レガシーSSEで書かれた数学関数はymmレジスタを使った場合でもvzeroupperを使うと大丈夫なのだが, 今回ループの最適化で自動的にvinsertf128を使ったところでうっかりvzeroupperを生成するのを忘れたというところだろうか.

関数ポインタではないときに問題にならないのはうまくsinを取り替えているからかなあ. 後で調べる.

[追記]
関数ポインタを使わなくても現象を再現できるようになった(slow_sin3.cpp). これですっきりした.

2月19日_

VC2013のexp

fmathが32-bit環境でbuildできないというメールが来ていたので対処. うっかり_mm_set1_epi64xを使ってしまってためでそこを修正. で, 久しぶりにベンチマークをとったらVC2012とVC2013で速度が違う. コンパイルオプションは/EHsc /Ob2 /GS- /Qpar /fp:fast -DFMATH_USE_XBYAK /DNOMINMAX .

bench.cppの結果[clk]@core i3
VC2012 .VC2013
std::exp 54.7 56.5
fmath::exp 26.2 28.5

こちらは誤算の範囲でほぼ同じ. ところが同じようなベンチマークをしてるfastexp.cppでは

fastexp.cppの結果[clk]@core i3
VC2012 .VC2013
std::exp 62.2 349.8
fmath::exp 33.8 33.0

VC2013で明らかに遅くなってる(もちろん同じコンパイルオプション). なぜだろう. 後で調べる.

2月16日_

数値を2倍する細かい話

192bitの平方算Fp::squareの実装の続き. a, b, cを64bit, N=264として

x=(a N2 + b N+ c)2 = a2N4 + b2N2 + c2 + 2(abN2 + acN + bc)N

このabN2 + acN + bcを求めた後で2倍するとトータルのaddの回数が減る. これがどれぐらい効くのかを調べたかった.

この256bitの整数[x3:x2:x1:x0]を2倍するいくつかの方法を試す(x0たちは全て64bitレジスタ). 2倍したら257bitになるかもしれないので出力は[x4:x3:x2:x1:x0]とする. x4は0か1のどちらかしか値を取らない. まずはセオリー通りのadd + adcだ.

add x0, x0
adc x1, x1
adc x2, x2
adc x3, x3
mov x4, 0
adc x4, 0 ; carry ? 1 : 0

他にレジスタを使っていてx4を予めゼロクリアできない場合, 計算途中でゼロクリアする必要がある. 通常のxor x4, x4はフラグを変更するのでmov命令を使うことになる. これを使った場合, HaswellでFp::squareが54.2clkぐらいだった.

コードサイズはmov rax, 0はYASMなら7byte. ただ64bitモードでは32bitレジスタ演算をすると上位32bitはクリアされる. だからmov eax, 0にしても意味は同じで5byteになってお得(Xbyakはこの出力最適化は行う).

ちなみにLLVMでi256整数を読み込みzextでi320整数に0拡張して加算するコードをllcにかけると

add x0, x0
adc x1, x1
adc x2, x2
adc x3, x3
sbb x4, x4 ; carry ? -1 : 0
and x4, 1  ; carry ? 1 : 0

というコードになった. ややトリッキーだがこの場合は54.4clkぐらいで気持ち遅い. and命令の分か?

carryの部分の扱いとして

setc al
movzx eax, al

という方法もある. こちらも54.2clkぐらいだった. setcの方がadcよりは少しは速くなるかと思ったのだが.

さて, 今回はレジスタが一つ別のところから確保できたので予めraxをゼロクリアできた.

xor eax, eax
add x0, x0
adc x1, x1
adc x2, x2
adc x3, x3
setc al

これだと0.5clk速くなって53.6clkになった(この時点でLLVMよりは速い). うーむ結構依存関係が足を引っ張ってるのか. てかmovzxは実行ユニットを消費しないんじゃないのか. 消費はしないけどout of orderの制約が増えるということか?

最後にshldを使ってみた.

xor x4, x4
shld x4, x3
shld x3, x2
shld x2, x1
shld x1, x0
add x0, x0

これだと53.2clkになった. 以前も思ったがadcよりshldが速いってどうなってるんだろうと思ってしまう.

わずか1clkの違いとは言え, このルーチンの2%弱なので大きいといえば大きい. 計算結果が256bitに入ると分かっているときだとadd + adcよりshldを使った方が2clkも速くなった. mul1回分の差に近い.

でも, 実は最初に作った素直なFp::mulが48clkで, こうやって平方に最適化したものよりずっと速かった(mulの命令数はsquareの方が3個も少ないのに!). というわけでここ1週間ほど書いたコードは全て破棄. 無念.

2月15日_

pptxをpdfに変換するVBスクリプト

ちょっとしたテキストを書こうと思って, 今回はmarkdownをpandocで処理してみるかと試す. 数式表示が画像になってしまって本文との釣り合いがとりにくいのが気に食わない. もちろん, いろいろ細かい設定をするとできるのだろうけどまだ気が乗らない. TeX出力を試すとエラーになって(多分自分が悪いだけだろうが), それならTeXでいいかなあとか. Wordもちゃんと勉強すれば(cf. Wordはなぜ思い通りにならないのか?), それが一番楽そうに見えるのだけど今回もパス. プレゼンならPowerPoint + 内蔵数式エディタが一番楽だと思うんだけど. というわけで結局TeX + 図はpptxをpdfにして取り込むといういつものパターンになった(学習能力低い).

ただTeXも種類がいっぱいあって何がどう違うのか分からない. とりあえずpLateXのままでいく. pdfを使うとdvioutでは図が出ないので結局pdfにしないといけない. ptex2pdf -lでpLaTeXからダイレクトに変換できるらしい. いろいろ変わってるなあ. あとpdfビューワはSumatraPDFがよいそうな. これはAcrobat ReaderやFoxitと違ってロックを書けないので表示したまま作業できる. 複数開くのを防ぐために[設定]→[詳細設定]→[ReuseInstance = true]にした. 実際インストールすると軽い. 軽くて気に入ってたFoxitはバージョンが上がるごとにごちゃごちゃしてくるし, 段々重くなるし, そもそも日本語のレイアウトが妙な部分が気持ち悪かったがこちらはそういうこともない. 今度からこちらを使おう.

で, いつからか知らないけどTeXからpdfを直接読めるようになっていて, それはありがたいがpptxはまだ直接読んでくれない(そりゃそうか). pptxを編集していちいちpdfで保存し直すのが面倒. というわけで, ごたごたと前置きを書いたが, 変換スクリプトを作ることにした.

VBスクリプトなんて6年以上前にちょろっと試しただけでさっぱり覚えていない. いろいろwebの入門記事を参考にしながらようやく書いた(pptx2pdf.vbs). もちろん要PowerPoint. 調べていて結構同様のものが既に多数あることに気づく. まあいいや(試してもエラーになるのも多かったし).

Makefileに依存関係を書いてようやく作業効率がましになった.

2月13日_

スパムメール

ここしばらくIf you can't click the above link, ... というスパムメールがスパムフィルタをすり抜けて大量に来てる. Subjectも宛て先もX-Mailer(phpmailerが多いようだが)もばらばら.

迷惑メールとして登録してるのに全然フィルタの効果が上がらないのが腹が立つ.

2月12日_

最適化続きの続き

今回のケースだと先にx * yを求めてからreductionするコードとの比較をしたいが, コード書くのがなにぶん面倒なんだよなあ(誰か代わりに試して←とか言うようになると若くない).

とかぐだぐだ言ってないでコード書け, というわけで仕事が終わったあとがりがりと書いてみた(gen_montMul3).

mul/addの数@Haswell
pがfull bit clk pがfull bitでない clk
乗算しながらreduction:A 21m + 47a 48 21m + 52a 46
乗算完了後reduction:B 21m + 48a 53 21m + 53a 49

表の中のaはadd(or adc or setc)の回数, mはmul(or mulx)の回数. pが192bit素数のときfull bit, 191bit素数のときfull bitでないと書いている. full bitでないといはいくつかの演算を省略できるのでaddの回数が減っている.

乗算しながらreductionというのは192bit x 64bitしながらMontgomery reductionをする. 乗算完了後reductionは192bit x 192bitを求めてからMontgomery reductionをする.

192bit x 192bitを普通の筆算ですると192bit x 64bitに3a. それが3回で9a. あと256bitの加算が2回で8a. 合計17a. 次のように順序を入れ換えると

               x2 x1 x0
   *           y2 y1 y0
   --------------------
         x2 y1|x0 y1
            x2 y0

         x2 y1|x1 y0
            x0 y2

      x2 y2|x1 y1|x0 y0

[x2y1:x0y1] + (x2y0 << 64)で3a, [x2y1:x1y0] + (x0y2 << 64)で3a. この二つを足すとき5a. その結果を[x2y2:x1y1:x0y0]に足すとき5a. 合計16a(full bitでないときは15a).

ただ実験してみると16aですむ192bit x 192bitの乗算は17a必要な素直な筆算より1clk程度遅かった. 命令順序をうまくするともしかしたら速くなるかもしれない(後でもうちょっと頑張ってみるかも).

このように最初はBの方が加算回数を1個減らせるので得かと思ったのだけど, その方法は遅かったので普通の16aかかる方を使う. すると384bitのreductionでは384bit + 256bitの加算などが必要になり, そこでaddが増えるのでトータルでは21m + 48aと1命令多くなってしまった.

ただ実測値は命令数の差以上に遅い. 3clk以上遅いということは乗算1回分損してるじゃないか. Bタイプの演算はそれだけ不利らしいと分かった. あとはBタイプのsqrでどこまで挽回できるかだな. sqrやbitが長くなったりするとBタイプは改良の余地が増えるので速くなるだろうとは思う.

昨日も書いたけど, 今日いじっててやはり連続するadcが遅いと痛感.

あと, このあたりの回数評価は論文とか何も見てない. 大昔からずっとやられてるところなのですごくて有名なトリックとかあるのかもしれない. 詳しい人, 教えてください.

2月11日_

最適化続き

mul/addの回数は変わらないけどもうちょっといじって, movの数を減らしたらかろうじて数速くなった(diff). Sandy BridgeやHaswellでmov減らすと速くなるというのはちょっと解せないけど, 実際に触ってるとわりとそうなる.

mul命令1個減らす代わりにメモリへのread/writeが1回ずつ増えたらpayしないこともある(ように見える). 結局は命令数最小を目指す方がよかったりするのだろうか.

あとadcが無視できない. AMD系は相当前からかなり速かった気がするけどIntelではaddに比べて1clk程度遅い. スループットもaddに比べて大きいので連続すると結構辛い. しかし連続して使うしかない.

mulxを使うと思ったより速くなるのはadcの隙間に入れられるせいか. でもそれぐらいout of orderでしそうな気もするが, やっぱりcarryが変わらないことも大きいのか.
# 以前のx86/x64最適化勉強会で質問されたけどよくわからない.

とにかく昔ながらのaddを無視したmulの回数だけの比較や, mul + addの回数での比較じゃ精度が悪い. 実装してみないと分からんことが多い. 今回のケースだと先にx * yを求めてからreductionするコードとの比較をしたいが, コード書くのがなにぶん面倒なんだよなあ(誰か代わりに試して←とか言うようになると若くない).

2月9日_

192bit整数のMontgomery乗算

大分前から暗号に使うための固定長整数演算クラス(mont_fp.hpp)をちまちまと改良してるのだけど, 平方(Fp::square)は作ってなかった. 小さい整数では意味がないかなあと思ってたのと, まあ別に作るの面倒だったから.

とはいえ速くなるなら作るべきなので試してみた. ただその前にどういう方針でするかいろいろ考える.

今はx=[x2:x1:x0]とy=[y2:y1:y0]をかけながら都度Montgomery reductionをする場合.

x * y0の計算で3m + 3a(m:mul, a:add). reductionの一つ分だけするのにm + (3m + 3a) + 5a = 4m + 8a. 次のx * y1で3m + 3a, それを足すのに5a, reductionで4m + 8a, 合計7m + 16a. これをもう1回7m + 16a. 合計21m + 43a.

先にx * yを求めからreductionする場合, x * yに9m + 16a. reductionがm + (3m + 3a) + 7a, m + (3m + 3a) + 6a, m + (3m + 3a) + 5aで合計12m + 27a. 全部で21m + 43a.

どちらでやっても同じ. ただ後者の場合はメモリアクセスが増える. 現在は前者で実装していた. とりあえず平方の場合はx * yが9mじゃなくて6mになるので後者の方法のまま掛け算の値をstackに保持してreadすることにした(差分). おっそろしく汚くなる.

で, 肝心のベンチマークだけどHaswellで2~3clk遅くなった? 測定誤差のせいかよくわからない. もともとmulがスループット1ぐらいなので3個mulが減ってどこまで効果があるか疑問だったけどそんな程度なのは残念.

順序いじれば多少は変わるかもしれないけどコードが増えてしまうよりはsquareはなしの方がいいのかなあ.

2月6日_

snprintfが-1を返すのはいつか

5日で書いたようにsnprintfはバッファが足りないときはエラーではないので-1は返さない. それでは-1を返すのはどんなときだろう.

たとえばsnprintf(0, 0, "%");は-1を返すけれどもそれはコンパイル時に警告が出るので分かる. そうじゃなくてprintfの文字指定としてはvalidだけどエラーが出るパターンを思いつかなかった. ロケール周りで何かおかしなことするとでるかなあと漠然と思ったけど.

で, glibcのソースを見ることにする. へーというようなパターンがあった. わかるかな?

snprintfの内部関数で_IO_vfprintfまでたどり着いたら見失った. しばらく探してvfprintf.cを見つける. なんてマクロのオンパレード. 2300行のうち1000行ぐらいがマクロ. これ, 本当に全て正しく動いてるのか. メンテできてるのが信じられない.

まあ, それはともかく多少コードを眺めてみた.

答え その1. マルチバイトの変換時にstackを使うが, それで足りないときはmallocをするようだ. そしてmallocが失敗すると-1を返す.

そしてマルチバイトで変換できない文字があってもエラーだった. つまり

snprintf(0, 0, "%S", L"\x80");

は-1を返す(VCではエラーにならなかった).

答え その2. 整数のパースでエラーになることがある. たとえば

snprintf(0, 0, "%2147483648d", 0);

これはなかなか興味深い. INT_MAXぎりぎりを渡すとどうなるだろう.

#include <stdio.h>
int main() { return snprintf(0, 0, "%2147483647d", 0); }

メモリが数GBしかないマシンではさくっとmallocに失敗して-1が返るが16GiB以上あると10秒ぐらいCPUを食って2147483647が返る. 正しいバッファサイズを求めるためにひたすら計算しているわけだ. 同様のコードをJavaで試すとかなりメモリ(10GiBぐらい)を食う(無ければもちろんout of memory).

public class T {
  public static void main(String argv[]) {
    System.out.printf("%2147483647d\n", 0);
  }
}

widthをユーザが自由に設定できるとDOS攻撃ができそうだな. あと, コードを見た限りでは-1を返すとbufは領域があるなら空文字列に設定されていた.

2月5日_

書き込み成功時の文字数はNULL文字を含まず, 失敗なら含むのは何故か.

たとえばGetWindowsDirectoryの関数の戻り値はこんな感じ.

関数が成功すると、lpBuffer バッファに書き込まれた文字数(終端の NULL 文字を除く)が TCHAR 単位で返ります。 バッファのサイズが不足して、パス名の一部を格納できなかった場合、必要なバッファのサイズ(終端の NULL 文字を含む)が返ります。

なんで成功時と失敗時でNULL文字を含んだり含まなかったり別々なんだろう, ややこしいなと思って表にしてみた. char buf[10];として(buf, sizeof(buf))を渡すとしよう.

関数の返り値
書き込みたい字列の長さ 9 10 11
常にNULLを含む値を返す場合 10 11 12
常にNULLを含まない値を返す場合 9 10 11
成功時含まない/失敗時含む場合 9 11 12

GetWindowsDirectoryのような仕様だと返り値がsizeof(buf)に等しくなることはない. 成功したかの確認で<と<=のどちらを使っても間違わないようにしたのかなと思った.

まあ単に成功したときは書き込まれた文字の長さ, 失敗したときは書き込みに必要なバッファサイズという意味と考えるとわかりやすいからかもしれないけど.

C99のvsnprintf(VC除く)もそういう仕様になっている.

2月4日_

『流星ひとつ』(沢木耕太郎)

お蔵入りになっていた30年前の藤圭子に対するインタビュー. 彼女については去年吊り革広告などで見かけた情報から亡くなったらしいということぐらいしか知らないけど, 会話のみで綴られた文章から静かにくっきりと人物像が浮かび上がる. 著者の力はやはりすごい.

2月3日_

3D Now!を思い出す

@takehiro_tさんの発言につられて自分の昔のページ(3D Now! + Enhanced 3D Now!)で復習する.

mm0=[*:D]の逆数を求める.
pfrcp    mm1,mm0
pfrcpit1 mm0,mm1
pfrcpit2 mm0,mm1    ;mm0=[*:1/D]

逆数命令はpfrcpとpfrcpit1とpfrcpit2の組み合わせ. 当時はこの逆数命令を別のものを求めるのに使えないかいろいろ調べてみたけどよくわかなかった.

mm0=[*:D]の平方根の逆数を求める.
pfrsqrt  mm1,mm0
movq     mm2,mm1
pfmul    mm1,mm1
pfrsqit1 mm1,mm0
pfrcpit2 mm1,mm2    ;mm1=[*:1/√D]

逆数平方根はpfrsqrt + pfrsqit1 + pfrcpit2とNewton法の一部. こちらも当時(今も)詳細が分かってなかった. pfrsqit2って無いですよね?

今, AMDのマニュアルを見に行って初めて知った. pfrsqit1のit1ってiteration 1の意味だったんだ.

そのころは資料もツールも殆ど無くて手さぐりだったな. 98年頃はFM-TOWNS上で3D Now!命令を扱えるアセンブラプリプロセッサを作るところから始めないといけなかった.

そういえば私が触っていたK6-2は[esi]の形のアドレッシングを使うと1クロックペナルティを食らう仕様だったような.

99年頃はペアリングと言ったらPentiumやK6-2のやつと暗号のやつと両方やってたんだなあとしみじみ.

以下言い訳(になってないけど)

とりあえず(最初のtweetの時点で)3D Now!の方がSSEよりも先に浮動小数点演算命令があって, 逆数や逆数平方根の近似命令もあった, それらが2段階的に近似するというのは覚えていた. ただ, 命令の名前は覚えてなかった.

間違えたのは自分の発言に間違いがあったと焦って続けて発言したけど実はミスってなかったという点(財布を忘れたと家に帰ったら実は持っていたみたいな). もう一つのミスはlogの近似計算で使っていたのが逆数平方根じゃなくて逆数だったという点.

2月1日_

・NULLアクセスかと思ったらメモリリークだった

たいした話ではないんだが, カレントディレクトリを取得する関数で

  const char *p = getcwd(str.getBuffer(), size);
  if (p == NULL) return false;
  ...

このようにstrの内部バッファを取得するgetBuffer()に値を設定しようとしているのを見つけた. getBuffer()はNULLを返すことがあるので, そのときはgetcwdがNULLアドレスに書き込みしようとして落ちるだろうと思った.

ところがLinuxのgetcwdのマニュアルをよく読むと第一引数がNULLだった場合は内部でmallocした領域に書き込んでそのポインタを返す仕様だった.

正確にはPOSIX.1-2008ではNULLを渡されたときの動作は未定義で, 上記の仕様はlibcの拡張らしい(getcwd). でもその拡張仕様はVisual Studioでもサポートしている(_getcwd).

というわけでそのときはgccでもVCでもpをfreeしないといけない(あるいはもちろんgetBuffer()がNULLならエラーにするチェックを先にする).

まあ, そもそもgetBuffer()がNULLを返すような状況はメモリが枯渇しているときなのでどうでもいいと言えばどうでもいいのだけど, ちょっとおもしろかった.

報道関係者の皆様へのお願い

研究できない状況になったらもともこもない. そんなに過熱してたのか.


御意見は光成滋生<herumi@nifty.com>までお願いします