TVM:Object家族

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类也提供了多种函数接口,方便用户操作。
image

Object、ObjectPtr、ObjectRef关系

image

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,但我们可以用下图来帮助理解这两套继承体系:
image

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)

posted @ 2022-10-04 16:33  牛犁heart  阅读(211)  评论(0编辑  收藏  举报