ぺんぎんさんのおうち

日記です。たまに日記じゃないこともあります。

mpz_t, mpz_classのメモ

メモなのでかなり適当.HackMDに書けばよかった.

x86_64で動かすときの話.

 

mpz_classはmpz_tのwrapperで,演算子オーバーライドしてあるからプリミティブ型みたいに記述できるんだけど,一次変数のために無駄なメモリ確保が起きる可能性がある(いい感じにやってくれてメモリマネージャが動かないことの方が多いかも)のでmpz_関数を使うのが吉.

[追記]  

例として掛け算を挙げる.

mpz_class x, y, xがあり,z = x*y; と書いても mpz_mul(z, x, y); と書いても(get_mpz_tは省略した),同じアセンブリを出力してくれるっぽい.見やすい方で.

[追記おわり]  

 

数値の上限が決まってるならmpz_tじゃなくてmp_limb_t[CONST_SIZE]を使うといいぞい.

32bit,64bit環境でsizeof(mp_limb_t)が変わることに気をつけないといけないけど.

 

 

mpz_t 構造体の中身は

  • _mp_d: mp_limb_tのポインタ
  • _mp_size: 実際に利用している領域のサイズ
  • _mp_alloc: 確保している領域のサイズ

イメージとしてはGoのスライスに近いかな.

t := make([]uint64, length, capacity)としたときに,lengthが_mp_size, capacityが_mp_allocに相当してると言ってよいかも.

 

常に_mp_size < _mp_alloc が成り立つ._mp_alloc = _mp_size-1とかにしたらどうなるんだろね.

 

mpz_t xと書いてもコンストラクタは実行されないので_mp_dはNULLだし_mp_sizeや_mp_allocには適当な値が入ってる.mpz_initすると_mp_dにヒープ領域のメモリが割り当てられてsize=0とalloc=1される.mpz_initしなくても,ローカルの配列(スタック領域)の先頭アドレスを_mp_dに渡す方法がある.この場合はsizeとallocを自分で面倒見ないといけない.printデバッグしたいときによく使う.ただ,スタックのアドレスを_mp_dにセットした状態でmpz_mulとかにリターンオペランドとして渡すとバグるのでやめよう.

領域拡張するときにreallocが動いて,本来ならヒープ領域のアドレスに対して行うところをかスタック領域に対してfreeしようとするのかな."double free or corruption (out)"が出るはず.

 

mpz_initしたらちゃんとmpz_clearしておかないと,gmp_mallocで確保した領域は勝手には解放されないはず.gdbで追ってみたけどfreeされてる気がしない.関数の中でローカル変数として使うときは注意すべし.mpz_classはコンストラクタでmpz_init, デストラクタでmpz_clearを呼び出してる.

 

次のようなコードを書いてみる.

#include <gmpxx.h>

// $ g++ free_mpz.cpp -lgmpxx -lgmp -O3 && ./a.out

void f() {
    mpz_t t;
    mpz_init2(t, 0x1FFFFFFFF);
#if 0
    mpz_clear(t);
#endif
}

int main() {
     size_t n = 0x11100;
    for (size_t i = 0; i < n; i++) {
        f();
    }
}

 mpz_clearしないと

mmap(NULL, 1073745920, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)

 が出る.要するに,mallocしたのにfreeせず関数を抜けてる状態.

 

mpz_tは値が大きくなることで確保している領域を拡張することはあっても,値が小さくなったときに領域を小さくすることはない(reallocは敵)

ある程度余裕をもたせて初期化するときはmpz_init2を使う.sizeは0だけど,指定したビット数/sizeof(mp_limb_t)だけ確保しといてくれる.mpz_initしたあとにmpz_set_strするとmallocで1バイト確保したあとにreallocで領域の拡張が起きるので,mpz_set_strでセットする値のサイズが分かってるならmpz_init2で大きくメモリを確保したほうがreallocが必要なくていいかもしれない(試してないのでわからない).

 

mpz_tオブジェクト自体はサイズが固定なのでスタックに乗るけど,掛け算とかすると大抵の場合_mp_dは追加の領域が必要になるので,reallocが走ってあんまりうれしくない.

mpz_set_strとか,サイズが拡張されるような処理をやるとreallocが走ってるのが確認できた.サイズが少しずつ拡張されるようなコードを書くとreallocが頻発して速度が悪化するかも.