19
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

一度確保した領域を使い回す

Last updated at Posted at 2014-07-29

初期化に時間がかかったり、そもそもメモリを確保する時間が問題になる場合、
一度確保したインスタンスは使い回したい。

毎回確保する

あるクラスAのインスタンスが計算の一時領域として必要だとする。

void func(int N){
  A a(N);
  ...
}

これだとこの関数が呼ばれるたびに初期化が必要となる。
この関数が何回も呼ばれるとすると、new/deleteのコストが無視できない事もある。
そんな場合にどうするかが今回の主題である。

静的に確保する

void func(int N){
  static A a(N);
  ...
}

これで初期化は最初に呼ばれた時だけになる。
しかし問題が2つある:

  • 初期化のための引数が最初の一回しか渡せない
  • このような関数が複数あったとき、関数毎に静的変数が生成されメモリを沢山使う

関数オブジェクトにする

class func{
  A a;
public:
  func(int N): a(N) {}
  void operator(){
    ...
  }
};

これも静的に確保してるのと同様である。

一度確保したインスタンスを使い回す

以上の不満を解決するには、
一旦確保したインスタンスをその引数と共に覚えておく人がいて、
各関数はその人から一時的に借りてこれたらいい。
という事でこんなクラスを作る:

Rotary.hpp
# include <vector>
# include <memory>
# include <boost/any.hpp>

template <class T> class Rotary {
  /** 確保したインスタンスを引数と共に保持する */
  struct Item {
    bool isFree; // 貸し出しているか
    boost::any args; // 引数
    T *ptr; // インスタンスのポインタ
  };
  std::vector<Item> items;

  Rotary() {};
  ~Rotary() {
    for (auto &i : items) {
      delete i.ptr;
    }
  }

  Rotary(const Rotary &) = delete;
  Rotary(Rotary &&) = delete;

private:
  /** 借りたメモリを返却する */
  static void release(T *p) {
    auto &man = getRef();
    for (auto &i : man.items) {
      if (p == i.ptr)
        i.isFree = true;
    }
  }

public:
  /** 自動的に返却される */
  typedef std::unique_ptr<T, decltype(&release)> Pointer;

private:
  /** 貸し出す */
  template <typename... Args> Pointer rent_dyn(Args... args) {
    typedef std::tuple<Args...> Tuple;
    Tuple t = std::make_tuple(args...);
    for (auto &i : items) {
      if (i.isFree && typeid(Tuple) == i.args.type() &&
          t == boost::any_cast<Tuple>(i.args)) {
        i.isFree = false;
        return Pointer(i.ptr, release);
      }
    } // 既に確保したインスタンスの内、貸し出していないのがあればそれを貸す
    /* 無ければ新しく用意する */
    T *mem = new T(args...);
    items.push_back({ false, t, mem });
    return Pointer(mem, release);
  }

  static Rotary<T> &getRef() {
    static Rotary<T> rot; // singleton (スレッドセーフではない)
    return rot;
  }

public:
  template <typename... Args> static Pointer rent(Args... args) {
    return getRef().rent_dyn(args...);
  }
};
# include "Rotary.hpp"
# include <iostream>

typedef Rotary<std::vector<int> > Rot;

void func1(int N) {
  auto tmp1 = Rot::rent(N);
  std::cout << tmp1->size() << std::endl;
  std::cout << tmp1.get() << std::endl;
}

void func2(int N) {
  auto tmp1 = Rot::rent(N);
  auto tmp2 = Rot::rent(N);
  std::cout << tmp1->size() << std::endl;
  std::cout << tmp1.get() << std::endl;
  std::cout << tmp2->size() << std::endl;
  std::cout << tmp2.get() << std::endl;
}

int main(int argc, char const *argv[]) {
  std::cout << "func1(10)" << std::endl;
  func1(10);
  std::cout << "func1(10)" << std::endl;
  func1(10);
  std::cout << "func2(10)" << std::endl;
  func2(10);
  std::cout << "func1(8)" << std::endl;
  func1(8);
  std::cout << "func1(10)" << std::endl;
  func1(10);
  return 0;
}

こんな風に各関数はロータリーからメモリを借りてこれる。

func1(10)
10
0x236c010 # 初確保
func1(10)
10
0x236c010 # 同じのを使う
func2(10)
10
0x236c010 # 一つ目は同じのを使う
10
0x236c0a0 # 既存のメモリは全部貸し出し中だから新しく確保
func1(8)
8
0x236c080 # 引数が違ったら別の領域に確保
func1(10)
10
0x236c010 # ちゃんと最初に確保したのを使ってる

のように同じメモリを使い回せている事が分かる。

課題

  • vector<Item>unordered_mapにする方が検索が早いはず
  • 複数の翻訳単位があるときの静的変数static Rotary<T> rotの扱い
19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?