[英]F# vs. C# performance Signatures with sample code
關於這個話題已經有很多討論,但我都是關於鞭打死馬,特別是當我發現他們可能還在呼吸時。
我正在研究解析CSV的異常和異國情調的文件格式,為了好玩,我決定用我知道的2 .net語言C#和F#來表征性能。
結果......令人不安。 F#大幅提升了2倍或更多(實際上我認為它更像是.5n,但是由於我正在測試硬件IO,因此獲得真正的基准測試很難)。
像讀取CSV這樣常見的性能特征讓我感到驚訝(請注意,系數意味着C#在非常小的文件上獲勝。我正在進行的測試越多,感覺C#的表現越差,這既令人驚訝也有關系,因為它可能意味着我做錯了)。
一些筆記:Core 2 duo筆記本電腦,主軸磁盤80演出,3演出ddr 800內存,Windows 7 64位溢價,.Net 4,沒有打開電源選項。
第一次運行后30,000行5寬1短語10個字符或更少給我3個支持尾調用遞歸(它似乎緩存文件)
對於尾部調用遞歸,300,000(重復相同的數據)是2的因子,F#的可變實現略微勝出,但性能簽名表明我正在擊中磁盤而不是ram-disking整個文件,這會導致半隨機性能尖峰。
F#代碼
//Module used to import data from an arbitrary CSV source
module CSVImport
open System.IO
//imports the data froma path into a list of strings and an associated value
let ImportData (path:string) : List<string []> =
//recursively rips through the file grabbing a line and adding it to the
let rec readline (reader:StreamReader) (lines:List<string []>) : List<string []> =
let line = reader.ReadLine()
match line with
| null -> lines
| _ -> readline reader (line.Split(',')::lines)
//grab a file and open it, then return the parsed data
use chaosfile = new StreamReader(path)
readline chaosfile []
//a recreation of the above function using a while loop
let ImportDataWhile (path:string) : list<string []> =
use chaosfile = new StreamReader(path)
//values ina loop construct must be mutable
let mutable retval = []
//loop
while chaosfile.EndOfStream <> true do
retval <- chaosfile.ReadLine().Split(',')::retval
//return retval by just declaring it
retval
let CSVlines (path:string) : string seq=
seq { use streamreader = new StreamReader(path)
while not streamreader.EndOfStream do
yield streamreader.ReadLine() }
let ImportDataSeq (path:string) : string [] list =
let mutable retval = []
let sequencer = CSVlines path
for line in sequencer do
retval <- line.Split()::retval
retval
C#代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
namespace CSVparse
{
public class CSVprocess
{
public static List<string[]> ImportDataC(string path)
{
List<string[]> retval = new List<string[]>();
using(StreamReader readfile = new StreamReader(path))
{
string line = readfile.ReadLine();
while (line != null)
{
retval.Add(line.Split());
line = readfile.ReadLine();
}
}
return retval;
}
public static List<string[]> ImportDataReadLines(string path)
{
List<string[]> retval = new List<string[]>();
IEnumerable<string> toparse = File.ReadLines(path);
foreach (string split in toparse)
{
retval.Add(split.Split());
}
return retval;
}
}
}
請注意那里的各種實現。 使用迭代器,使用序列,使用尾調用優化,while循環使用2種語言...
一個主要問題是我正在訪問磁盤,因此可以解釋一些特性,我打算重寫此代碼以從內存流中讀取(假設我沒有開始交換,這應該更加一致)
但是我所教授/閱讀的所有內容都表明,while循環/ for循環比尾部調用優化/遞歸更快,而我運行的每個實際基准都說死了。
所以我想我的問題是,我應該質疑傳統智慧嗎?
尾調用遞歸真的比.net生態系統中的循環更好嗎?
這對Mono有什么影響?
我認為差異可能來自F#和C#中的不同List
。 F#使用單鏈表(請參閱http://msdn.microsoft.com/en-us/library/dd233224.aspx ),而在C# System.Collections.Generic.List
使用,它基於數組。
對於單鏈接列表,連接速度要快得多,尤其是在解析大文件時(需要不時地分配/復制整個數組列表)。
嘗試在C#代碼中使用LinkedList
,我對結果很好奇:) ...
PS:此外,這將是何時使用分析器的一個很好的例子。 您可以輕松找到C#代碼的“熱點”......
編輯
所以,我為自己嘗試了這個:我使用了兩個相同的文件來防止緩存效果。 這些文件是3.000.000行,10次'abcdef',用逗號分隔。
主程序如下所示:
static void Main(string[] args) {
var dt = DateTime.Now;
CSVprocess.ImportDataC("test.csv"); // C# implementation
System.Console.WriteLine("Time {0}", DateTime.Now - dt);
dt = DateTime.Now;
CSVImport.ImportData("test1.csv"); // F# implementation
System.Console.WriteLine("Time {0}", DateTime.Now - dt);
}
(我也嘗試過首先執行F#實現然后執行C#...)
結果是:
在F#解決方案之后運行C#解決方案為F#版本提供相同的性能,但對C#提供4.7秒(我假設由於F#解決方案分配了大量內存)。 單獨運行每個解決方案不會改變上述結果。
使用具有6.000.000行的文件為C#解決方案提供約7秒的時間,F#解決方案產生OutOfMemoryException(我在12GB Ram的機器上運行它...)
所以對我而言,傳統的“智慧”似乎是正確的,而使用簡單循環的C#對於這類任務來說更快......
你真的, 真的 , 真的 , 真的不應該在這些結果中讀取任何東西 - 要么將整個系統作為系統測試的一種形式進行基准測試,要么從基准測試中刪除磁盤I / O. 這只會讓事情變得混亂。 采用TextReader
參數而不是物理路徑可能是更好的做法,以避免將實現鏈接到物理文件。
此外,作為微基准測試,您的測試還有一些其他缺陷:
ImportDataC
或ImportDataReadLines
嗎? 選擇是為了清晰 - 在實際應用中,不要重復實現,而是要考慮相似性並根據另一個來定義一個。 .Split(',')
在C#中.Split()
- 你打算在逗號或空格上分割嗎? 我注意到,看起來你的F#正在使用F#列表,而C#正在使用.Net List。 可能會嘗試更改F#以使用其他列表類型來獲取更多數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.