RFC-0047:表格

RFC-0047:資料表
狀態已接受
區域
  • FIDL
說明

在 FIDL 語言中新增向前與向後相容的複合資料類型的機制。

作者
提交日期 (年/月)2018-07-27
審查日期 (年/月)2018-09-20

摘要

在 FIDL 語言中新增向前與向後相容的複合資料類型的機制。

與其他 RFC 的關係

本 RFC 稍後修訂為:

提振精神

FIDL 結構並未提供可隨時間修改結構定義的機制。資料表與結構類似,但每個欄位都要加入序數,以允許結構演變:

  • 可使用現有程式碼新增及忽略新欄位
  • 較新的程式碼可略過舊 (已淘汰) 的欄位

資料表會比結構更複雜,因此處理表格速度會較慢,序列化作業會使用更多空間。因此,建議您保持結構體原樣,並導入新事物。

此外,使用可變動的結構定義也能提供 FIDL 的變化版本,該變數能夠合理地在磁碟或網路中進行序列化。

表格範例可能如下所示:

table Station {
    1: string name;
    3: bool encrypted;
    2: uint32 channel;
};

設計

原文語言

table_declaration 新增至 FIDL 文法:

declaration = const-declaration | enum-declaration | interface-declaration |
              struct-declaration | union-declaration | table-declaration ;

table-declaration = ( attribute-list ) , "table" , IDENTIFIER , "{" , ( table-field , ";" )* , "}" ;

table-field = table-field-ordinal , table-field-declaration ;

table-field-ordinal = ordinal , ":" ;

table-field-declaration = struct-field | "reserved" ;

注意事項:

  • 序數的起始值必須是 1,且序數不得允許間隔 (如果最大普通為 7,則所有 1、2、3、4、5、6、7 都必須存在)。
  • 每個欄位都無法聲明相同的序數。
  • 檢查序數衝突後,編譯器會捨棄「保留」欄位。 這允許註解,指出在舊版資料表的部分版本中使用了某個欄位,但該欄位已遭捨棄,這樣未來的修訂版本就不會意外重複使用該序數。
  • 資料表不得包含可為空值的欄位。

凡是目前可在該語言中使用結構體的任何位置,都能使用資料表。特別是:

  • 結構體和聯集可包含資料表
  • 資料表可以包含結構體和聯集
  • 介面引數可以是資料表
  • 資料表可設為

電匯格式

資料表會儲存為封裝的 vector<envelope>,向量的每個元素都是一個序數元素 (因此索引 0 為序數 1,索引 1 為序數 2 等)。我們會在下文說明信封。

表格只能儲存截至最後一張清單的信封,也就是最大集序。這可確保標準表示法。舉例來說,如未設定任何欄位,正確的編碼就會為空白向量。如果資料表的欄位為序數 5,但欄位只設為序數 3,則正確的編碼是 3 個信封的向量。

信封

envelope 會儲存變數大小、未經解讀的酬載。酬載可能包含任意數量的位元組和處理。這個機構允許將一則 FIDL 訊息封裝在另一個 FIDL 訊息之間。

信封會以包含下列項目的記錄儲存:

  • num_bytes:信封中 32 位元未簽署位元組數,如果信封為空值,則一律為 8 的倍數
  • num_handles:信封中的 32 位元未簽署控制代碼數量,如果信封為空值,則必須為零
  • data:64 位元狀態指標或指向外界資料的指標

data 欄位有兩個不同行為。

編碼以進行傳輸時,data 表示內容存在:

  • FIDL_ALLOC_ABSENT (全部 0 位元):信封為空值
  • FIDL_ALLOC_PRESENT (全部 1 位元):信封為非空值,資料是下一個外線物件

解碼以供使用時,data 是內容的指標。

  • 0:信封為空值
  • <valid pointer>:信封非空值,資料位於指定記憶體位址

針對把手,信封會在內容結束後立即保留把手儲存。 解碼時,假設 data 並非空值,data 會指向資料的第一個位元組。

信封會填充到下一個 8 位元組物件對齊方式 (實務上表示沒有額外的邊框間距)。

語言繫結

資料表不會產生結構等資料欄位,而是要為每個欄位產生一組方法。以 C++ 為例,我們會:

class SampleTable {
 public:
  // For "1: int32 foo;"
  const int32* foo();         // getter, returns nullptr if foo not present
  bool has_foo();             // presence check
  int32* mutable_foo();       // mutable getter, forces a default value if not set
  void set_foo(int32 x);      // set value
  void clear_foo();           // remove from structure
  optional<int32> take_foo(); // get foo if present, remove from structure
};

格式指南

我該使用結構體還是表格?

結構和資料表提供語意上的相似度,選擇偏好的方式似乎很複雜。

對於非常高階的處理序間通訊 (IPC) 或永久儲存空間,序列化效能不需擔心:

  • 資料表提供一些前瞻相容性,因此提供未來驗證元素:偏好大多數概念。
  • 僅針對未來非常不可能改變的概念 (例如 struct Vec3 { float x; float y; float z }Ipv4Address) 利用結構體的效能優勢。

序列化效能成為覆寫的疑慮 (例如裝置驅動程式的資料路徑常見) 後,我們就可以開始優先選擇結構體,並且仰賴為介面新增新方法,因應日後的異動。

回溯相容性

本次異動包含兩個關鍵字:tablereserved。 沒有任何回溯相容性問題

效能

您可選擇啟用這項功能,如果不使用,就不會影響處理序間通訊 (IPC) 成效。 我們預期成效差異落在可評估的雜訊範圍內。

安全性

對安全性沒有影響。

測試

需要每個語言繫結的其他測試,以及對 fidlc 進行測試。

適合使用資料表的 echo 套件擴充版本。

對資料表編碼/解碼加入模糊效果很有用,因為剖析時往往相當複雜。

缺點、替代方案和未知

在這個領域中,我們會回答以下兩個重大問題:

  • 欄位識別的序數與字串 (一般強制運送結構定義)
  • 如果序數:每個訊息的稀疏與稠密空格

以聯集向量形式的資料表

提議將 table 視為 vector<union>。這帶來兩個問題:

  • 採用這種格式的讀取器最有效率的實作方式,不能低於讀者實作所建議資料表格式最有效率的實作方式,因此會永久限制峰值效能。
  • 不提供任何線路相容性保證!向量必需包含長度和主體,因此聯集無法透過線結轉換成具有提案的表格 (我們希望縮短轉換難度的次數)。

而藉由引進信封基本功能,我們可以用相同的方式寫下相容性保證的理由並推理...在表格和擴充聯集 (開發中) 之間分享一些棘手的實作細節,並且能夠以幾乎免費的形式公開實用的基本內容。

序數對比字串

使用序數需在編譯時具有結構定義,但可以提升實作效率 (字串處理速度總是比整數處理慢)。由於 FIDL 在編譯期間已需要具備結構定義,因此希望這裡的字串應該不會有爭議。

密集封裝與稀疏封裝

密集與稀疏空間的問題可能會比較具有爭議性。現有的練習分為兩個營地:

  • Thrift 和 Protobuf 使用稀疏空間 — 欄位可以為任何序數值。
  • FlatBuffers 和 Cap'n'Proto 使用密集序數 — 欄位必須為連續序數。

Protobuf 傳輸格式與一般可剖析為固定大小結構的 Protobuf 實作搭配使用時會出現錯誤,其中已解碼的記憶體使用的記憶體量與傳輸的位元組數沒有關聯。如要查看這項資訊,請設想含有 10, 000 (選用) int64 個欄位的訊息。傳送者可以選擇只傳送一封郵件,產生只有幾位元組的線,但在記憶體中卻幾乎是 100 KB。藉由傳送其中許多項目做為 RPC,往往很容易就會遏止流程控管的實作作業,並造成 OOM。

稀疏序數的替代實作策略 (如先前的對話中的建議) 將傳送已排序的 (ordinal, value) 元元組。選擇就地解碼的實作方式,必須依賴透過二進位檔搜尋來尋找序數。這可避免之前提到的流程控制錯誤,但隨著我們執行數量可能驚人的二元搜尋時,可能在執行階段導入一些重大的效率問題。

Cap'n'Proto 實作了一個非常複雜的演算法來處理序數,我們想要避免這件事不再複雜。

FlatBuffers 的傳輸格式與本文件中的提議非常類似:利用稠密的序線空間提供單一陣列查詢,找出特定欄位的資料 (或為空值)。

先前的圖片和參考資料

  • FlatBuffers 演算法類似於此演算法,但在這裡經過調整,以便更符合 FIDL 慣例。
  • Protobuf (我們相信) 最初廣受歡迎的是序數/值表示法,而 Protobuf (我們相信) 過去幾年來也廣為採用該配置。
  • Cap'n'Proto 和 Thrift 各具特色。