WinRT開發有著多種選擇性,就程式語言這一點就表現的很突出;這裡就這一點 深入展開,探討在WinRT開發之初如何依據各 個程式語言的特性、功能和效率來對 產品的技術方向做出選擇。

這裡我選擇執行計算複雜度較高的演算法作為測試方法,雖然不能代表全部,但 是很大程度上展示大家平時開發過程中所面臨的常見場景 和問題。考慮到演示和 理解,就選擇了查詢100000以內的所有素數的個數的演算法作為演示。另外也順帶演 示如何在WinRT下實現多程式語言和技 術之間的協作吧。

關於基本知識和演算法吧詳細的說明,請自行搜尋各大引擎吧(關鍵 詞:prime、素數),這裡我就列舉在各個語言下我的簡單實現吧,其中包括使用 普通演算法和並 行計算的兩個版本。

 

第一部分,從目前.NET主流來看吧,以C# 為例,普通版本,這個沒什麼多說的,就是從前往後看某個數是不是素數:

private static int 
CountingInternal(int n)
{
     var numprimes = 1;
     for (var i = 3; i <= n; i += 2)
     {
         var isPrime = true;
         var limit = Math.Ceiling(Math.Sqrt(i)) + 1;
         for (var j = 3; j < limit; j += 2)
         {
             if (i%j == 0)
             {
                 isPrime = false;
                 break;
             }
         }
         if (isPrime)
         {
             numprimes++;
         }
     }
     return numprimes;
}

並行版本稍微複雜一點點,選擇Parallel.For來並行執行一個從1至n/2的並行 迴圈(我這裡偷懶了一下,沒有處理奇 偶數的情況,因為我的呼叫時傳入的都是 偶數),發現是素數,使用Interlocked輔助方法給計數增加1。

private static int 
CountingParallel(int n)
{
     var numprimes = 1;
     Parallel.For(1, n/2, i =>
     {
         if (IsPrime(i*2 + 1))
         {
              Interlocked.Increment(ref numprimes);
         }
     });
     return numprimes;
}

public static bool IsPrime(int n)
{
     if (n%2 == 0)
         return false;
     var limit = (int) (Math.Ceiling(Math.Sqrt(n)) + 1);
     for (var i = 3; i < limit; i += 2)
     {
         if (n%i == 0)
         {
             return false;
         }
     }
     return true;
}

第一種場景,直接嵌入演算法到C# WinRT App工程,執行結果如下(單位毫 秒):

執行次數 1(啟動) 2 3 4 5
普通 14.0299 9.0005 9.1825 8.0021 11.0181
並行 6.0008 2.0004 2.9993 2.0014 3.999

第二種場景,將C#演算法包裝在一個類庫裡(注意 是CLR類庫,只能在C#/VB直接通用),在C# WinRT App工程中呼叫這個類庫,執行 結果如下(單位毫秒):

執行次數 1(啟動) 2 3 4 5
普通 12.0299 9.0019 10.003 9.0014 9.00017
並行 6.0008 2 3.0003 2.9997 1.9995

第三種場景,將C#演算法包裝到一個Windows Runtime Component(WRC)中,在C# WinRT App工程中呼叫這個WRC類庫,執行結 果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  11.9904  9.0032  9  9。0028 9.00149 
並行   6.0008  1.9817  1.9985  1.9993  2

第四種場景,將C#演算法包裝到一個Windows Runtime Component(WRC)中,在WinJS App工程中呼叫這個WRC類庫,執行結果如 下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  11  9  8  9 8
並行   4  1  1  3  2

小結:以上是從.NET角度來進行的比較,很容易 看出第一次CLR載入在這裡效能損耗表現的很明顯,完成載入之後效能將穩定在一 定範 圍內波動;另外,平行計算在純演算法的應用中有很明顯的效能優勢。

 

第二部分,接下來我們迴歸Native環境,這裡我 依然使用普通和平行計算兩種來嘗試,普通的依然沒什麼可說的(實際上和C#的沒 區 別,除了關鍵字不一樣)。

static int CountingInternal(int n)
{
     auto numprimes = 1;
     for (auto i = 3; i <= n; i += 2)
     {
         auto isPrime = true;
         auto limit = ceil(sqrt(i)) + 1;

         for (auto j = 3; j < limit; j += 2)
         {
             if (i%j == 0)
             {
                 isPrime = false;
                 break;
             }
         }

         if (isPrime)
         {
             numprimes++;
         }
     }
     return numprimes;
}

並行版本,需要注意的是C++ lambda的傳值 和作用域問題,其他的和C#的沒區別:

static bool IsPrime(int n)
{
     if (n%2 == 0)
         return false;
     auto limit = (int) (ceil(sqrt(n)) + 1);
     for(auto i=3; i<limit; i+=2)
     {
         if(n%i == 0)
         {
             return false;
         }
     }
     return true;
}

static int CountingParallel(int n)
{
     auto numprimes = 1;
     parallel_for(1, n/2, [&](int i)
     {
         if(IsPrime(i*2+1))
         {
             InterlockedIncrement((volatile unsigned long*)&numprimes);
         }
     });
     return numprimes;
}

第一種場景,直接將C++演算法放到C++ WinRT App 中使用,執行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  8.0019 7.9991  8.0209  8.9843  8.0181 
並行   1.9794  1.998  1.9994  1.984  2.0003

第二種場景,將C++演算法包裝在DLL中,在C++ WinRT App中使用,執行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  9 9  9  8  9 
並行   3 2  3  2  2

第三種場景,將C++演算法包裝在動態連線庫Dll中,在C# WinRT App中通過 PInvoke來呼叫,執行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  9 9  8  9  9 
並行   3 2  3  2  3

第四種場景,將C++演算法包裝在靜態連結庫Lib中,在C++ WinRT App中呼叫,執 行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  8 8  8  9  9 
並行   2 3  3  2  3

第五種場景,將C++演算法包裝在Windows Runtime Component(WRC)中,在C# WinRT App中呼叫,執行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  8.0014 8.0191  8.0293  8.0019  9.0291
並行   1.9994 1.9999  1.998  1.9994  2.99982

第六種場景,將Windows Runtime Component(WRC)中,在WinJS App中呼叫, 執行結果如下(單位毫秒):

執行次數  1(啟動)  2 3 4
普通  9 8  9  8  8 
並行   2 2  3  2  3

第七種場景是將C++演算法包裝在Windows Runtime Library(WRL,基於COM的底 層開發)中,然後在任何一種WinRT App中呼叫,可以預見這是一種很強大的方 式,但同時也是最費解的一種方式,我成功的包裝了普通演算法的COM版,但是嘗試 了很長時間不能成功實現並行運算 的版本,也就放棄在這裡展示了,如果你知道 如何在WRL中實現平行計算並返回 IAsyncOperation<T>,請不吝賜教。 

小結:基於C++的實現在適用性、穩定性和執行效率上無可挑剔,如果對於所有 細節(包括第一次啟動)的效率考慮,C++是優先 的;如果考慮到C++的複雜度, 如果專案對效能要求可以適當放鬆但對進度要求很高的時候,選擇CLR會比較容易 控制的;如果原來已有的Web專案 向WinRT遷移,那麼前段展示則可以考慮使用 WinJS+HTML來實現,後臺演算法根據需要選擇C++或者CLR。

 

第三部分,如果所有的演算法全部執行在 JavaScript中,那麼其效能如何呢?這裡我先買個關子,留待你自己去探究和發 掘。

 

總結,WinRT在程式語言的選擇性上有著非常好的 靈活性,在做選擇的時候需要充分考慮自己的要求,比如效能、比如工期、比如經 驗等 等。對於全新專案,在有經驗的情況下,追求極致效能的首先首當其衝是 C++,如果考慮到經驗和掌控,可以選擇使用C++做底層,選擇相對容易上手 的 C#/VB或者HTML+JS做介面的方法;如果專案工期要求很緊,或者從老系統遷移,那 麼這時候更多的考慮是使用已有資源,直到效能瓶頸的時 候才採取措 施——以C++重寫效能瓶頸來解決,當然,如果沒有C++經驗,也可以考 慮使用C#/VB來 實現WRC以包裝核心邏輯,從而提升執行效率。

 

附以上測試原始碼和測試工程,點選這裡下載