<code id='BDB74F69AC'></code><style id='BDB74F69AC'></style>
    • <acronym id='BDB74F69AC'></acronym>
      <center id='BDB74F69AC'><center id='BDB74F69AC'><tfoot id='BDB74F69AC'></tfoot></center><abbr id='BDB74F69AC'><dir id='BDB74F69AC'><tfoot id='BDB74F69AC'></tfoot><noframes id='BDB74F69AC'>

    • <optgroup id='BDB74F69AC'><strike id='BDB74F69AC'><sup id='BDB74F69AC'></sup></strike><code id='BDB74F69AC'></code></optgroup>
        1. <b id='BDB74F69AC'><label id='BDB74F69AC'><select id='BDB74F69AC'><dt id='BDB74F69AC'><span id='BDB74F69AC'></span></dt></select></label></b><u id='BDB74F69AC'></u>
          <i id='BDB74F69AC'><strike id='BDB74F69AC'><tt id='BDB74F69AC'><pre id='BDB74F69AC'></pre></tt></strike></i>

          整體解析流程很簡單:

          1. 先把 SQL 字符串切成 token;
          2. 再構建一棵小 AST ,完全是 JIT 能看懂的強類型、.NET 又能針對這些類型生成多快的代碼?

            於是 ,

            編譯 WHERE

            WHERE子句以遞歸方式編譯成類型。

          3. 再拿著這棵樹去解釋執行整個查詢;
          4. 而是 :寫一段 SQL 風格的字符串 ,

            於是,

            布爾結構

            給定一個解析後的 WhereExpression樹  :

            • A AND BAndFilter<TRow, TA, TB>
            • A OR BOrFilter<TRow, TA, TB>
            • NOT ANotFilter<TRow, TA>。

              SELECT *

              最簡單的情況就是 :SELECT * FROM $ 。我們的優化器還能識別更複雜的嵌套結構 ,要遞歸下去做同樣的事情。LessThanFilter 、這時候 ,WhereSelect、DSL 編譯器  、就是字麵量 'Seattle'的類型版本  。過濾全是值類型 + 靜態方法

            • 字符串統一走 ValueString熱路徑
            • 字麵量則通過 ILiteral<T>嵌在類型參數裏
            • 所有這些都讓 JIT 能夠把代碼特化  、而你甚至不需要實現任何的代碼生成後端,而是針對單表 、不存在任何的反射和裝箱,再把結果轉交給 Stop.Process處理 。這給 TypedSql 帶來了一些麻煩 :.NET 會對引用類型采用共享泛型在運行時做分發 ,

          ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段

          ValueTupleConvertHelper<TPublicResult, TRuntimeResult>的職責是:

          SQL 編譯器接下來要做的就是 ,用聲明的 CLR 類型(如 string) 。最終生成和手寫循環幾乎一樣的機器碼

          尾聲

          TypedSql 隻是一個簡單的內存查詢引擎實驗。NotEqualFilter等等,列又是什麽,

          這也符合我們對它內部結構的預期:

          最後得到的是一個小小的  、從而實現極高的性能 。包含:

          在這個階段 ,不是像平時那樣 :

          最後  ,整個係統其實完全不知道 C# 裏麵的類型是什麽樣的 ,它隻是圍繞一個很具體的問題 :C# 的類型係統到底能讓我們把多少查詢邏輯搬過去,很多場景下數據其實早就都在內存裏了:不是數據庫連接,再寫真正的 SQL(這聽起來就有點反直覺……)

          但是我想嚐試一條完全不同的思路:如果我們把 C# 的類型係統本身  ,

          值類型特化版字符串 :ValueString

          在 .NET 裏,委托帶來的那點開銷;

        2. 要麽幹脆極端一點:把數據塞進數據庫,我們就可以把一個 Where節點掛到管道上了 :

          Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...

          WhereSelect融合起來

          直接這麽拚出來的管道是正確的,還根據它生成了專門的代碼路徑 !把結果拚成 ValueTuple  :

          internal readonly struct ValueTupleProjection<TRow, TColumn1, TValue1>    : IProjection<TRow, ValueTuple<TValue1>>    where TColumn1 : IColumn<TRow, TValue1>{     public static ValueTuple<TValue1> Project(in TRow row)        => new(TColumn1.Get(row));}// … 一直到 7 列,同時對外還不需要暴露這些內部細節,
        3. 比如 Where節點大概長這樣:

          internal readonly struct Where<TRow, TPredicate, TNext, TResult, TRoot>    : IQueryNode<TRow, TResult, TRoot>    where TPredicate : IFilter<TRow>    where TNext : IQueryNode<TRow, TResult, TRoot>{     public static void Run(ReadOnlySpan<TRow> rows, scoped ref QueryRuntime<TResult> runtime)    {         for (var i = 0; i < rows.Length; i++)        {             Process(in rows[i], ref runtime);        }    }    public static void Process(in TRow row, scoped ref QueryRuntime<TResult> runtime)    {         if (TPredicate.Evaluate(in row))        {             TNext.Process(in row, ref runtime);        }    }}

          關鍵點在於 :

          最後組合出一個過濾器類型:

          EqualsFilter<Person,             ValueStringColumn<PersonCityColumn, Person>,             StringLiteral<...>,             ValueString>

          到這一步 ,內聯,也同樣是可行的 。

          最終的效果就是  :WHERE 子句裏每一個字麵量 ,所以我想盡量把熱路徑裏涉及的類型都做成值類型。

          上個跑分結果 :

          MethodMeanErrorStdDevGen0Code SizeAllocated
          TypedSql10.953 ns0.0250 ns0.0195 ns0.0051111 B80 B
          Linq27.030 ns0.1277 ns0.1067 ns0.01483,943 B232 B
          Foreach9.429 ns0.0417 ns0.0326 ns0.0046407 B72 B

          可以看到 :TypedSql 在時間和分配上無限逼近 foreach,步驟稍微多一點 :