簡體   English   中英

F#與C#性能簽名與示例代碼

[英]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#...)

結果是:

  • C#:3.7秒
  • F#:7.6秒

在F#解決方案之后運行C#解決方案為F#版本提供相同的性能,但對C#提供4.7秒(我假設由於F#解決方案分配了大量內存)。 單獨運行每個解決方案不會改變上述結果。

使用具有6.000.000行的文件為C#解決方案提供約7秒的時間,F#解決方案產生OutOfMemoryException(我在12GB Ram的機器上運行它...)

所以對我而言,傳統的“智慧”似乎是正確的,而使用簡單循環的C#對於這類任務來說更快......

你真的, 真的真的真的不應該在這些結果中讀取任何東西 - 要么將整個系統作為系統測試的一種形式進行基准測試,要么從基准測試中刪除磁盤I / O. 這只會讓事情變得混亂。 采用TextReader參數而不是物理路徑可能是更好的做法,以避免將實現鏈接到物理文件。

此外,作為微基准測試,您的測試還有一些其他缺陷:

  • 您定義了許多在基准測試期間未調用的函數。 您在測試ImportDataCImportDataReadLines嗎? 選擇是為了清晰 - 在實際應用中,不要重復實現,而是要考慮相似性並根據另一個來定義一個。
  • 你在F#中調用.Split(',')在C#中.Split() - 你打算在逗號或空格上分割嗎?
  • 您正在重新發明輪子 - 至少將您的實現與使用高階函數(又稱LINQ)的更短版本進行比較。

我注意到,看起來你的F#正在使用F#列表,而C#正在使用.Net List。 可能會嘗試更改F#以使用其他列表類型來獲取更多數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM