Flutterエンジンソース解読-メモリ管理編


サマリ
本文は主にFlutterエンジンの中のメモリ管理に関するソースコードを解読し、Flutterエンジンのコアコードの多くはC++で書かれており、メモリ管理は主に引用カウントであり、C++言語自体の柔軟性と結びつけて、少ないコードでObjective-C言語に類似したARCのメモリ管理能力を実現した.
開始前
C++コードでは多くのマクロに遭遇するのが一般的ですが、これらのマクロの意味を理解するには、その背後にあるソースコードを参照する必要があります.メモリモデルに関連するソースコードで出会ったマクロについて、冒頭に簡単な紹介をします.[fluter/engine/fml/macros.h]
マクロ名自体が最高のコメントで、C++ではdeleteでcopy、assign、moveなどの関数を無効にします.
#define FML_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete

#define FML_DISALLOW_ASSIGN(TypeName) \
  TypeName& operator=(const TypeName&) = delete

#define FML_DISALLOW_MOVE(TypeName) \
  TypeName(TypeName&&) = delete;    \
  TypeName& operator=(TypeName&&) = delete

#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&) = delete;          \
  TypeName& operator=(const TypeName&) = delete

#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \
  TypeName(const TypeName&) = delete;               \
  TypeName(TypeName&&) = delete;                    \
  TypeName& operator=(const TypeName&) = delete;    \
  TypeName& operator=(TypeName&&) = delete

#define FML_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
  TypeName() = delete;                               \
  FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName)


ソース構造
- flutter/engine/fml/memory
    - ref_ptr.h
    - ref_ptr_internal.h
    - ref_counted.h
    - ref_counted_internal.h
    - weak_ptr.h
    - weak_ptr_internal.h
    - weak_ptr_internal.cc
    - thread_checker.h

重要な概念
次のいくつかの重要な概念に注目する必要があります.
  • 参照ポインタ、参照カウントの実装は、参照ポインタによってインスタンスを指し、インスタンスはメモリの解放を管理するために参照をカウントする.
  • 弱いポインタは、メモリへの参照が参照カウントを増加させないことを示す弱いポインタが必要です.
  • Thread Safeは、メモリ管理に関する面で、ポインタがスレッドの安全な
  • であるかどうかに注目する必要があります.
    リファレンスポインタ
    参照ポインタは、RefCountedThreadSafeを継承したクラスのインスタンスを指し、参照ポインタ自体のスタックアウトスタックによってクラスインスタンスの参照カウントの増減を実現することができる.
    RefCountedThreadSafeBase
    ソースパス、[fml/memory/ref_counted_internal.h]
    このクラスは、参照カウントを使用するすべてのクラスの最初のベースクラスとして存在します.主に、参照カウントの基本的な3つの能力を提供します.
  • ref_count_,初期化時のデフォルトは1 u
  • です.
  • AddRefは、参照カウントを増加するとともに、動作の原子性
  • を確保する.
  • Releaseは、参照カウントを低減するとともに、動作の原子性
  • を確保する.
    class RefCountedThreadSafeBase {
     public:
      void AddRef() const {
    #ifndef NDEBUG
        FML_DCHECK(!adoption_required_);
        FML_DCHECK(!destruction_started_);
    #endif
        ref_count_.fetch_add(1u, std::memory_order_relaxed);
      }
    
      bool HasOneRef() const {
        return ref_count_.load(std::memory_order_acquire) == 1u;
      }
    
      void AssertHasOneRef() const { FML_DCHECK(HasOneRef()); }
    
     protected:
      RefCountedThreadSafeBase();
      ~RefCountedThreadSafeBase();
    
      // Returns true if the object should self-delete.
      bool Release() const {
    #ifndef NDEBUG
        FML_DCHECK(!adoption_required_);
        FML_DCHECK(!destruction_started_);
    #endif
        FML_DCHECK(ref_count_.load(std::memory_order_acquire) != 0u);
        if (ref_count_.fetch_sub(1u, std::memory_order_release) == 1u) {
          std::atomic_thread_fence(std::memory_order_acquire);
    #ifndef NDEBUG
          destruction_started_ = true;
    #endif
          return true;
        }
        return false;
      }
    
    #ifndef NDEBUG
      void Adopt() {
        FML_DCHECK(adoption_required_);
        adoption_required_ = false;
      }
    #endif
    
     private:
      mutable std::atomic_uint_fast32_t ref_count_;
    
    #ifndef NDEBUG
      mutable bool adoption_required_;
      mutable bool destruction_started_;
    #endif
    
      FML_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase);
    };
    
    inline RefCountedThreadSafeBase::RefCountedThreadSafeBase()
        : ref_count_(1u)
    #ifndef NDEBUG
          ,
          adoption_required_(true),
          destruction_started_(false)
    #endif
    {
    }
    
    inline RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
    #ifndef NDEBUG
      FML_DCHECK(!adoption_required_);
      // Should only be destroyed as a result of |Release()|.
      FML_DCHECK(destruction_started_);
    #endif
    }
    
    

    クラス名から見ると,スレッドが安全であることを示し,ソースレベルからref_count_の原子操作を確保した.
    まず定義を見てみましょう.
    mutable std::atomic_uint_fast32_t ref_count_;
    

    atomic_uint_fast32_tは実際にはstd::atomicであり、現在のタイプが原子操作可能であることを示す.
    次に,AddRefの実装を見ると,ここではstd::memory_order_relaxedを用い,この動作の原子性のみを保証した.
    ref_count_.fetch_add(1u, std::memory_order_relaxed);
    

    同様に、Release関数では、動作の原子性を確保するための呼び出しも使用されます.
    ref_count_.fetch_sub(1u, std::memory_order_release);
    

    RefCountedThreadSafe
    ソースパス、[fml/memory/ref_counted.h]
    RefCountedThreadSafeは、参照カウントを使用してメモリを管理するすべてのクラスのベースクラスとして、テンプレートタイプによって実際のサブクラスのタイプを導入します.
    template 
    class RefCountedThreadSafe : public internal::RefCountedThreadSafeBase {
     public:
      void Release() const {
        if (internal::RefCountedThreadSafeBase::Release())
          delete static_cast(this);
      }
      
     protected:
      RefCountedThreadSafe() {}
      ~RefCountedThreadSafe() {}
    
     private:
    #ifndef NDEBUG
      template 
      friend RefPtr AdoptRef(U*);
      
      void Adopt() { internal::RefCountedThreadSafeBase::Adopt(); }
    #endif
    
      FML_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);
    };
    

    Release関数では最終的にdeleteがインスタンスのメモリを削除します.つまり、参照カウントが0になるとすぐにメモリが解放されます.
    ここでは、RefPtrで使用するシーンが表示される友元関数を定義します.
      template  friend RefPtr AdoptRef(U*);
    

    RefPtr
    ソースパス、[fml/memory/ref_ptr.h]RefPtrは参照ポインタの実装クラスであり,各種類の構造,付与,解析関数の挙動に注目する必要がある.構造関数、コピー構造関数はAddRefを必要とし、移行構造関数はAddRefを必要としない.構造関数にはReleaseが必要です.コピー付与関数AddRefの新しいオブジェクト、Releaseの長いオブジェクト.割り当て関数を転送するには、参照カウントを変更する必要はありません.
  • コンストラクタ、コピーコンストラクタは、AddRef関数
  • を呼び出す.
      template 
      explicit RefPtr(U* p) : ptr_(p) {
        if (ptr_)
          ptr_->AddRef();
      }
    
      RefPtr(const RefPtr& r) : ptr_(r.ptr_) {
        if (ptr_)
          ptr_->AddRef();
      }
    
  • 転送構造関数は、AddRef
  • を呼び出すことはありません.
      RefPtr(RefPtr&& r) : ptr_(r.ptr_) { r.ptr_ = nullptr; }
    
      template 
      RefPtr(RefPtr&& r) : ptr_(r.ptr_) {
        r.ptr_ = nullptr;   
      }
    
  • 構造関数は、Release
  • を呼び出します.
    ~RefPtr() {
        if (ptr_)
          ptr_->Release();
    }
    
  • コピー付与の実装、AddRefの新しいオブジェクト、Releaseの長いオブジェクト
  • .
      RefPtr& operator=(const RefPtr& r) {
        // Call |AddRef()| first so self-assignments work.
        if (r.ptr_)
          r.ptr_->AddRef();
        T* old_ptr = ptr_;
        ptr_ = r.ptr_;
        if (old_ptr)
          old_ptr->Release();
        return *this;
      }
    
      template 
      RefPtr& operator=(const RefPtr& r) {
        // Call |AddRef()| first so self-assignments work.
        if (r.ptr_)
          r.ptr_->AddRef();
        T* old_ptr = ptr_;
        ptr_ = r.ptr_;
        if (old_ptr)
          old_ptr->Release();
        return *this;
     }
    
  • 転送付与の実現は、参照カウントの変更
  • は存在しない.
      RefPtr& operator=(RefPtr&& r) {
        RefPtr(std::move(r)).swap(*this);
        return *this;
      }
    
      template 
      RefPtr& operator=(RefPtr&& r) {
        RefPtr(std::move(r)).swap(*this);
        return *this;
      }
    
    MakeRefCounted、これは私有化構造関数に提供されるクラスに対応するポインタを作成する関数であり、この関数はヘルプクラスを呼び出して実装される
    template 
    RefPtr MakeRefCounted(Args&&... args) {
      return internal::MakeRefCountedHelper::MakeRefCounted(
          std::forward(args)...);
    }
    
    template 
    class MakeRefCountedHelper final {
     public:
      template 
      static RefPtr MakeRefCounted(Args&&... args) {
        return AdoptRef(new T(std::forward(args)...));
      }
    };
    

    次に、実際のプライベート構造関数のクラスは、ヘルプクラスをメタクラスとして定義する必要があります.一般的には、次のマクロで完了します.
    #define FML_FRIEND_MAKE_REF_COUNTED(T) \
      friend class ::fml::internal::MakeRefCountedHelper
    

    弱ポインタ
    弱いポインタの設計は比較的面白く、WeakPtrFactoryを追加して本物のポインタを持ち、WeakPtrを取得するたびに、WeakPtrFlagが指すポインタのライフサイクルを表すためにWeakPtrをコピーします.
    WeakPtrFlag
    まずWeakPtrFlagを見てみましょう.RefCountedThreadSafeから受け継がれていますが、is_valid_の単純なタイプが付いていて、マークが有効かどうかです.
    class WeakPtrFlag : public fml::RefCountedThreadSafe {
     public:
      WeakPtrFlag() : is_valid_(true) {}
    
      ~WeakPtrFlag() {
        FML_DCHECK(!is_valid_);
      }
    
      bool is_valid() const { return is_valid_; }
    
      void Invalidate() {
        FML_DCHECK(is_valid_);
        is_valid_ = false;
      }
     private:
      bool is_valid_;
    
      FML_DISALLOW_COPY_AND_ASSIGN(WeakPtrFlag);
    };
    

    WeakPtrFactory
    さらにWeakPtrFactoryを見ると、最も重要な関数はGetWeakPtrであり、WeakPtrFactoryから任意の複数のWeakPtrを取得することができるが、インスタンスの参照カウントは増加しない.
    template 
    class WeakPtrFactory {
     public:
      explicit WeakPtrFactory(T* ptr)
          : ptr_(ptr), flag_(fml::MakeRefCounted<:internal::weakptrflag>()) {
        FML_DCHECK(ptr_);
      }
    
      ~WeakPtrFactory() {
        FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
        flag_->Invalidate();
      }
    
      WeakPtr GetWeakPtr() const {
        return WeakPtr(ptr_, flag_.Clone(), checker_);
      }
    
     private:
      T* const ptr_;
      fml::RefPtr<:internal::weakptrflag> flag_;
      DebugThreadChecker checker_;
    
      FML_DISALLOW_COPY_AND_ASSIGN(WeakPtrFactory);
    };
    

    WeakPtr WeakPtr自体の実装を見てみると、主に以下のいくつかの関数に注目すればよい.特に、このエンドコードを知るには、*thisは実際にexplicit operator bool() constを通じて変換される.
      explicit operator bool() const {
        FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
        return flag_ && flag_->is_valid();
      }
    
      T* get() const {
        FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
        return *this ? ptr_ : nullptr;
      }
    
      T& operator*() const {
        FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
        FML_DCHECK(*this);
        return *get();
      }
    
      T* operator->() const {
        FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
        FML_DCHECK(*this);
        return get();
      }
    

    スレッドのセキュリティ
    ソースパス、[fml/memory/thread_checker.h]ThreadCheckerは非常に簡単で、現在のスレッドであるかどうかを判断する関数を提供しているだけで、ソースコードに多くの場所で使用されていることがわかります.
    // Returns true if the current thread is the thread this object was created
    // on and false otherwise.
    bool IsCreationThreadCurrent() const {
      return !!pthread_equal(pthread_self(), self_);
    }
    
    #if !defined(NDEBUG) && false
    #define FML_DECLARE_THREAD_CHECKER(c) fml::ThreadChecker c
    #define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) \
      FML_DCHECK((c).IsCreationThreadCurrent())
    #else
    #define FML_DECLARE_THREAD_CHECKER(c)
    #define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) ((void)0)
    #endif
    

    まとめ
    Flutterエンジン設計のRefPtrWeakPtrは比較的コンパクトであり、両者の間の結合は標準ライブラリのshared_ptrweak_ptrほど大きくない.WeakPtrRefPtrへの補助であり、同時に不可欠であり、異なるスレッド間でデータを共有する場合、WeakPtrがより使用すべき方法である.WeakPtrWeakPtrFactoryのライフサイクルによってインスタンスのライフサイクルを管理します.一般的にはWeakPtrFactoryをインスタンスのクラスの下に配置します.これはインスタンスが自分で破棄されるとWeakPtrFactoryも破棄されます.すべての弱いポインタは自然にインスタンスを指さすことができません.実装方法と標準のweak_ptrは全く違いますが、さすがにいい案ですね.
    全体の実現方案も完璧にC++のRAI技術を利用した.
    シリーズ記事:
    Flutterエンジンのソースコード解読-FlutterがiOS上でどのように動作しているか
    参照先:
    std::atomic std::atomic::fetch_add std::memory_order