Object.h概述
命名空间:
TVM::runtime
文件中包含的结构:
- 1.结构体TypeIndex
- 2.类Object
- 3.类ObjectPtr
- 4.类ObjectRef
- 5.结构体ObjectPtrHash
- 6.结构体ObjectPtrEqual
- 7.宏
结构体TypeIndex
该结构体内部仅包含一个枚举类型,通过下面的代码可以看到,Object类存在一个TypeIndex类型的成员变量。
/*!
* \brief Namespace for the list of type index.
* \note Use struct so that we have to use TypeIndex::ENumName to refer to
* the constant, but still able to use enum.
*/
struct TypeIndex {
enum {
/*! \brief Root object type. */
kRoot = 0,
// Standard static index assignments,
// Frontends can take benefit of these constants.
/*! \brief runtime::Module. */
kRuntimeModule = 1,
/*! \brief runtime::NDArray. */
kRuntimeNDArray = 2,
/*! \brief runtime::String. */
kRuntimeString = 3,
/*! \brief runtime::Array. */
kRuntimeArray = 4,
/*! \brief runtime::Map. */
kRuntimeMap = 5,
/*! \brief runtime::ShapeTuple. */
kRuntimeShapeTuple = 6,
/*! \brief runtime::PackedFunc. */
kRuntimePackedFunc = 7,
// static assignments that may subject to change.
kRuntimeClosure,
kRuntimeADT,
kStaticIndexEnd,
/*! \brief Type index is allocated during runtime. */
kDynamic = kStaticIndexEnd
};
}; // namespace TypeIndex
为什么要将enum类型放在一个struct结构体中呢?
避免命名冲突
包含在struct的{}中,相当于在一个命名空间下,这样已经能够避免命名冲突的问题,而且使用起来比较方便TypeIndex::ENumName。同样也可以将其放在类中,但由于enum中的类型信息本就是对外部开放的,因此struct相较于class更为简洁方便。
参考:C++ enum枚举型
在TypeIndex中可以看到,共有四种object type
:
- 1.kRoot:root object type
- 2.前端可以获取的常量:
1.runtime::Module
2.runtime::NDArray
3.runtime::String
4.runtime::Array
5.runtime::Map
6.runtime::ShapeTuple
7.runtime::PackedFunc
- 3.static assignments that may subject to change 可能发生变化的静态分配
- 4.Type index is allocated during runtime.运行时分配的Type Index
kDynamic
Class Object
成员变量:
tvm::runtime::Object
+ _type_key
+ _type_final
+ _type_child_slots
+ _type_child_slots_can_overflow
+ _type_has_method_visit_attrs
+ _type_has_method_sequal_reduce
+ _type_has_method_shash_reduce
+ _type_index
# type_index_
# ref_counter_
# deleter_
可以看到,除了类的回收器deleter_和引用计数ref_counter外,其余成员变量都是关于type_index的标志位或子类插槽(slots)等信息。由于作为多有容器对象的基类(base class of all object containers.)所以主要提供了一系列的操作接口。但最重要的成员变量是type_index_。
成员函数:
成员函数主要是类相关的构造函数,拷贝构造、拷贝复制、move构造、move赋值函数
其次是type_index_相关的函数
剩下的为引用计数相关函数。
类相关
// 构造函数
// Default constructor and copy constructor
Object() {}
// 拷贝构造
// Override the copy and assign constructors to do nothing.
// This is to make sure only contents, but not deleter and ref_counter
// are copied when a child class copies itself.
// This will enable us to use make_object<ObjectClass>(*obj_ptr)
// to copy an existing object.
Object(const Object& other) { // NOLINT(*)
}
// 移动构造(move)
Object(Object&& other) { // NOLINT(*)
}
// 拷贝赋值
Object& operator=(const Object& other) { // NOLINT(*)
return *this;
}
// 移动构造(move)
Object& operator=(Object&& other) { // NOLINT(*)
return *this;
}
// 析构器(FDeleter函数指针)
typedef void (*FDeleter)(Object* self);
type_index_相关
uint32_t type_index() const { return type_index_; }
std::string GetTypeKey() const { return TypeIndex2Key(type_index_); }
std::string GetTypeKey() const { return TypeIndex2Key(type_index_); }
static std::string TypeIndex2Key(uint32_t tindex);
static size_t TypeIndex2KeyHash(uint32_t tindex);
static uint32_t TypeKey2Index(const std::string& key);
template <typename TargetType>
inline bool IsInstance() const;
static uint32_t _GetOrAllocRuntimeTypeIndex() { return TypeIndex::kRoot; }
static uint32_t RuntimeTypeIndex() { return TypeIndex::kRoot; }
static uint32_t GetOrAllocRuntimeTypeIndex(const std::string& key, uint32_t static_tindex,
uint32_t parent_tindex, uint32_t type_child_slots,
bool type_child_slots_can_overflow);
继承、引用计数相关
void IncRef();
void DecRef();
int use_count() const;
DerivedFrom(uint32_t parent_tindex) const;
友元类
template <typename>
friend class ObjAllocatorBase;
template <typename>
friend class ObjectPtr;
friend class TVMRetValue;
friend class ObjectInternal;
class ObjectPtr
注释中将这个类描述为 A custom smart pointer for Object.一个用户自定义的指向Object类的智能指针。
其中最重要的成员变量和成员函数为:
private:
/*! \brief internal pointer field */
Object* data_{nullptr};
/*!
* \brief constructor from Object
* \param data The data pointer
*/
explicit ObjectPtr(Object* data) : data_(data) {
if (data != nullptr) {
data_->IncRef();
}
}
可以看到,该类的成员变量中包含一个指向Object对象的指针 data_,以及一个私有的构造函数,接收一个指向Object对象的指针,初始化data_并增加 data_的引用计数。
该类中的其余函数主要是类的多种构造函数,以及重载了多个运算符,供用户将该类的对象作为指针使用。
总结:因此可以将ObjectPtr看作是一个指向Object对象的指针的包装器,其本质依旧是Object类型的指针。经过包装后我们可以根据其提供的各种接口,更加方便地对指针进行操作。值得注意的是,该类是一个模板类,因此该类的对象包含一个描述Object对象的类型。
class ObjectRef
注释中将该类描述为 Base class of all object reference所有对象引用的基类。
其中最重要的成员变量和成员函数为:
protected:
/*! \brief Internal pointer that backs the reference. */
ObjectPtr<Object> data_;
该类中包含了一个ObjectPtr
的对象。因此可以看做是ObjectPtr
的包装器。除该对象外,ObjectRef类也提供了多种函数接口,方便用户操作。
Object、ObjectPtr、ObjectRef关系
ObjectPtr
类是封装的、指向Object类的一个智能指针类
/*!
* \brief A custom smart pointer for Object.
* \tparam T the content data type.
* \sa make_object
*/
template <typename T>
class ObjectPtr {
public:
ObjectPtr() {}
ObjectPtr(std::nullptr_t) {} // NOLINT(*)
ObjectPtr(const ObjectPtr<T>& other) // NOLINT(*)
: ObjectPtr(other.data_) {}
template <typename U>
ObjectPtr(const ObjectPtr<U>& other) // NOLINT(*)
: ObjectPtr(other.data_) {
static_assert(std::is_base_of<T, U>::value,
"can only assign of child class ObjectPtr to parent");
}
......
// 关键数据成员,一个指向Object对象的指针
Object *data_{nullptr};
// 几个关键的运算符重载接口,用于获取指向Object的指针或者引用
T* get() const { return static_cast<T*>(data_); }
T* operator->() const { return get(); }
T& operator*() const { return *get(); }
包含了智能指针应该有的:拷贝构造、移动构造、swap函数、运算符重载等的实现,可参考C++智能指针
使用自定义的智能指针管理Object对象生命周期,便于管理,防止内存泄漏
ObjectRef
类为所有对象引用的基类( Base class of all object reference)
它的关键数据成员和几个运算符重载接口如下:
// 关键数据成员
ObjectPtr<Object> data_;
// 关键运算符重载接口
const Object* get() const { return data_.get(); }
const Object* operator->() const { return get(); }
在具体的使用中,Object
机制有两套继承体系,一套继承自Object
,用于表示实际的类, 一套继承自ObjectRef
,它就像是指向实际类对象的智能指针,用于操作实际的类对象,虽然实际上没有shared_ptr,但我们可以用下图来帮助理解这两套继承体系:
Object的所有子类基本上都遵循了 NameNode 继承 Object,Name继承 ObjectRef;,例如:
/*! \brief Base node of all statements. */
class StmtNode : public Object
/*! \brief Container of all statements */
class Stmt : public ObjectRef
而 ObjectPtr作为模板类,记录有指向Object类对象的类型,在使用过程中,会出现指针的向下转型(downcast)以及类型转换等操作,因此其作用主要做转型和检查。
使用场景:对Object的子类做修改
SumExpr ToSumExpr(PrimExpr expr) {
// Ref对象调用.as<>()函数返回一个类型为<SumExprNode>的Ptr
if (const auto* op = expr.as<SumExprNode>()) {
// 通过Ptr构造Ref
return GetRef<SumExpr>(op);
}
// 通过make_object创建<>类型对象,并返回指向该对象的指针。
ObjectPtr<SumExprNode> n = make_object<SumExprNode>();
n->dtype = expr.dtype();
// Ref对象调用.as<>()函数返回一个类型为<>的Ptr
if (const auto* op = expr.as<IntImmNode>()) {
// 通过Ref对内容进行操作
n->base = op->value;
// 通过Ptr构造Ref对象
return SumExpr(n);
} else {
n->args.emplace_back(ToSplitExpr(expr));
return SumExpr(n);
}
}
ObjectPtr:用于记录类型,类型检查(.as()),对Object实体内容进行操作,还可以作为构造引用的参数。
Object:为对象实体。
ObjectRef:作为函数间传递和处理的形式。
如果拿Object、ObjectPtr、ObjectRef
这三个类和shared_ptr
类比的话:
-
Object
相当于控制块,可以通过引用计数ref_counter_
来控制对象的生命周期,对象的析构函数也可以通过delete_这个函数指针指定 -
Object
的子类的除去Object基类的部分相当于数据块,里面保存有类的真实数据 -
ObjectRef
就像是shared_ptr这个wrapper,自身不包含实际数据,但是可以操作实际的数据 -
ObjectPtr
的作用在使用的角度有点类似ObjectRef,不同的是数据类型,ObjectPtr<T>
是一个模板
参考:
深入理解TVM:Object家族(二)
TVM源码品读:万物基石——Object类(1)