.NET 和 Node.js 的效能比較

類別: IT
標籤: node.js

修正於(2013年-3月-31日 下午11:41 標準時間)此文已經撰寫完畢!在絕大多數讀者眼裡認為Node.js的非同步包是無法實現非同步的功能的,那麼此文又該基於什麼來寫呢?我確信我不是這樣所想的.我曾經想通過這個帳號進行測試,並將這些資料推薦到http://guillaume86.calepin.co/dotnet-vs-nodejs-performance.html這裡去,我確信未來不會影響到已經存在的言論.謝謝你們每一個人的意見,使得我保持清醒

修正於(2013年-3月-29 下午3:43 標準時間)有許多的評論圍繞NPM的async包來做驗證,我也將在我的賬號中從新測試.

如果你今天和任何一個在矽谷的創業者交談,你都有機會聽到node.js。 由於強迫非阻塞IO, Node.js是快速,可擴充套件性,並且它高效地利用了單執行緒模型的諸多特性,也是人們爭論最多的一個關鍵原因。我個人喜歡Javascript,所以能夠在伺服器端使用Javascript似乎是一個重要的收穫。Node.js超級快,因為不存在上下文切換和執行緒同步。這一觀念,實際上我並沒有真正地贊同過。我們都知道,這些做法應該是在任何多執行緒程式要完全避免,但要放棄一切,似乎像一個極端。但如果這意味著持續的高效能,又能得到保障,那將是有意義的。所以,我想測試這些理論,我想盡可能地以經驗為主地探明到底相比.NET,node.js是如何做到更快。

於是乎,我提出了一個涉及IO(理論上不涉及資料庫)和計算的問題。我想讓它們處於負載,這樣我就能看每個系統在壓力下是怎樣表現的。我提出了以下的問題:我有將近200個檔案,每個檔案在某在地方包含1萬到3萬個隨機小數。每一次對伺服器的請求都包含一個數字,例如/1和/120,伺服器就會開啟相應的檔案,讀取檔案的內容,並完成在記憶體中的排序,然後輸出中間值。對了,就是這樣。我們的目標就是達到最大的200個同時發生的請求,在這樣的想法下,每個請求都有一個對應的檔案並且沒有重疊。

我同樣想讓兩個平臺一致(.NET和Node.js)。舉個例子,我並不想在IIS上搭載.NET,因為IIS帶來的開銷太大了(快取,路由,效能計數器),如果我們以後都不用這些,那這樣的花銷就不公平了。我也避免了整個ASP.NET的管道,包括MVC,出於同樣的原因,它們帶來了我們在這個案例下不需要關心的特性。


接下來,我們使用.NET和Node.js建立一個基本HTTP監聽.那誰來做客戶端呢?在這裡我計劃建立一個.NET控制檯應用程式去訪問我們之前建立的這兩個HTTP服務.客戶端是寫的.NET之上的,使之我們的.NET和Node.js的服務都能夠使用相同的客戶端.在如此小規模的客戶端訪問是可以被忽略的問題.在我們研究更詳細的問題之前,讓我們看下面的結果圖

.NET and Node.JS - Performance Comparison

在.NET和Node.js中效能訪問記數(越低越好)

我們可以看到在平均情況下Node.js效能卓越.即使在少有的峰值出異常的情況下,一部分讀者可能被其迷惑.我要澄清的是圖中兩條線在最後時刻有意思的測試,隨著時間的推移你可能認為.NET和Node.js的效能總和甚至會超過.NET的啟動時間,接下來讓我們用不通的切面來進行更多的測試。

我們將從客戶端開始,客戶端使用 HttpClient來驅動對伺服器的請求。響應時間都是在客戶端維持的,以至於伺服器上不會出現極端的執行差異,也不會影響我們生成的數字。注意一點,不到最後一步,我是不會輕易做任何Console.Write。
publicvoidStart()
{
    Task[] tasks =newTask[this.tasks];
 
    for(inti = 0; i <this.tasks; ++i)
    {
        tasks[i] =this.Perform(i);
    }
 
    Task.WaitAll(tasks);
 
    result.ToList().ForEach(Console.WriteLine);
}
publicasync Task Perform(intstate)
{
    stringurl = String.Format("{0}{1}",this.baseUrl, state.ToString().PadLeft(3,'0'));
    var client =newHttpClient();
    Stopwatch timer =newStopwatch();
 
    timer.Start();
    stringresult = await client.GetStringAsync(url);
    timer.Stop();
 
    this.result.Enqueue(String.Format("{0,4}\t{1,5}\t{2}", url, timer.ElapsedMilliseconds, result));
}
有了這樣的客戶端,我們可以開始關注伺服器端了。首先,我們開始用Node.js來實現了。Node.js的美在於它的簡明的語法。我們能用區區不足40行的程式碼來來建立一些基於CPU核數的程式,並且能在它們上面共享受CPU限制的任務。
var http = require('http');
var fs = require('fs');
var cluster = require('cluster');
var numCPUs = require('os').cpus().length;
 
if(cluster.isMaster) {
    // Fork workers.
    for(var i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
 
    cluster.on('exit',function(worker, code, signal) {
        console.log('worker '+ worker.process.pid +' died');
    });
}
else{
    http.createServer(function(request, response) {
        var file = parseInt(request.url.substring(1));
        file = file % 200;
        file = String("000"+ file).slice(-3);
 
        // read the file
        fs.readFile('../data/input'+file+'.txt','ascii',function(err, data) {
            if(err) {
                response.writeHead(400, {'Content-Type':'text/plain'});
                response.end();
            }
            else{
                var results = data.toString().split("\r\n");
 
                results.sort();
                response.writeHead(200, {'Content-Type':'text/plain'});
                response.end('input'+file+'.txt\t'+ results[(parseInt(results.length/2))]);
            }
        });
    }).listen(8080,'127.0.0.1');
}
console.log('Server running at http://127.0.0.1:8080/')
最後,我們來看看.NET服務實現,確切的說我們用的是.NET 4.5版本,自帶async/await功能。如我前面提到的一樣,我希望用不帶IIS或者ASP.NET的純.NET來和node比較,所以我用一個簡單的HTTP監聽程式來開始:
publicasync Task Start()
{
    while(true)
    {
        var context = awaitthis.listener.GetContextAsync();
        this.ProcessRequest(context);
    }
}
通過這個我可以開始處理各個請求,請求一進來我讀取檔案流系統,並沒有阻塞執行緒池裡的執行緒,然後執行記憶體排序,這是用Array.Sort重寫的一個簡單任務。對於.NET,在這裡通過參照並行程式設計寫的並行排序演算法使得程式有了顯著的效能提升,但是我沒有選擇這樣是因為這不是這兩者比較的重點。
private async void ProcessRequest(HttpListenerContext context)
{
    try
    {
        var filename = this.GetFileFromUrl(context.Request.Url.PathAndQuery.Substring(1));
        string rawData = string.Empty;
 
        using (StreamReader reader = new StreamReader(Path.Combine(dataDirectory, filename)))
        {
            rawData = await reader.ReadToEndAsync();
        }
         
        var sorted = await this.SortAsync(context, rawData);
        var response = encoding.GetBytes(String.Format("{0}\t{1}", filename, sorted[sorted.Length / 2]));
 
        await context.Response.OutputStream.WriteAsync(response, 0, response.Length);
        context.Response.StatusCode = (int)HttpStatusCode.OK;
    }
    catch(Exception e)
    {
        context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
        Console.WriteLine(e.Message);
    }
    finally
    {
        context.Response.Close();
    }
}
 
private async Task<string[]> SortAsync(HttpListenerContext context, string rawData)
{
    return await Task.Factory.StartNew(() =>
    {
        var array = rawData.Split(new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
        Array.Sort(array);
 
        return array;
    });
}

你能下載整個原始碼,這個壓縮檔案包括了.NET和Node.js的客戶端和伺服器端的原始碼。同時壓縮包裡還包括了生成隨機數檔案的工具,你能在你的本機中執行這些測試。你將在zip檔案中發現這些原始的數字。

當你決定為你的伺服器選擇下一個框架的時候,我希望這些測試能對你有幫助。對於大多數創業者來說,最關鍵的支點是效能,擴充性。顯然,Node.js在今天已經展現給我們了。

同時,請記住以下的一些評論是基於使用async NPM 包的原始文章。本文已更新了正確的資訊。

.NET 和 Node.js 的效能比較原文請看這裡