本文档介绍了有关构建编译时集合的积极讨论 使用 C++ 编写大量对象。以下使用场景示例展示了 集合很有用:
- StringRef - 一种支持构建字符串编译时集合的类型 具有关联的唯一数字 ID 的标签,以便进行跟踪。
- LockClass - 一种支持构建编译时状态集合的类型 对象以进行运行时锁验证。
以下部分讨论了每种用例的常见和独特要求, 介绍目前在实现方面面临的挑战,以及建议的解决方案。
StringRef
StringRef 是一种实现字符串引用概念的类型。字符串 reference 是从数字 ID 到字符串的映射。使用 映射可以更经济地利用跟踪缓冲区:(id, string) 对在跟踪会话中发出一次,则后续事件可能会参考 按 ID 映射到字符串,而不是以内嵌方式包含完整的字符序列。
以下是 StringRef
的实际应用示例:
#include <lib/ktrace.h>
template <typename Op, typename... Args>
inline DoSomething(Op&& op, Args&&... args) {
ktrace_probe(TraceAlways, TraceContext::Thread, "DoSomething"_stringref);
// ...
}
此处,字符串字面量运算符 _stringref
会返回 StringRef
的实例
它提供了将字符串“DoSomething”数字 ID
跟踪函数所使用的名称
要求
- 在发出任何命令之前,在每个跟踪会话中最多发出每个(ID、字符串)映射一次 引用该 ID 的跟踪事件。理想情况下,完整的映射集是 在跟踪会话开始时一次性发出,以避免 在对时间敏感的代码过程中发出映射,但是,这并不 一项硬性要求
- 密集 ID 空间是必要的,这样下游处理代码就可以使用 线性预分配数组或向量来实现 ID 到字符串查找; 但这不是硬性要求
- 某个用于唯一重复字符串引用的方法,因为跟踪调用必须是 支持在模板以及内联函数和方法中使用。
当前实现
以下是当前 StringRef
实现的概要。
struct StringRef {
static constexpr int kInvalidId = -1;
const char* string{nullptr};
ktl::atomic<int> id{kInvalidId};
StringRef* next{nullptr};
int GetId() {
const int ref_id = id.load(ktl::memory_order_relaxed);
return ref_id == kInvalidId ? Register(this) : ref_id;
}
// Returns the head of the global string ref linked list.
static StringRef* head() { return head_.load(ktl::memory_order_acquire); }
private:
// Registers a string ref in the global linked list.
static int Register(StringRef* string_ref);
static ktl::atomic<int> id_counter_;
static ktl::atomic<StringRef*> head_;
};
// Returns an instance of StringRef that corresponds to the given string
// literal.
template <typename T, T... chars>
inline StringRef* operator""_stringref() {
static const char storage[] = {chars..., '\0'};
static StringRef string_ref{storage};
return &string_ref;
}
LockClass
LockClass 是一种类型,用于捕获所有 锁的实例(例如,如果是结构体/类成员,则为包含的类型; 底层锁基元的类型以及描述其行为的标志)。通过 运行时锁定验证器使用 LockClass 类型来确定排序 规则会应用于每个锁,并用于确定每个锁的类别跟踪结构, 用于记录排序观察结果。
以下是 LockClass 实际应用的简化示例:
struct Foo {
LockClass<Foo, fbl::Mutex> lock;
// ...
};
struct Bar {
LockClass<Bar, fbl::Mutex> lock;
};
要求
- 能够针对 LockClass 的所有实例迭代跟踪状态, 周期检测和错误报告目的。
- 某个唯一重复跟踪状态的方法, LockClass 可能在多个编译单元中可见,具体取决于 包含类型(例如 Foo 和 Bar)。
- 密集 ID 空间是必要的,这样下游处理代码就可以简化 id 存储,但这不是硬性要求。
当前实现
以下是 LockClass 的简化实现:
template <typename ContainingType, typename LockType>
class LockClass {
// ...
private:
static LockClassState lock_class_state_;
};
每次 LockClass
实例化都会创建一个 LockClassState
的唯一实例
跟踪与课程锁定相关的在线锁定顺序观察
(ContainingType
,LockType
)。LockClassState
的当前实现
构建一个包含全局 ctor 中所有实例的链接列表,以支持
迭代要求。
编译时数组解决方案
满足这两种类型要求的一种方法是构建编译时 使用 COMDAT 部分和组删除重复项的静态实例的数组。这个 完全不需要在初始化时构建对象链接列表,或者 运行时,并且支持每种类型的所有要求。
例如:
// Defined in the linker script mark the beginning and end of the section:
// .data.lock_class_state_table.
extern "C" LockClassState __start_lock_class_state_table[];
extern "C" LockClassState __end_lock_class_state_table[];
template <typename ContainingType, typename LockClass>
class LockClass {
// ...
private:
static LockClassState lock_class_state_ __SECTION(".data.lock_class_state_table");
};
// Defined in the linker script to make the beginning and end of the section:
// .rodata.string_ref_table.
extern "C" StringRef __start_string_ref_table[];
extern "C" StringRef __end_string_ref_table[];
struct StringRef {
const char* const string;
size_t GetId() const {
return static_cast<size_t>(this - __start_string_ref_table);
}
};
template <typename T, T... chars>
inline StringRef* operator""_stringref() {
static const char storage[] = {chars..., '\0'};
static StringRef string_ref __SECTION(".rodata.string_ref_table") {storage};
return &string_ref;
}
这种方法的挑战在于,GCC 无法正确处理 部分属性。不过, Clang 确实能正确处理这些类型的区段属性。