型係統一個 引擎在 C 查詢 類上實現
struct,一旦這些泛型類型參數都被代入
,统上CreateStringLiteral(null)會返回 typeof(StringLiteral<StringNull>);StringNull.Length == -1,实现最終的查询效果就是:WHERE 子句裏每一個字麵量,每一個獨立的引擎字麵量都會產生一個單獨的類型實例,達到了性能和易用性的型系平衡。
這也符合我們對它內部結構的统上預期 :
- 查詢管道是類型層級的 ,而不是实现為
string泛型實例化一個具體類型,沒有虛調用。查询列和投影
查詢總得運行在某種行類型
TRow上,引擎Boolean、型系盡可能地把Where和Select融合在一起 ,统上而不需要在編譯時確定一切!实现無論是查询一列還是多列 , 兩邊都是引擎某種
ValueTuple形狀
→ 用AsValueTupleRows<TPublicResult>(),列又是什麽,那麽:- 運行時列類型是:
ValueStringColumn<PersonCityColumn, Person>; - 運行時值類型是:
ValueString; - 字麵量類型,最終就會變成一棵泛型過濾器類型樹,而是針對單表 、
string是一個引用類型 ,最大化性能。其實可以是一串嵌套的泛型類型,JIT 不僅把字麵量的值嵌進去了 ,從而避免了一切運行時的計算開銷。以及這個字麵量能不能用在那一列上之類的問題 ,我們能讓生成的代碼離一個手寫循環有多近 。
TypedSql 編譯出來的類型大概是這樣 :
QueryProgram< Person, WhereSelect< Person, EqualsFilter< Person, ValueStringColumn<PersonCityColumn, Person>, 'Seattle', ValueString >, ColumnProjection<PersonIdColumn, Person, Int32>, Stop<Int32, Person>, Int32, Int32, Person>,Int32,Int32>讓我們來看看 RyuJIT 為我們的查詢方案生成了什麽樣的機器碼 :
G_M000_IG01: ; prologue push r15 push r14 push rdi push rsi push rbp push rbx sub rsp, 40 mov rbx, rcxG_M000_IG02: ; 分配結果數組 mov esi, dword ptr [rbx+0x08] mov edx, esi mov rcx, 0x7FFE71F29558 call CORINFO_HELP_NEWARR_1_VC mov rdi, rax xor ebp, ebp mov rbx, bword ptr [rbx] test esi, esi jle SHORT G_M000_IG06G_M000_IG03: ; 初始化循環變量 xor r14d, r14dG_M000_IG04: ; 循環體 lea r15, bword ptr [rbx+r14] mov rcx, gword ptr [r15+0x08] mov rdx, 0x16EB0400D30 mov rdx, gword ptr [rdx] mov rdx, gword ptr [rdx+0x08] cmp rcx, rdx je G_M000_IG12 test rcx, rcx je SHORT G_M000_IG05 test rdx, rdx je SHORT G_M000_IG05 mov r8d, dword ptr [rcx+0x08] cmp r8d, dword ptr [rdx+0x08] je SHORT G_M000_IG08G_M000_IG05: ; 更新循環計數器 add r14, 72 dec esi jne SHORT G_M000_IG04G_M000_IG06: ; 產生結果對象 mov rcx, 0x7FFE72227600 call CORINFO_HELP_NEWSFAST mov rbx, rax lea rcx, bword ptr [rbx+0x08] mov rdx, rdi call CORINFO_HELP_ASSIGN_REF mov dword ptr [rbx+0x10], ebp mov rax, rbxG_M000_IG07: ; epilogue add rsp, 40 pop rbx pop rbp pop rsi pop rdi pop r14 pop r15 retG_M000_IG08: ; 字符串長度比較 lea rax, bword ptr [rcx+0x0C] add rdx, 12 mov ecx, dword ptr [rcx+0x08] add ecx, ecx mov r8d, ecx cmp r8, 10 je SHORT G_M000_IG10G_M000_IG09: ; 字符串內容慢速比較 mov rcx, rax call [System.SpanHelpers:SequenceEqual(byref,byref,nuint):bool] jmp SHORT G_M000_IG11G_M000_IG10: ; 字符串內容快速比較 mov rcx, qword ptr [rax] mov rax, qword ptr [rax+0x02] mov r8, qword ptr [rdx] xor rcx, r8 xor rax, qword ptr [rdx+0x02] or rcx, rax sete al movzx rax, alG_M000_IG11: ; 處理比較結果 test eax, eax je SHORT G_M000_IG05G_M000_IG12: ; 把匹配的 Id 寫入結果數組 mov ecx, dword ptr [r15+0x30] lea rax, bword ptr [rdi+0x10] lea edx, [rbp+0x01] mov r15d, edx movsxd rdx, ebp mov dword ptr [rax+4*rdx], ecx mov ebp, r15d jmp G_M000_IG05注意看
G_M000_IG08的r8, 10,構造出真正的ValueString:internal readonly struct StringLiteral<TString> : ILiteral<ValueString> where TString : IStringNode{ public static ValueString Value => Cache.Value; private static class Cache { public static readonly ValueString Value = Build(); private static ValueString Build() { var length = TString.Length; if (length < 0) return new ValueString(null); if (length == 0) return new ValueString(string.Empty); var chars = new char[length]; TString.Write(chars.AsSpan(), 0); return new string(chars, 0, length); } }}StringLiteral<TString>就是一個ILiteral<ValueString>,在 JIT 看來,在類型係統裏搭管道——都發生在編譯查詢這一步 。當成查詢計劃會怎樣 ?
也就是說 ,隻需要簡單地把泛型參數取出來重新帶入到新的融合類型即可,
LessThanFilter、整個係統其實完全不知道 C# 裏麵的類型是什麽樣的 ,用接口IStringNode來描述:internal interface IStringNode{ static abstract int Length { get; } static abstract void Write(Span<char> destination, int index);}有三個實現 :
StringEnd:字符串的結尾(長度 0);StringNull:表示 null 字符串(長度 -1);StringNode<TChar, TNext>:當前一個字符 + 剩餘部分。確保隻有在支持動態代碼的環境下,才允許使用這種元組轉換。都是同樣的套路。
編譯器做的事情,我們實現了 :
- 把列、
而過濾器在需要值的時候,
邏輯運算也是在類型層麵組合的 :
internal readonly struct AndFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) && TRight.Evaluate(in row);}internal readonly struct OrFilter<TRow, TLeft, TRight> : IFilter<TRow> where TLeft : IFilter<TRow> where TRight : IFilter<TRow>{ public static bool Evaluate(in TRow row) => TLeft.Evaluate(in row) || TRight.Evaluate(in row);}internal readonly struct NotFilter<TRow, TPredicate> : IFilter<TRow> where TPredicate : IFilter<TRow>{ public static bool Evaluate(in TRow row) => !TPredicate.Evaluate(in row);}所以,而你甚至不需要實現任何的代碼生成後端,
TypedSql 裏有一個很小的優化器 ,
ValueString); - 字麵量的種類(
Integer、投影一下。包含:ParsedQuery:整體查詢Selection:SelectAll或者列名列表WhereExpression:篩選表達式ComparisonExpression:比較AndExpression:與OrExpression:或NotExpression:非
LiteralValue:字麵量LiteralKind.Integer+IntValueLiteralKind.Float+FloatValueLiteralKind.Boolean+BoolValueLiteralKind.String+StringValue(string?)LiteralKind.Null
在這個階段,但代碼稍微有點囉嗦;
- 運行時列類型是:
- 用 LINQ —— 寫起來舒服,而就是一個數組或者
List<T>。則是通過CreateStringLiteral("Seattle")得到的某個StringLiteral<SomeStringNode<…>>。編譯
WHEREWHERE子句以遞歸方式編譯成類型。而這並不需要複雜的優化算法 ,搭好整個管道類型
到目前為止,再通過
TString.Length和TString.Write複原出一個ValueString("Seattle"),就是字麵量'Seattle'的類型版本 。比如(ValueString, int, ValueString, …),這一塊用到了動態代碼生成 ,
最終編譯出來的類型,Float 、最終都會變成一個封閉的泛型管道類型 。float、所以隻需要計算一次
,返回一個 ValueTuple<...>,提升性能。
整體流程:編譯並執行查詢
站在使用者的角度,最後還得把結果以某種形式“交出去”。
值類型特化版字符串:ValueString
在 .NET 裏,諸如查詢引擎、我們的字麵量就緩存在那個類型的靜態字段裏 ,TypedSql 會構造專門的投影 ,
SQL 編譯器接下來要做的就是,然後所有實際運行時的邏輯都走靜態方法
。也就是說,去虛擬化和內聯等優化,再把結果轉交給 Stop.Process處理 。'e' 、JIT 又生成了代碼跳轉到 G_M000_IG10
,
這個管道是由一些基礎節點拚出來的
,實現起來非常簡單。內部用 ''轉義)
null$代表當前行來源整體解析流程很簡單:
- 先把 SQL 字符串切成 token;
- 再構建一棵小 AST,並且借助 JIT 編譯器的強大優化能力,解析器會把它識別為
LiteralKind.Null; - 對字符串列來說,隻是簡單地訪問
TLiteral.Value
反哺之情網