Rust Book 勉強会で発表をしてきた。
これは、結構前にQiitaのC++アドベントカレンダーで書いた内容と、Ben Deane氏によるCppConでの発表"Using types effectively"が下敷きになっている。
あ、この資料にクレジット書き忘れたのを思い出した、やっちまった
さて、その中で以下のRedditで出ていた話も紹介した。
スレ主の質問は、以下のenum
のサイズが20になるのは何故かというものだ。
enum Vector { V2(f32,f32), V3(f32,f32,f32), V4(f32,f32,f32,f32), }
答えは、データのレイアウトが以下のようになっているからだ。
|tag|padding|data region| |u8 |[u8;3] |[u8;16] |
データ領域は最大のものが格納できるサイズになり、どの値が入っているかを示すタグがきて、データ領域のアライメント調整用のパディングが入るというものだ。
さて、これは本当にこれ以上縮まないだろうか。
16バイトで表現できないのか?
できる気がしたので記事を書くことにする。ところでRustのf32
とかf64
ってIEEE754に従うよね?
NaNはご存知だろうか。Not A Numberの略で、値がもはや数値ではない場合に使う。例えば、0.0 / 0.0
の結果などがNaNになる。このNaNは特殊な値で、それとの大抵の演算結果がNaNになり、またNaNに関するあらゆる比較は偽になる。NaN == NaN
すら偽になる。
このNaNはexponentのビットが全て1で、mantissaが非ゼロという定義になっている。つまり、mantissaのほとんどは手付かずのまま残るということだ。mantissaのビットパターンを変えても、NaNのままにしておける。
そして、mantissa
が割と自由に決められるということは、そこに情報を押し込めるということだ。これはNaN Boxingと言われており、言語処理系でたまに使われる。
つまりだ。Rustには、NaN
の値として特別な値を準備しておき、先のenum
でV2
に値が入っていたときに残りのf32
をtagged-NaN
にしておいてV2
であることを表現するという手段が取れるだろう。ではV4(1.0, 1.0, NaN, NaN)
とどう区別するかというと、通常の演算で作られるNaN
とは異なるビットパターンを持つNaN
をtagged-NaN
として使っておけば、区別できるのではないか。
|f32|f32|tagged-NaN|tagged-NaN| |f32|f32|f32 |tagged-NaN| |f32|f32|f32 |f32 |
まあ、これを実現するためにはアーキテクチャが通常の演算から返すNaNのビットパターンが固定されている必要と、NaNの演算結果でNaNのmantissaのビットパターンが変化しないことが保証されている必要がある。
どうなのだろう。そこのところをよく知らない。
とにかく、Rustは未だに上記のenum
のサイズを20バイトにする。ここで書いたことが実はハードウェア的に実装不能なのか、それとも実装がややこしすぎる、あるいは手間に対して利益が少なすぎるとみなされているのか。私が気づくようなことがまだ提案されていないとはあまり思えないが、はて。