RFC-0047:資料表 | |
---|---|
狀態 | 已接受 |
領域 |
|
說明 | 在 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 全都必須包含。
- 沒有任何兩個欄位可聲明相同序數。
- 「預訂」] 欄位會在檢查序數衝突之後,遭到編譯器捨棄。 這個 API 可以加上註解,讓部分舊版資料表使用欄位, 以避免日後修訂時意外重複使用該序詞。
- 資料表禁止可為空值的欄位。
凡是目前可在該語言中使用結構體的位置,都可以使用表格。 尤其是:
- 結構體和聯集可以包含資料表
- 資料表可以包含結構體和聯集
- 介面引數可以是資料表
- 也能將資料表設為
傳輸格式
資料表會以封裝的 vector<envelope>
形式儲存。
向量的每個元素都是一個序數元素 (因此索引 0 代表序數 1)
索引 1 是序數 2,以此類推)。
下文說明信封。
表格只能儲存最大信封至最後一個存在的信封 (也就是最大序數組合)。 這可確保以標準表示法呈現。舉例來說,如果沒有設定欄位, 編碼是空白向量 如果資料表的欄位設為序數 5,但欄位的值設為序數 3,那麼正確 編碼是 3 個信封的向量
信封
envelope
會儲存變數大小的未解譯酬載。
酬載可能包含任意數量的位元組和控制代碼。
這個機構允許封裝另一封郵件中的 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
)。
序列化效能會成為覆寫問題 (這種情況常見於 為裝置驅動程式的資料路徑) 傳回後,我們就只偏好結構體 就必須在介面中加入新方法,以便因應日後的變更。
回溯相容性
本次異動包含兩個關鍵字:table
和 reserved
。
沒有回溯相容性疑慮。
成效
您可自行選擇使用這項功能,如果不使用,則不會影響 IPC 效能。 我們預期效能差異會是可測量的雜訊中。
安全性
不會影響安全性。
測試
您需要為每個語言繫結進行額外的測試,以及針對 fidlc
進行測試。
請擴充 echo 套件以使用資料表。
為資料表編碼/解碼加入模糊化器的做法,或許更有幫助 — 常會遇到棘手情況
缺點、替代方案和未知
這個領域還有兩個重要問題需要回答:
- 欄位識別字串和字串 (序數強制傳送結構定義)
- 如果是序數:每則訊息的稀疏度與密集序數空間
以聯集向量形式的資料表
建議將 table
視為vector<union>
。
這會造成兩個問題:
- 導入這種格式的閱讀器效率必須降低 比起建議的資料表格式,讀取器最有效率的閱讀器實作方式 ,因此對峰值效能永久限制了。
- 兩者無法保證任何線路相容性! 向量必須包含長度和主體,因此聯集必須 無法將提案文件轉換為表格 (還有 我們不希望轉型成花費的時間)
相反地,只要介紹信封基式,我們就能寫下訊息 相容性保證以相同方式說明... 資料表和可延伸聯集 (開發中) 之間有一些棘手的實作細節,
序數對比字串
使用泛型需要在編譯時具備結構定義。 但可讓實作方式更有效率 (字串處理方式一律會 低於整數處理速度)。 由於 FIDL 在編譯期間已需要具備結構定義,因此希望 以上字串的序數沒有爭議
稠密與稀疏型封裝
稠密與稀疏式空間的問題可能較具爭議性。 現有的實作中包含兩個營隊:
- Thrift 和 Protobuf 使用稀疏序數 — 欄位可為任何序數值。
- FlatBuffers 和 Cap'n'Proto 使用密集的序數空間 — 欄位必須連續表示序數。
Protobuf 線格式 (搭配可剖析的一般 Protobuf 實作時)
到固定大小的結構體中,發生錯誤:已解碼記憶體使用的記憶體容量
與透過線傳輸的位元組數無關。
如要查看此內容,請設想含有 10, 000 (選用) int64
s 欄位的訊息。
寄件者可選擇只傳送一封郵件,導致郵件只有少許位元組
但記憶體容量將近 100 KB
以這些 RPC 形式傳送許多作業,通常很容易就能阻止流量控管
實作並造成 OOM
稀疏序數的替代實作策略 (如先前的對話所述),
觸發條件會傳送 (ordinal, value)
個元組的已排序陣列。
選擇就地解碼的導入作業必須依賴資料二進位搜尋
來尋找序數
它可避免了先前提到的錯誤流量控制錯誤,但會引入可能相當大的內容
效率不彰,因為我們所執行的二元搜尋次數可能相當可觀。
Cap'n'Proto 實作了非常複雜的演算法來處理日常事務 這些情況都不複雜,因此我們想避免這類複雜作業
FlatBuffers 的線路格式與本文件所述非常相似: 利用密集序數空間提供單一陣列查詢,以找出 欄位資料 (或為空值)。
既有藝術品和參考資料
- FlatBuffers 演算法與這個演算法類似,但這裡已進行調整 配合 FIDL 慣例
- Protobuf (我們相信) 最初是廣受歡迎的序數/值表示法 多年來,這項計劃的可靠性無庸置疑。
- 上述的《Cap'n'Proto》和《 Thrift》皆提供小扭曲。