Flutterエンジンソース解読-メモリ管理編
サマリ
本文は主にFlutterエンジンの中のメモリ管理に関するソースコードを解読し、Flutterエンジンのコアコードの多くはC++で書かれており、メモリ管理は主に引用カウントであり、C++言語自体の柔軟性と結びつけて、少ないコードでObjective-C言語に類似したARCのメモリ管理能力を実現した.
開始前
C++コードでは多くのマクロに遭遇するのが一般的ですが、これらのマクロの意味を理解するには、その背後にあるソースコードを参照する必要があります.メモリモデルに関連するソースコードで出会ったマクロについて、冒頭に簡単な紹介をします.[fluter/engine/fml/macros.h]
マクロ名自体が最高のコメントで、C++ではdeleteでcopy、assign、moveなどの関数を無効にします.
ソース構造
重要な概念
次のいくつかの重要な概念に注目する必要があります.参照ポインタ、参照カウントの実装は、参照ポインタによってインスタンスを指し、インスタンスはメモリの解放を管理するために参照をカウントする. 弱いポインタは、メモリへの参照が参照カウントを増加させないことを示す弱いポインタが必要です. Thread Safeは、メモリ管理に関する面で、ポインタがスレッドの安全な であるかどうかに注目する必要があります.
リファレンスポインタ
参照ポインタは、
RefCountedThreadSafeBase
ソースパス、[fml/memory/ref_counted_internal.h]
このクラスは、参照カウントを使用するすべてのクラスの最初のベースクラスとして存在します.主に、参照カウントの基本的な3つの能力を提供します. ref_count_,初期化時のデフォルトは1 u です. AddRefは、参照カウントを増加するとともに、動作の原子性 を確保する. Releaseは、参照カウントを低減するとともに、動作の原子性 を確保する.
クラス名から見ると,スレッドが安全であることを示し,ソースレベルから
まず定義を見てみましょう.
atomic_uint_fast32_tは実際には
次に,AddRefの実装を見ると,ここでは
同様に、Release関数では、動作の原子性を確保するための呼び出しも使用されます.
RefCountedThreadSafe
ソースパス、[fml/memory/ref_counted.h]
RefCountedThreadSafeは、参照カウントを使用してメモリを管理するすべてのクラスのベースクラスとして、テンプレートタイプによって実際のサブクラスのタイプを導入します.
Release関数では最終的にdeleteがインスタンスのメモリを削除します.つまり、参照カウントが0になるとすぐにメモリが解放されます.
ここでは、RefPtrで使用するシーンが表示される友元関数を定義します.
RefPtr
ソースパス、[fml/memory/ref_ptr.h]コンストラクタ、コピーコンストラクタは、 を呼び出す.転送構造関数は、 を呼び出すことはありません.構造関数は、 を呼び出します.コピー付与の実装、 .転送付与の実現は、参照カウントの変更 は存在しない.
次に、実際のプライベート構造関数のクラスは、ヘルプクラスをメタクラスとして定義する必要があります.一般的には、次のマクロで完了します.
弱ポインタ
弱いポインタの設計は比較的面白く、
WeakPtrFlag
まず
WeakPtrFactory
さらに
WeakPtr
スレッドのセキュリティ
ソースパス、[fml/memory/thread_checker.h]
まとめ
Flutterエンジン設計の
全体の実現方案も完璧にC++のRAI技術を利用した.
シリーズ記事:
Flutterエンジンのソースコード解読-FlutterがiOS上でどのように動作しているか
参照先:
std::atomic std::atomic::fetch_add std::memory_order
本文は主に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
重要な概念
次のいくつかの重要な概念に注目する必要があります.
リファレンスポインタ
参照ポインタは、
RefCountedThreadSafe
を継承したクラスのインスタンスを指し、参照ポインタ自体のスタックアウトスタックによってクラスインスタンスの参照カウントの増減を実現することができる.RefCountedThreadSafeBase
ソースパス、[fml/memory/ref_counted_internal.h]
このクラスは、参照カウントを使用するすべてのクラスの最初のベースクラスとして存在します.主に、参照カウントの基本的な3つの能力を提供します.
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エンジン設計の
RefPtr
とWeakPtr
は比較的コンパクトであり、両者の間の結合は標準ライブラリのshared_ptr
とweak_ptr
ほど大きくない.WeakPtr
はRefPtr
への補助であり、同時に不可欠であり、異なるスレッド間でデータを共有する場合、WeakPtr
がより使用すべき方法である.WeakPtr
はWeakPtrFactory
のライフサイクルによってインスタンスのライフサイクルを管理します.一般的にはWeakPtrFactory
をインスタンスのクラスの下に配置します.これはインスタンスが自分で破棄されるとWeakPtrFactory
も破棄されます.すべての弱いポインタは自然にインスタンスを指さすことができません.実装方法と標準のweak_ptrは全く違いますが、さすがにいい案ですね.全体の実現方案も完璧にC++のRAI技術を利用した.
シリーズ記事:
Flutterエンジンのソースコード解読-FlutterがiOS上でどのように動作しているか
参照先:
std::atomic std::atomic::fetch_add std::memory_order