Bercriber's Blog

すべて 日記 音楽 短歌 振り返り プログラミング

2024/02/14 17:57


時計は今しか指さない

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

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

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

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

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

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


2024/01/26 20:35


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

2024/01/26 10:37


絶賛空売り敗北中

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

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

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

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


2024/01/22 12:22


初日の入り

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

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

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

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

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

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

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

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


2024/01/06 17:59


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

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


2024/01/02 00:09


初日の入り


2023/12/30 15:19


2023年まとめ

買って良かった物

適当に管理してるCSV家計簿によると今年はAmazonに53万使ったらしい。怖い。Kindleの漫画に10万はまぁいいとして、お菓子に10万つかってて草。糖尿病への架け橋かかりだしてるのかもしれないしそうではないとおもう。

漫画

健康

今年はぎっくり腰以外目立って不調はなかったような気がするが。8月のギックリ腰が残りの人生のすべてを変えたしまったのではないかというくらい、ずっと腰痛いUltra。オワタ。ほんとにただただ失われていくだけの人生に成り果てているということがずっと悲しい。メンタルはずっとおわってる。あと歯が~

Mazda3二年目。全然乗ってねぇ。s660かロードスターに乗り換えたいんですけどどうでしょうか>お財布先生

約55マソの利益で、10マソの損失らしい。11月くらいから空売り初めて、いまちゃんと任天堂株含み損RTX4090。なんでやん。一週間くらいの短期トレードをパチンコ感覚でやってる。今年はトレードの勉強少ししようか。短期のトレンドに右往左往するトレードに勉強もクソもあるのかよくわからん7090x。

音楽

すみません。一曲も作ってないです。こないだDAW開いてみたんですけど何やっていいかわからなかったです。。。

ゲーム

何本か買ったらしいがスプラ以外まともにやってない。サーモンランが生活に組み込まれてしまっているので精神安定剤としての役割を担い始めたらしい。ガチマは今シーズンスプスコでやっとS+に上がれて、エリアXP1900。初めてXマッチ計測した時からなんにも成長してないな。Steamの履歴覗いたら他のゲームもちゃんとやってたわ。Vampire Survivors,Outer Wilds,one step from eden。買ったけどほとんどプレイしてないものとして、ホグワーツレガシー、オクトパストラベラー2、TUNIC。とりまマリオRPGやろうず。

短歌

三日月の欠けた部分を運び込み満月にする仕事してます

幽霊と話せる公衆電話に百円入れて百円お釣り

僕だけを僕たらしめる歩みさえ何もしてないと言われたんだ

生き抜いて生き抜いた先待つ人よどうして待っているんだ俺を

唐突にありがとうとか言われたぞ一体何をしたんだ俺は

生ぬるい雨の匂いがしてきたら夕立ちは去る僕も帰れる

何もかも忘れていたい夏を背に 背に 背に 背に 背にして忘れる

いつまでも売り切れのままの自販機はミイラ製造機だったよさよなら

閃きは眠気の中で瞬きて忘れることを約束されてる

終着を眼に映す来た道をとぼとぼ帰ることはもうなく

夏風に歌心ゆれ僕はただずっとまえからいやだったんだ


2023/12/08 18:02


先月は11月だったんだよ

月イチブログに書くことナッシングアイウィルリグレット

さて、色々買った気がする

買えてないモノ


毎週の楽しみとして、フリーレン、呪術廻戦のアニメ。久々にアニメの更新を楽しみにしてる。ゲーム。スプラ3アプデ来て何故か久しぶりにスプスコもってる。熟練度5になったけどS+にすら上がれなくなっててかなしけり。竹かバレルもったら余裕なはずなんだそうにちがいない。サモラン楽しけり

blueskyのinvite来てて覗いたら昔懐かしの人がいて、どうしよう。彼らとのどうしようもない格差を未だに僕は直視できないままだ。Twitterにまだまだ見ていたい人が沢山いるから別SNSめんどくさい

noteに短歌投げたらコメントきてた。一年ぶりじゃねぇの。キニナルが気に仕方がわからない。うーん

そろそろ曲作ってくれ

雨夜雨雲の向こう側必ず月は綺麗なままだ


2023/11/22 23:18


秋の火の部分

秋空に取り残された夏の曇夕立の匂いがした直後

ポケットにしまったままにした夕日すごい洗濯機光ってたよ

アスファルト蜃気楼もう見えなくてもう見えない向こう側の夏

許せないあの日の自分許したしお前の金のこととかついでに

立冬に残暑と言うような日差しの日を担う秋の火の部分

突然の冬嵐より来たる使者すごく見覚えあります君を

触れられるうちに触れようとしないでどんどん厚着になって遠い

僕だけを僕たらしめる歩みさえ何もしてないと言われたんだ

生き抜いて生き抜いた先待つ人よどうして待っているんだ俺を


2023/11/01 15:18


10月を通り抜けて

今年もあと二ヶ月とかなんとかよりも俺の20代があと余命半年もないことに焦燥感を感じる。死なないでくれ俺の20代。

月初めあたりから腰痛が悪化して接骨院ちょっと通った。効果があったのかはよくわからん。そんなことより休みをもらったのでそっちのほうがうれしかった。無限ギックリ腰編終わらないんだろうな。新しいオムロンの低周波治療器を買った。これが当たりでかなり腰の状態がましになってる。しかし毎夜毎夜一時間腰回りにビリビリさせてるのはめんどくさいがこの習慣をやめるとすぐに悪化しそうなのでしばらくどころかずっとやめられないのではないかと思う。無限ビクンビクン編入ってます。しかしパッドが外れやすいのがストレス。その毎夜の一時間何やっているのかというとKindleで漫画読んでる。ナルトを全巻まとめ買いしたので今のところそれ読んでれば何ら退屈しない。ナルト読み終わったら積読してた本を消化していこうと思うがタブレットで読めるから寝転んだ状態でも読みやすいのであって、紙の本では腕が辛そうではある。

スプラ3相変わらず毎日のようにやってる。あきそうあきそうと言いながら全然やってて、ちゃんと楽しい。最近はバレルスピナーを持つようになってそれがめっちゃいい。人速2.6安全靴1アク強0.3の飾り気のない王道ギアって感じだけど、足速いのくそたのしい。あとテイオウイカ。無印よりデコのほうを気に入ってきて、テイオウの強制的に相手を下がらせる感じがよいし、センサーとの相性もよい。しかしテイオウで仕留めるの難しすぎてただの自滅しに行く人になってることも多々ある。操作がむずいし、意外と相手の球で体が弾かれる。あと感度もだいぶ変えた。-2から+4にしたので今までジャイロ足りない!とか言って戦えなかった近接戦も対応できるようになったのが嬉しい。反面ずっとチャージャー使いだったので、今まで使っていた竹やスコープなどの難易度がだいぶ上がったどころか無理になった気がする。スコープはさすがに諦めて高感度竹を練習中。まだまだ楽しい。

プログラミングやってない。Zigで書いたゲームボーイエミュレータのRust版をちょっと書いたけど、ちょっと書いただけ。あんまりコード書く気になれなくてかなしけり。

株ちょっと売り買いしてちょっと利益でた。カバーと任天堂株を短期でちょこちょこ。お小遣い増えてやったね。利確したあとに買い増したカバー株マイナスになっててかなしけり。短期で見ても26くらいまで上がってくれてもいいはずなんだけど、絶賛23代。ちゃんと利益だしてるところなので長期で持っていても損はなさそうだけど、VTuberというのがこれからくるVR時代にどう適応するのか長期の見通しが不明感ある。YoutubeがVRプラットフォームを出すのであれば収益体形を維持できそうではあるがはたして。決算もうすぐだが株価どうなんのっと。

いい曲とたくさん出会えて嬉しい。Re-swimとかAnytime,Anywhereとか。BIGMAMAも新しいアルバム出してくれたし。自分でも作りたいと思うが、プログラミング以上にやる気でなくてかなしけり。単純に何やっていいかわからんのよな。DAW立ち上げてプロジェクト作って、そして二度と開かないとという。なぜなんだぜ。ヨルシカの新しいTab譜がでたのでそれ少しやってる。ほんとギターってくそだなってくらい練習がストレス。ただ音を出すだけなのにコストがかかりすぎてあほらしい。ピアノの偉大さを改めて感じるも俺のピアノの上には本やお菓子が積まれている。

メガネいい加減レンズ新しくせねばせねばと思っていつまでもしないままでいる。レンズのみを新しくするには店舗に実際に行ってお願いする必要があるわけだがそれがくそめんどくさい。ので新しいフレームとレンズを買おうとなるわけだが、Talexのオーバーグラスが必需品になっているので使えるフレームのサイズが凄まじく限定される。Webに記載されている寸法をよく確認しながら3つくらいメガネ買ったけど、きれいにオーバーグラスの内側に収まってくれないので困ってる。すごい困ってる。一昨日も一つ注文した。サイズ的には大丈夫なはずだが、ブリッジの形によってはうまく収まらなかったりするのでうまくいくかはまだわからない。すっごい困ってる。

僕だけを僕たらしめる歩みさえ何もしてないと言われたんだ



24