今年もつつがなく2月が終わってた

20代あと余命一ヶ月です。

  • ギックリ腰再び来る。ふざけるな。もう二度となるものかとトラウマを植え付けられたでおなじみのぎっくり腰になった。初体験から半年である。くそが。発症時は激しい痛みのあまり、視界がホワイトアウトし、酷いも耳鳴りもおまけに付いてきた。一人で仕事してたらこれはもうほんとに収拾不能な事象で、仕事との相性が最悪である。転職したほうがいいと思います。このブログを遡れば何年も前から他の要因で転職を考えていたが、さらに理由が増えてもうどうしようもない。結局転職はできていないしこれからもそうだろう。しかし仕事を続けられる健康もない。終わりです。

  • 株高に何一つ乗れず儲け損なっている。謎の半導体メーカーの株を買っておくだけのことだったのに。十分に買うに値する技術的理由を知っているのに。資金もあるのに。あれはいったいなんだったんだろうか。

  • PRSのレフティ買おうかどうか迷ってた。今はもういらない。やっぱ欲しい。欲しい。

  • スプラ、ロンブラ持ち始めた。今までと全く違うプレイスタイルでキル数が20とか出るので楽しい。

いつか目指した場所に咲くアムネジアなんどもおやすみ黎明の雨

時計は今しか指さない

如月の岸辺で待てばまばゆさは雪の陰影冬の有限

この空は誰の願いを叶えるの?誰も誰かは知らなくていい

今もこの瞬間だけを指し示す時計刹那に照らす雷雪

千年の命が尽きる雨上がり風が吹く方へ広がる陰

夕暮れにすべてが無駄になっていく今日も空が青かったことも

探したら見つかる場所を思い出と言うんだ いつもここで待ってるよ

haskellでゲームボーイのCPUエミュレータを書いた

一昨年の夏にどうやってもテスト通らなくて放置してたゲームボーイエミュレータを根本的に書き換えてみたらだいぶましになった。最初はlens/Stateを使う方針でCPUのレジスタなんかをレコードのフィールドにぶち込んでそれらを書き換えていく感じだったが、ざっと書いたところでひどく遅くてかなしくなったので放置していた。今回はレジスタなどの本質的にミュータブルなデータを全部Vector.Unboxed.Mutableにぶちこんだら早くなったという話。そりゃそうなんよ。

lens/StateMでできるだけイミュータブルなレコード更新をしようとした型設計。

type CPU a = StateT CPUState (StateT MBCState (StateT LoggerState IO)) a
data CPUState = CPUState {
    _a, __f, _b, _c, _d, _e, _h, _l :: Word8,
    _sp, _pc :: Word16,
    ...
  } deriving Show

makeLenses ''CPUState

フラグ更新などはFレジスタに詰め込まれているのでlens関数を用いてアクセサーを書ける

carry :: Lens' CPUState Bool
carry = lens get set
  where
    get c = testBit (__f c) 4
    set c b =
      if b then
        c { __f = setBit (__f c) 4 }
      else
        c { __f = clearBit (__f c) 4 }

命令の実装などはStateをつかうことで、たとえばrustの実装と比べるとselfをまるっと省略ができている。それでありながら動的言語の様な記法でもある。一方で変数名にcarryやhalfもしくは省略してcとhを使いたかったが、フラグとレジスタのアクセサーと名前がかぶるので使えないあたりが不便なところ。名前空間

add :: OP -> CPU ()
add op = do
  (a', c', h') <- add8CarryHalf <$> use a <*> readOP8 op
  a .= a'
  zero .= (a' == 0)
  negative .= False
  half .= h'
  carry .= c'
fn add(&mut self, op: OP)  {
    let (a, carry, half) = self.a.add_carry_half(self.load8(op));
    self.a = a;
    self.set_carry(carry);
    self.set_half(half);
    self.set_negative(false);
    self.set_zero(self.a == 0);
}

上記のhaskellの実装ではモナドスタックをつかっており、なかなかモナモナとネストしてるあたり、こんなんでいいのか感もいなめない。モナドトランスフォーマーの宿命である。バベルの塔のごとく積み上がるモナドスタックをどうにかしようという試みとしてEffectなるものがある。最近ではOCamlに公式に採用されたらしい。よくはしらんけど。そんなんでhaskellのeffect実装を調べた感じではいくつかあるものの比較的浅いスタックだとmtlより遅いらしいということで採用は見送った。

そしてそのモナドスタックを潰しながら実行してく。

type Gameboy a = StateT GameboyState IO a

data GameboyState = GameboyState {
    _cpu :: CPUState,
    _mbc :: MBCState,
    _logger :: LoggerState,
    _car :: Cartrige
  }

makeLenses ''GameboyState

stepGameboy :: GameboyState -> IO GameboyState
stepGameboy gb = do
  let run = flip runStateT
  (((_,cpu'),mbc'),logger') <- run (gb^.logger) $ run (gb^.mbc) $ run (gb^.cpu) executeCPU
  pure $ gb 
    & (cpu .~ cpu')
    . (mbc .~ mbc')
    . (logger .~ logger')

(((_,cpu'),mbc'),logger') <- run (gb^.logger) $ run (gb^.mbc) $ run (gb^.cpu) executeCPUという風にモナドスタックみを感じる。もうちょいましになりそうだけど。上記の様な実装ではゲームプレイはできない処理速度となった。一応StateとIORefとIOVectorを比べてStateが一番マシだったのでそれを採用したので、haskellでは無理なのかとかなしくなった。

その実装をたまに思い出しては少し手を入れたりしていた。モナドスタックを潰してステートをフラットにしたりした。CPUの命令がGBモナドになるので微妙な開放感がある。3倍くらい早くなったがそれでもまだ遅かった。

type GB a = StateT GBState IO a

data GBState = GBState {
  _cpu :: CPUState,
  _mbc :: MBCState,
  _logger :: LoggerState
  }

-- CPU.hs
add :: OP -> GB ()
add op = do
  (a', c', h') <- add8CarryHalf <$> (use $ cpu.a) <*> readOP8 w
  cpu %= 
      (a .~ a')
    . (zero .~ (a' == 0))
    . (negative .~ False)
    . (half .~ h')
    . (carry .~ c')

そんなところで放置してたが今年の冬にrustで書いてみたら、あまりの実装のラクさと速さに余計にhaskellとはなんだったのかとかなしみをいっそう深めたのでもう一度haskell実装を考えてみることにした。最適化をすればC++並みの速度がでるとの記載をはるか昔に見たのを思い出しながら。

IOVectorのベンチは取ったがUnboxedを失念していたのでUnboxedも見てみたところ、なんかよくわからんほどの差がでたので、なぜ忘れていたのかとさらにかなしくなった。haskellのデータはサンクと呼ばれる形で保持されている。一見ただのInt8であってもdata Int8 = I8# Int8#というようにdata型になっている。これはヒープのポインターであり遅延評価のときなどに役立つクロージャのようなもの。しかし遅延評価の必要もないプリミティブなデータであればその様なラベルを省略して生のデータとして扱えるよねというのがUnboxedだ。ということで「もう状態全部Vector.Unboxed.Mutableにぶち込もう」となり、以下のようなデータ構造と命令の実装になった。

newtype Store a = Store (MVector (PrimState IO) a)

data CPU = CPU { 
  mbc :: MBC,

  regs8 :: Store Word8,
  regs16 :: Store Word16,
  ...
  }

data CPURegisters8 = A | F | B | C | D | E | H | L | IME | Halt | Cycle
  deriving (Enum, Show, Eq)

readReg8 :: CPU -> CPURegisters8 -> IO Word8
readReg8 (CPU {..}) r = readStore regs8 $ fromEnum r

writeReg8 :: CPU -> CPURegisters8 -> Word8 -> IO ()
writeReg8 (CPU {..}) r n = writeStore regs8 (fromEnum r) n

readFlag :: CPU -> CPUFlags -> IO Bool
readFlag cpu flag = do
  f <- readReg8 cpu F
  pure $ testBit f $ 4 + fromEnum flag

writeFlag :: CPU -> CPUFlags -> Bool -> IO ()
writeFlag cpu flag bool = do
  f <- readReg8 cpu F
  let f' = (if bool then setBit else clearBit) f (4 + fromEnum flag)
  writeReg8 cpu F $ f' .&. 0b11110000


add :: CPU -> Op8 -> IO ()
add cpu op = do
  (a, carry, half) <- addCarryHalf <$> readReg8 cpu A <*> readOp8 cpu op
  writeReg8 cpu A a
  writeFlag cpu Carry carry
  writeFlag cpu Half half
  writeFlag cpu Negative False
  writeFlag cpu Zero $ a == 0

StoreはただのVector.Unboxed.Mutableのラッパーである。CPURegistersをEnumのinstanceとすることでそのままVectorのインデックスとして扱える。速さを優先してlensもStateも使わなかったのですべての関数で明示的にそれらの状態を引き回す記述が増えている。こうなってくるとただのCよりめんどくさくて遅いCである気もしてくる。

ただだいぶ早くなった。テスト用のROMを26000000CPUStepくらい回すとテストが終わる。rust版と一秒差くらいなら悪くないんじゃないかな。両方とも最適化の余地が全然あるとは思う。haskellではPPUなどを書いてないのでゲームプレイはできない。終わり。

> time { stack run -- .\rom\gb-test-roms\cpu_instrs\cpu_instrs.gb | Out-Default }
cpu_instrs

01:ok  02:ok  03:ok  04:ok  05:ok  06:ok  07:ok  08:ok  09:ok  10:ok  11:ok

Passed all tests

TotalSeconds      : 5.6943804


> time { cargo run --release .\rom\gb-test-roms\cpu_instrs\cpu_instrs.gb | Out-Default }
cpu_instrs

01:ok  02:ok  03:ok  04:ok  05:ok  06:ok  07:ok  08:ok  09:ok  10:ok  11:ok

Passed all tests

TotalSeconds      : 4.6624661

追記。rust版だけloggerがオンになってたのでオフにして計測し直した。約2.5秒早くなった。

> time { cargo run --release .\rom\gb-test-roms\cpu_instrs\cpu_instrs.gb | Out-Default }
cpu_instrs

01:ok  02:ok  03:ok  04:ok  05:ok  06:ok  07:ok  08:ok  09:ok  10:ok  11:ok

Passed all tests

TotalSeconds      : 2.16294

動的にステートフルなトレイトオブジェクト

ゲームボーイにはMBCというコンポーネントがある。メモリとROM(ゲームソフト)にアクセスするためのもので16bitしかないメモリアドレス空間を拡張的に使うものだ。MBCにはいくつもの種類があり、微妙に実装が違う。どのMBCを使うかはを実行時に決まるので、ここで他言語におけるインターフェイスやトレイトオブジェクト的なものがほしいのだが、haskellには静的な型クラスしかない(ゆうてここあんま調べてなかったな。ふつうにData.Dynamicでいけるんかな)。そんなこんなでhaskellでもlens/Stateでステートフルなトレイトオブジェクトもどきを使った。

type MBC a = StateT MBCState (StateT LoggerState IO) a

data MBCState = MBCState {
    _mbcnState :: MBCNState,
    _memory :: Memory,
    _reader :: Int -> MBC Word8,
    _writer :: Int -> Word8 -> MBC ()
  }

data MBCNState
  = MBC0State
  | MBC1State {
    _bank :: Int,
    _bank1 :: Int,
    _bank2 :: Int,
    _bankRAMX :: Int,
    _enableRAMX :: Bool,
    _bankingMode :: Bool
  } 
  deriving Show

makeLenses ''MBCState
makeLenses ''MBCNState

newMBCState :: Cartrige -> IO MBCState
newMBCState car = do
  _memory <- newMemory car
  pure $ MBCState { .. }
  where
    (_mbcnState, _reader, _writer) = case car^.mbcType of
      MBC0 -> (MBC0State, readMBC0, writeMBC0)
      MBC1 -> (MBC1State 0x4000 1 0 0 False False, readMBC1, writeMBC1)
      _ -> undefined

newするタイミングでROMに使われているMBCのタイプを読み取り実装と状態を選択するVTable的なやつ。

readMBC1 :: Int -> MBC Word8
readMBC1 i
  | 0 <= i && i <= 0x3fff = (V.! i) <$> (use $ memory.cartrige.rom)

  | 0x4000 <= i && i <= 0x7fff = do
    (Just b) <- preuse $ mbcnState.bank
    rom' <- use $ memory.cartrige.rom
    pure (rom' V.! (b .|. (i - 0x4000)))

  | 0x8000 <= i && i <= 0x9fff = do
    ram' <- use $ memory.ram
    lift $ VM.read ram' i

  | 0xa000 <= i && i <= 0xbfff = do
    ramx' <- use $ memory.ramx
    (Just b) <- preuse $ mbcnState.bankRAMX
    if b == 0 then do
      ram' <- use $ memory.ram
      lift $ VM.read ram' i
    else 
      lift $ VM.read ramx' (b .|. (i - 0xa000))

  | otherwise = do
    ram' <- use $ memory.ram
    lift $ VM.read ram' i

preuseしてちゃんと自分のステートを引っ張ってこれる。しかしpreuseはおそらく毎回直和型を走査してデータを引っ張ってきているのでコストがかかる。newしたタイミングでそれは決定していて無駄な走査なので気になる点ではある。

Vector.Unboxed.Mutableで書き直した版ではStore Word64にまとめて放り込んでいて、すべてのMBCTypeの関数で同じ型のMBCStateを引き回してる。readするたびMBCTypeを判別しているので良くない気がする。

readMBC :: MBC -> Word16 -> IO Word8
readMBC mbc@(MBC {..}) i = case mbcType cartridge of
  MBC1 -> readMBC1 mbc i
  _ -> error "readMBC unimplement MBCType"

そういえば、zig

一昨年の夏、僕がhaskellに絶望していたころzigが流行っていたのでzigでも書いたのを思い出した。早すぎて意味がわからん。こんだけちがうとrust版もなんかおかしいな。

> time { zig build run -Drelease-fast -- .\rom\gb-test-roms\cpu_instrs\cpu_instrs.gb }
Serial:
cpu_instrs

01:ok  02:ok  03:ok  04:ok  05:ok  06:ok  07:ok  08:ok  09:ok  10:ok  11:ok

Passed all tests

TotalSeconds      : 0.937751

絶賛空売り敗北中

先々月くらいから信用取引をするようになって空売りに手を出してる。今月の株高に空売りのポジションとって全敗。6ヶ月か一年以内に買い戻さないといけないので下がるまで気軽に待つかというのがやりにくいというのもあるが、 元々短期で見るつもりのポジションだったので、読みが外れたら早々に損切りしてる。まだ含み損のポジションが残っててこれも怪しい感じなのでかなしい。今月のお小遣いないなった。。。

さて、ジェシー・リバモア 世紀の相場師という本を読んでる。なかなかおもしろい本で今月の僕の失敗を咎めるようなことが多く書かれていて学びがある。

  • 内なる感情との戦い
    • 感情が自分のルールから自分を逸脱させる
  • 最小抵抗ラインを行く
    • これの定義がよくわからんけど、トレンドのようなものだろうか
  • 相場の流れに逆らわない
    • 値上がっている時に売りから入らない
    • 値下がっている時に買いから入らない
  • 市場の潮目を待つ
    • 自分の理論に当てはまる状況になるまで忍耐強く待つ
    • 彼はポジションを取っているときより取っていないときの方がずっと多かった
  • 高値で売り抜け、底値で買い取りなど考えない
  • ピボタルポイント
    • 地盤
    • 反転
    • 継続
  • 「一日逆転のポイント」は赤信号
    • 最高値を付けながらも引け近くになってストンと前日の安値を下回るような時は撤退時

6割くらいしかまだ読んでないがすでに刺さるところがある。ここ一週間仕事がなくて冬休みシーズン2が始まっていたので相場をみてることが多かったのだが、自分なりに思うところができてきた。

  • 値動きが一番激しいのは開始直後から一時間以内
    • 特段ニュースがない限り
    • その時間の上ぶれ下ぶれを見逃さないようにする
    • 普段は仕事中なので無理
  • 12時半から1時は一般素人、1時すぎからは本職
    • お昼休み中にアマと、お昼休みから戻ったプロ
    • もちろんプロも後場開始直後から参加してるに決まってるんだけど
    • 1時を境に値動きが違う気がする
    • 僕も1時から仕事なので無理

などといった、しょうもない群集心理を思ったりした。日経平均最高値更新からの一服感は引っ張っていた外国筋が利確して米株に流れているからなのではないかと推測。米株が高値更新してるから日本株もいつもなら追従するはずだが、短期的にそれがないのは日本株から米株へ流れているんだろうなというがしてるので、NYダウが上がってる間はN255は下がるんじゃねと思いつつも、N255の空売り損切りしてるので、眼の前のマイナス値の不快感から開放されたい欲のほうが強いんだよな。など。

初日の入り

夕暮れの前後の赤の多彩さのどれを思いだせば君の頬

風音と波音を消すアラートにかき消されてく僕の存在

ぐらぐらと恐怖沸き立つゆらゆらと水面を撫でる初日の入り

目を細め歪めて映す光だけ識別すべくレンズを拭いた

欲望に手つかずのまま冬休み終わる夜に言い返せない

落ちてくることを期待し追いかける落ちてくることなく諦める

誰しもが必ずいつか消えていく推しは推せるときに推し尽くせ

抜け殻の様な言葉を伝えても体はいつも動かないまま

Mazda3にNDロードスターシフトノブ付けて外した

微妙だったな。2年近く乗ってきて慣れ親しんでいるのもあるけど、標準品の一体感、滑らか感が心地よい。NDの物は300gあるらしく確かにだいぶずっしりしてる。その分シフトチェンジするのに比較的余計な力が必要になるし、タクタイルの振動反動が大きく手に伝わるショックがいまいちだった。自分の持ち方ではシフトの球体の皮の部分ではなくて、球の下の金属部分にいつも手が触れていて、指先の感触が微妙。皮に触りたい。標準品は皮の部分とその下の金属部分は滑らかに繋がっていている。NDのは球体と支持棒みたいな感じで滑らかに繋がってはいない。そのあたりも指先の違和感だった。さらにND品のステッチの一番下の縫い目の糸が指にひっかかりそこもフリクション。まぁ慣れなんだろうけど一時間走って標準品に戻したのでした。