本文件將探討如何主動討論在 C++ 中建構物件的編譯時間集合。下列使用案例是編譯時間集合實用的用途:
- StringRef - 一種類型,支援建構具有相關不重複數字 ID 的字串標籤編譯時間集合,以供追蹤之用。
- LockClass - 支援建構狀態物件的編譯時間集合,以便驗證執行階段鎖定。
以下各節將討論各種用途的常見需求、實作方式目前的挑戰,以及建議的解決方案。
字串參照
StringRef 是一種實作字串參照概念的型別。字串參照是從數字 ID 對應至字元字串。使用對應會提高追蹤緩衝區的成本效益:一個 (ID、字串) 組合會在追蹤工作階段中發出一次,然後後續事件可能會依 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 所有例項化的追蹤狀態,以用於週期偵測和錯誤回報。
- 建立不重複追蹤狀態的一些方法,因為視包含類型 (例如 Foo 和 Bar) 的使用方式而定,LockClass 的例項化可能是來自多個編譯單位。
- 建議您使用密集的 ID 空間,以便下用串流處理程式碼簡化 ID 儲存空間,但這並非硬性規定。
目前導入作業
以下是 LockClass 的簡化實作:
template <typename ContainingType, typename LockType>
class LockClass {
// ...
private:
static LockClassState lock_class_state_;
};
每次建立 LockClass
執行個體時,系統都會建立一個不重複的 LockClassState
執行個體,用於追蹤與類別鎖定 (ContainingType
、LockType
) 相關的線上鎖定訂單觀察結果。目前的 LockClassState
實作做法是在全域導數中建構所有執行個體的連結清單,以支援疊代要求。
編譯時間陣列解決方案
滿足這兩種類型需求的其中一種方式,是使用 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 會正確處理這些類型的區段屬性。