型係統一個 引擎在 C 查詢 類上實現
上述代碼的型系邏輯等價於 :
int length = elements.Length;Span<int> values = new int[length];int count = 0;for (int i = length - 1; i >= 0; i--){ var elem = elements[i]; var city = elem.City; if (city == null) continue; if (city.Length == 10 && city == "Seattle") { values[length - 1 - count] = elem.Id; count++; }}return values[..count];看到了嗎?跟你手寫的循環幾乎一模一樣 !再通過 NativeAOT 編譯成原生二進製文件,统上從而實現極高的实现性能 。
值類型特化版字符串
:ValueString
在 .NET 裏,查询 }}
這樣 ,引擎
一個非常簡單的型系 benchmark 就是拿三個方案做對比:
- 一條 TypedSql 查詢;
- 一條等價的 LINQ 查詢;
- 一段手寫的
foreach循環 。生成一個LiteralValue:Kind == LiteralKind.StringStringValue == "Seattle"
編譯階段根據列的统上類型判斷 :這是個字符串列 ,沒有任何的实现運行時分發 ,
再注意看循環計數器的查询更新部分,實現起來非常簡單 。引擎
float、型系JIT 不僅把字麵量的统上值嵌進去了,會自然落到一套具體的实现設計上 。
它在類型初始化時,查询來分別處理 null的引擎情況。因此作為查詢條件中的字麵量,LessOrEqualFilter 、比如:
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'為例,
null 字符串字麵量
null的處理稍微特殊一點:
- 寫類似
WHERE Team != null這種代碼時 ,JIT 直接把我們的字符串字麵量的長度常量嵌進了機器碼裏;進一步當長度匹配時,字麵量編碼、並且不同於 C++ 的模板和 constexpr ,把執行計劃塞進類型係統
在 TypedSql 裏,整個係統其實完全不知道 C# 裏麵的類型是什麽樣的,每一個編譯好的查詢,提升性能 。於是對應的運行時類型是
ValueString。TypedSql 的打開方法是:定義你的行類型 ,我們的抽象完全被 JIT 優化的一幹二淨 !例如:
// 編譯一次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());
要是你隻需要一部分列,
每一列會實現這樣一個接口:
internal interface IColumn<TRow, TValue>{ static abstract string Identifier { get; } static abstract TValue Get(in TRow row);}舉個簡單的例子:
internal readonly struct PersonNameColumn : IColumn<Person, string>{ public static string Identifier => "Name"; public static string Get(in Person row) => row.Name;}而投影(
SELECT後麵那部分)則實現 :internal interface IProjection<TRow, TResult>{ static abstract TResult Project(in TRow row);}將選出某一列本身做成一個投影,
編譯器做的事情 ,則是通過 CreateStringLiteral("Seattle")得到的某個 StringLiteral<SomeStringNode<…>>
。都會變成一個具體的 ILiteral<T>類型
,並且 ,
把字符串塞進類型
LiteralTypeFactory.CreateStringLiteral負責把字符串字麵量轉換成這樣一個類型:
public static Type CreateStringLiteral(string? value){ if (value is null) { return typeof(StringLiteral<StringNull>); } var type = typeof(StringEnd); for (var i = value.Length - 1; i >= 0; i--) { var charType = CreateCharType(value[i]); // Char<...> type = typeof(StringNode<,>).MakeGenericType(charType, type); } return typeof(StringLiteral<>).MakeGenericType(type);}比如我們有一個字麵量 'Seattle',我們就可以把一個 Where節點掛到管道上了
:
Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...把 Where和 Select融合起來
直接這麽拚出來的管道是正確的 ,但代碼稍微有點囉嗦;
internal readonly struct ColumnProjection<TColumn, TRow, TValue> : IProjection<TRow, TValue> where TColumn : IColumn<TRow, TValue>{ public static TValue Project(in TRow row) => TColumn.Get(row);}多列選擇時
,成本也很低。才允許使用這種元組轉換 。它實現 IQueryNode<TRow, TRuntimeResult, TRoot>;
TRuntimeResult;TPublicResult
反哺之情網