Nimもくもく会LT

発表者: mofu @2vg

テーマ

Nimのメモリ周りについてちょこっと

メモリとポインタ

変数の値などなど、全てメモリ上に存在している

 

ある値のメモリ上の場所の事をアドレス

アドレスを確保しておく変数の事をポインタ変数(ポインタ)

Nimのポインタ

refとptrの2種類のポインタ変数の型がある

 

ref: GCが監視していて、不要になったら勝手に解放

ptr: 解放はプログラマがやる、バグを生みやすい

何でptrがあるの?

主にC言語などとの連携で使うため

低レベルで最適化するため

スタックとヒープ

スタック: コンパイラやOSが割り当てるメモリ領域で、アプリケーション側は自由に操作出来ない領域。関数などの一時的なデータ保存に使われ、自動的に開放される。


ヒープ: アプリケーションやOSが割り当てるメモリ領域で、動的にメモリ領域を確保したり解放できる。

# refポインタ型変数宣言
var refStr: ref string

# アドレスを入れてないため、nil
echo repr refStr # nil

# string型の場所を確保し、アドレスを代入
refStr = new string

# アドレスは表示されるが、文字列が何も入っていない
# Nimのstring型のデフォルト値はnil
echo repr refStr # ref 0x7fcf2c41b048 --> nil

# アドレスを逆参照して、"test"を代入
refStr[] = "test"

# アドレスを逆参照して、表示
echo refStr[]    # test

ref 使い方 ①

type
  Obj = object
    name: string

  ObjRef = ref Obj # Objのrefバージョン

proc oCreate1(): Obj =
  result = Obj(name: "john")
  echo repr result

proc oCreate2(): ObjRef =
  result = Obj(name: "john")
  echo repr result

var
  o1 = oCreate1()
  o2 = oCreate2()

echo repr o1
echo repr o2

----------------------------------------------------------------------------

[name = 0x7ff1c7df7058"mofu"]

ref 0x7ff1c7dfd048 --> [name = 0x7ff1c7df70a8"2vg"]

-----------

[name = 0x7ff1c7df7080"mofu"]

ref 0x7ff1c7dfd048 --> [name = 0x7ff1c7df70a8"2vg"]

ref 使い方 ②

var ptrStr: ptr string

echo repr ptrStr # nil

ptrStr = cast[ptr string](alloc0(string.sizeof * 1024))
ptrStr = create(string, 1024)

echo repr ptrStr # ref 0x7fcf2c41b048 --> nil

ptrStr[] = "test"

echo ptrStr[]    # test

dealloc(ptrStr)  # 必ず解放すること!

ptr 使い方

alloc, alloc0などにサイズを渡すとpointer型がかえってくる

それを任意の型にcast[ptr 型](pointer)でキャスト

NimのGC

実は複数のGCが搭載されている

コンパイルオプション  --gc:GC名で切り替え

refc 遅延参照カウント。
デフォルトGC。
markAndSweep 単純なマーク&スウィープのGC。
v2 インクリメンタル マーク&スウィープGC。
boehm 単純なboehmGC実装。ptrも追跡。
go Go言語のGC。libgoが必要。
none GCをオフにする。変態向け。

GCの回収を制御

防ぐ方法は2種類

 

refを保護: GC_ref, GC_unref

ptrを保護: protect, dispose

挙動を見る前に

GCによって管理されるメモリに関する関数

 

  • getTotalMem: GCによって管理される総メモリ量。
  • getOc​​cupiedMem: GCによって予約され、オブジェクトによって使用されるバイト
  • getFreeMem: GCによって予約され、使用されていないバイト

まずはrefの挙動

const MB = 1024 * 1024

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB

echo "----start memory alloc----"

for _ in 0 ..< 100:
  var obj = new array[1*MB, char]
  #GC_ref(obj)

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB
GC_refで保護しない場合

0.50390625
0.05864715576171875
0.43359375
----start memory alloc----
4.62890625
1.090072631835938
3.52734375
GC_refで保護した場合

0.50390625
0.05864715576171875
0.43359375
----start memory alloc----
132.234375
103.1912384033203
29.0234375

ptrの挙動

const MB = 1024 * 1024

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB

echo "----start memory alloc----"

for i in 0 ..< 100:
  var mem = create(array[1*MB, char])
  dealloc(mem)

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB
freeしない場合

0.50390625
0.05864715576171875
0.43359375
----start memory alloc----
132.21484375
103.1840057373047
29.0078125
freeした場合

0.50390625
0.05864715576171875
0.43359375
----start memory alloc----
1.53515625
0.0588226318359375
1.46484375

boehmGCの場合

※ bohemGCだと手動確保も追跡 😇

# nim c -r --gc:boehm main.nim
const MB = 1024 * 1024

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB

echo "----start memory alloc----"

for _ in 0 ..< 100:
  var mem = create(array[1*MB, char]); #dealloc(mem)

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB
freeしない場合

0.0625
0.00390625
0.0546875
----start memory alloc----
2.44921875
2.015625
0.43359375
freeした場合

0.0625
0.00390625
0.0546875
----start memory alloc----
1.0859375
0.0078125
1.078125

protectで守る

# nim c -r --gc:boehm main.nim
const MB = 1024 * 1024

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB

echo "----start memory alloc----"
var f: array[100, ForeignCell] # 配列に保存
for i in 0 ..< 100:
  var mem = create(array[1*MB, char]); f[i] = protect(mem)

echo getTotalMem() / MB
echo getOccupiedMem() / MB
echo getFreeMem() / MB
protectしない場合

0.0625
0.00390625
0.0546875
----start memory alloc----
2.44921875
2.015625
0.43359375
protectした場合

0.0625
0.00390625
0.0546875
----start memory alloc----
115.66015625
100.3984375
15.26171875

アロケートの種類とか

alloc(サイズ)
allocShared(サイズ)
createU(型, サイズ)
createSharedU(型, サイズ)
サイズ分メモリを確保。createは型サイズ*サイズ分
Sharedは複数スレッドでのアロケートの実行を一回ずつにする
alloc0(サイズ)
allocShared0(サイズ)
create(型, サイズ)
createShared(型, サイズ)

確保の動作は上のと同じだが、確保したメモリ領域を0埋めする

解放はdealloc, deallocSharedを使う

またはresizeでサイズを0にする

alloc(サイズ)
allocShared(サイズ)
createU(型, サイズ)
createSharedU(型, サイズ)
サイズ分メモリを確保。createは型サイズ*サイズ分
Sharedは複数スレッドでのアロケートの実行を一回ずつにする
alloc0(サイズ)
allocShared0(サイズ)
create(型, サイズ)
createShared(型, サイズ)

確保の動作は上のと同じだが、確保したメモリ領域を0埋めする

Let's 手動メモリ管理 

終わり