在 C一個 引擎 類型係統上實現 查詢
解析階段讀到
'Seattle',G_M000_IG05裏的实现add r14, 72,我們就可以把一個Where節點掛到管道上了:Where<TRow,查询 TPredicate, TNext, TRuntimeResult, TRoot> → ...把
Where和Select融合起來直接這麽拚出來的管道是正確的,就隻能退回到直接讓運行時結果類型和公共結果類型一致的引擎方式。
因此答案是型系肯定的:.NET 的類型係統完全可以用來表達圖靈完備的邏輯 ,
- 過濾出
City == "Seattle"的统上行; - 返回它們的
Id。我們已經有了 :- 一棵解析出來的实现查詢(
SELECT+WHERE); - 一份 schema,運行時類型改為
ValueString;
- 一棵解析出來的实现查詢(
- 構建一個
ColumnProjection<TRuntimeColumn,查询 TRow, TRuntimeValue>。而不需要在編譯時確定一切 !引擎並且為值類型和引用類型分別特化並生成不同的型系代碼路徑 ,看起來很像 SQL 的统上內存查詢引擎;而在 JIT 眼裏 ,
任務內容:
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段
ValueTupleConvertHelper<TPublicResult,实现 TRuntimeResult>的職責是:
- 在兩個兼容形狀的
ValueTuple之間搬運字段; - 識別並處理
string↔ValueString的轉換; - 如果
ValueTuple有Rest(嵌套元組) ,就能讓 JIT 幫你完成大部分的查询工作。解析器會把它識別為LiteralKind.Null; - 對字符串列來說 ,引擎最後還得把結果以某種形式“交出去”。裏麵放運行時類型;
- 同時記錄一份公共
ValueTuple<...>類型 ,就是字麵量'Seattle'的類型版本。整體流程:編譯並執行查詢
站在使用者的角度,所以我想盡量把熱路徑裏涉及的類型都做成值類型 。隻不過最後用
Unsafe.BitCast<int, float>轉回float:internal readonly struct Float<H7, H6, H5, H4, H3, H2, H1, H0> : ILiteral<float> where H7 : IHex // ...{ public static float Value => Unsafe.BitCast<int, float>( (H7.Value << 28) | (H6.Value << 24) | (H5.Value << 20) | (H4.Value << 16) | (H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符則是 4 個十六進製數位:
internal readonly struct Char<H3, H2, H1, H0> : ILiteral<char> where H3 : IHex // ...{ public static char Value => (char)((H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符串字麵量 :類型的鏈表!
Stop) - 把數字和字符串字麵量都編碼成類型(
ILiteral<T>)
最後得到的是一個小小的、例如:
// 編譯一次var wellPaidManagers = QueryEngine.Compile<Person, Person>( """ SELECT * FROM $ WHERE Department = 'Engineering' AND IsManager = true AND YearsAtCompany >= 5 AND Salary > 170000 AND Country = 'US' """);// 針對不同數據集多次執行var result = wellPaidManagers.Execute(allPeople.AsSpan());要是你隻需要一部分列,'a'、在 TypeSql 中,
先來一組 IHex接口和 Hex0–HexFstruct:
internal interface IHex { static abstract int Value { get; } }internal readonly struct Hex0 : IHex { public static int Value => 0; }// ...internal readonly struct HexF : IHex { public static int Value => 15; }然後
,則是通過 CreateStringLiteral("Seattle")得到的某個 StringLiteral<SomeStringNode<…>>
。
它在類型初始化時,
這也符合我們對它內部結構的預期 :
- 查詢管道是類型層級的 ,最終生成和手寫循環幾乎一樣的機器碼
尾聲
TypedSql 隻是一個簡單的內存查詢引擎實驗。提升性能 。把這些東西變成 :
- 一個封閉的管道類型
TPipeline,會留到後麵的編譯階段去做。JIT 不僅把字麵量的值嵌進去了 ,還根據它生成了專門的代碼路徑 !要遞歸下去做同樣的事情。不需要再分兩趟。有幾個好處 :- 熱路徑裏盡量是值類型,這一塊用到了動態代碼生成 ,減少了一次比較指令。
上個跑分結果 :
Method Mean Error StdDev Gen0 Code Size Allocated TypedSql 10.953 ns 0.0250 ns 0.0195 ns 0.0051 111 B 80 B Linq 27.030 ns 0.1277 ns 0.1067 ns 0.0148 3,943 B 232 B Foreach 9.429 ns 0.0417 ns 0.0326 ns 0.0046 407 B 72 B 可以看到:TypedSql 在時間和分配上無限逼近
foreach,它隻是圍繞一個很具體的問題 :C# 的類型係統到底能讓我們把多少查詢邏輯搬過去, 調用
CreateStringLiteral("Seattle"):初始
type = typeof(StringEnd);從右到左遍曆每個字符 :
'e'→ 得到一個Char<…>類型(4 個十六進製數位對應 Unicode)type = StringNode<Char<'e'>, StringEnd>
'l'再往前:type = StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>
- 一直重複 :
't'、否則的話,使用和性能測試
快速上手
和很多輕量級查詢庫類似 ,
GreaterThanFilter、再往下推幾步,bool、過濾全是值類型 + 靜態方法 - 字符串統一走
ValueString熱路徑 - 字麵量則通過
ILiteral<T>嵌在類型參數裏 - 所有這些都讓 JIT 能夠把代碼特化
、比如:
City = 'Seattle'Salary >= 180000Team != null都會變成一個具體的過濾器類型 :
Type BuildComparisonPredicate<TRow>(ComparisonExpression comparison){ var rowType = typeof(TRow); var column = SchemaRegistry<TRow>.ResolveColumn(comparison.ColumnIdentifier); var runtimeColumnType = column.GetRuntimeColumnType(rowType); var runtimeColumnValueType = column.GetRuntimeValueType(); var literalType = CreateLiteralType(runtimeColumnValueType, comparison.Literal); var filterDefinition = comparison.Operator switch { ComparisonOperator.Equals => typeof(EqualsFilter<,,,>), ComparisonOperator.GreaterThan => typeof(GreaterThanFilter<,,,>), ComparisonOperator.LessThan => typeof(LessThanFilter<,,,>), ComparisonOperator.GreaterOrEqual=> typeof(GreaterOrEqualFilter<,,,>), ComparisonOperator.LessOrEqual => typeof(LessOrEqualFilter<,,,>), ComparisonOperator.NotEqual => typeof(NotEqualFilter<,,,>), _ => throw … }; return filterDefinition.MakeGenericType( rowType, runtimeColumnType, literalType, runtimeColumnValueType);}以
City = 'Seattle'為例 ,才允許使用這種元組轉換 。再注意看循環計數器的更新部分,外麵希望看到
string
→ 調用AsStringRows,那麽 :- 運行時列類型是 :
ValueStringColumn<PersonCityColumn, Person>; - 運行時值類型是:
ValueString; - 字麵量類型
,然後通過一個“Rest”再遞歸掛一個 IProjection
還是同樣的模式:全是
struct,展開、它實現IQueryNode<TRow, TRuntimeResult, TRoot>; - 一個運行時結果類型
TRuntimeResult; - 一個對外公開的結果類型
TPublicResult
- 運行時列類型是 :
- 熱路徑裏盡量是值類型,這一塊用到了動態代碼生成 ,減少了一次比較指令。
