上一篇文章,我們通過實例比較了一下C#和Rust的性能表現,應該說在Release模式下面,Rust進行計算密集型的運算還是有些比較明顯的優勢的。那么,我們有沒有可能,在C#中做一些快速應用開發,而一些核心的算法用Rust來實現呢?答案是可以的。
編寫Rust代碼
下面這段代碼,保存在lib.rs文件中
use std::thread; #[no_mangle] pub extern fn process(){ let handles :Vec<_> =(0..10).map(|_|{ thread::spawn(||{ let mut x= 0; for _ in (0..5_000_000){ x+=1 } x }) }).collect(); for h in handles{ println!("Thread finished with count={}",h.join().map_err(|_| "Could not join a thread!").unwrap()); } println!("done!"); }
這段代碼的幾個關鍵點在於
1.聲明為pub,也就是說要讓外部可以訪問到
2.聲明為extern,意思應該也是說希望外部可以訪問
3.添加一個標記 #[no_mangle],這個開關據說是阻止編譯器在編譯的時候,重命名函數。我也還不是很理解,先照這么做吧
其他部分就是標准的Rust代碼了
生成Rust的動態鏈接庫
默認情況下,Rust編譯的庫叫做靜態鏈接庫,如果我們需要編譯動態鏈接庫的話,需要在Cargo.toml文件中定義
然后,運行cargo build -- release命令生成動態鏈接庫(dll)
我們在輸出目錄中,可以看到一個countlib.dll 的動態鏈接庫文件
在C#中使用這個動態鏈接庫
你可以將countlib.dll放在C#編譯輸出目錄的根目錄下面
using System; using System.Threading.Tasks; using System.Diagnostics; using System.Threading; using System.Runtime.InteropServices; namespace ConsoleApplication1 { class Program { [DllImport("countlib.dll",CallingConvention= CallingConvention.Cdecl)] public static extern void process(); static void Main(string[] args) { Stopwatch watch = new Stopwatch(); watch.Start(); //Parallel.For(0, 10, i => //{ // var x = 0; // for (int j = 0; j< 5000000; j++) // { // x += 1; // } // Console.WriteLine("線程:{0} 完成計數",Thread.CurrentThread.ManagedThreadId); //}); process();//調用Rust里面的程序process進行計算 watch.Stop(); Console.WriteLine("耗時:{0}秒", watch.Elapsed.TotalSeconds); Console.Read(); } } }
在Debug模式下面的耗時為 0.002秒(提升太明顯了吧)
在Release模式下面的耗時為0.002秒(基本上跟Debug模式不相上下,很神奇嗎)
那么,這個性能表現,幾乎接近了直接使用Rust的性能,比原先用C#的方式提高了5倍。
如此說來,計算密集型(尤其是需要用到多線程,多核)的任務,可以用Rust來編寫,然后在C#中調用。
【特別注意】
cargo build默認情況下會根據當前計算機的配置進行編譯,例如我是64位的計算機,那么編譯出來的dll也是64位的,在C#中用的時候,就需要同樣設置為64位,否則就會出現錯誤
那么,cargo build是否可以指定對應的平台進行編譯呢?可以通過指定 --target參數來實現,可用的值主要有
x86_64-pc-windows-gnu
i686-unknown-linux-gnu
x86_64-unknown-linux-gnu
詳細可以參考 http://doc.crates.io/manifest.html
我用下面這樣用就可以編譯一個通用的dll(既能用於32位,也能用於64位——采用WOW模式)
其實這個編譯選項,類似於我們在Visual Studio中使用Any CPU進行編譯