scalametaのbug調査のついでのメモ。
30万行のScalaコードをscalametaでparseすると、そこには11億個のTrieMapが〜〜〜https://t.co/TVgP3w2CMV pic.twitter.com/0e2HbBBZOJ
— Kenji Yoshida (@xuwei_k) March 16, 2024
いや11億個は嘘じゃん、11億はByte数じゃん
— Kenji Yoshida (@xuwei_k) 2024年3月16日
TrieMapでcacheするようにしたらしいけれど、それが必要ない時も生成されて、scala/scala 2.13.x branchのtest除いた30万行程度のコードをparseするだけで、(全部のTreeを保持したままにすると)メモリ8G Byteでも足りない、みたいなことになってました。 (あと数割増やすと、どうにか動く)
それでVisualVMの結果をscalametaのissueに貼ったので、そちらの問題に興味がある人はそっち見てもらうとして、TrieMap普通にemptyで生成するだけでも、結構な量使いそうだな?と思ったので、メモ。
VisualVMの出力からも計算できるし、実際にjolなどで計測してもいいですが、つまり以下のような感じで、Scala 2.13.13時点で168 byteだと思われます。
それぞれ1インスタンスあたり
- 32 byte: AtomicReferenceFieldUpdaterImpl, TrieMap, CNode
- 24 byte: INode
- 16 byte: Array[BasicNode], Gen, TrieMap$MangleHashing
合計: (32 * 3) + (24 * 1) + (16 * 3) = 168 byte
issueに貼ったもので
- TrieMap関連が、それぞれ、大体3732万4259個生成されてる
3732万4259個 * 168 byte = 6270475512 byte
=> これだけで8GBの大半を占める => そりゃまぁOutOfMemoryErrorになる
という感じで計算も合います。
同じ方法で java.util.concurrent.ConcurrentHashMap
生成すると、おそらく64 byteなので、2.6倍くらい消費する・・・つらい・・・。
val xs = List.fill(1_000_000)(scala.collection.concurrent.TrieMap.empty[String, String]) Thread.sleep(1_000_000)
というコード実行してVisualVMで見た例
jolの説明は今回書かないので、以下などを参照
以下、jolでの例
> jol scala.collection.concurrent.TrieMap scala.collection.concurrent.TrieMap object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) N/A 8 4 (object header: class) N/A 12 4 scala.util.hashing.Hashing TrieMap.hashingobj N/A 16 4 scala.math.Equiv TrieMap.equalityobj N/A 20 4 java.util.concurrent.atomic.AtomicReferenceFieldUpdater TrieMap.rootupdater N/A 24 4 java.lang.Object TrieMap.root N/A 28 4 (object alignment gap) Instance size: 32 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
> jol scala.collection.concurrent.Gen scala.collection.concurrent.Gen object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) N/A 8 4 (object header: class) N/A 12 4 (object alignment gap) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
> jol scala.collection.concurrent.CNode scala.collection.concurrent.CNode object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) N/A 8 4 (object header: class) N/A 12 4 scala.collection.concurrent.MainNode MainNode.prev N/A 16 4 int CNodeBase.csize N/A 20 4 int CNode.bitmap N/A 24 4 scala.collection.concurrent.BasicNode[] CNode.array N/A 28 4 scala.collection.concurrent.Gen CNode.gen N/A Instance size: 32 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
> jol scala.collection.concurrent.INode scala.collection.concurrent.INode object internals: OFF SZ TYPE DESCRIPTION VALUE 0 8 (object header: mark) N/A 8 4 (object header: class) N/A 12 4 scala.collection.concurrent.MainNode INodeBase.mainnode N/A 16 4 scala.collection.concurrent.Gen INodeBase.gen N/A 20 4 scala.math.Equiv INode.equiv N/A Instance size: 24 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total