繁体   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