这个话题已经有很多讨论了,但我喜欢打死马也要再鞭一下,特别是当我发现它们可能还在呼吸时。
我正在解析不寻常和奇异的CSV文件格式,并且为了好玩,我决定对比我所知道的两种.NET语言C#和F#的性能。
结果令人不安。F#赢了,而且优势很大,是2倍或更多(实际上我认为更接近于0.5n,但由于我正在测试硬件IO,所以很难得到真正的基准)。
在像读取CSV这样常见的事情中出现不同的性能特征对我来说很惊讶(请注意,系数意味着C#在非常小的文件上胜出。我做的测试越多,就越感觉C#的扩展性更差,这既让人惊讶又让人担忧,因为这可能意味着我做错了些什么)。
一些注释:Core 2 duo笔记本电脑,80GB磁盘,3GB DDR 800内存,Windows 7 64位版,.Net 4,没有开启电源选项。
30000行、5列、1个短语、每个短语10个字符或更少的数据,在第一次运行后,使用尾调用递归可以使性能提高3倍(它似乎缓存了文件)。
300000行相同的数据重复出现,尾调用递归的性能优势是2倍,F#的可变实现略微胜出,但性能特征表明我正在访问磁盘而不是将整个文件放在RAM磁盘中,这会导致半随机的性能峰值。
F#代码
请注意这里有许多种实现方式。使用迭代器、使用序列、使用尾调用优化,在两种语言中使用while循环...
一个主要的问题是我正在访问磁盘,因此一些特殊性质可以通过这个来解释。我打算重写这段代码以从内存流中读取(假设我不开始交换),这样应该更加一致。
但是我所学到/阅读到的所有内容都说while循环/for循环比尾调用优化/递归更快,而我运行的每个实际基准测试都表明相反。
那么我的问题是,我应该质疑传统智慧吗?
在.net生态系统中,尾调用递归真的比while循环更好吗?
在Mono上如何运行?
我正在解析不寻常和奇异的CSV文件格式,并且为了好玩,我决定对比我所知道的两种.NET语言C#和F#的性能。
结果令人不安。F#赢了,而且优势很大,是2倍或更多(实际上我认为更接近于0.5n,但由于我正在测试硬件IO,所以很难得到真正的基准)。
在像读取CSV这样常见的事情中出现不同的性能特征对我来说很惊讶(请注意,系数意味着C#在非常小的文件上胜出。我做的测试越多,就越感觉C#的扩展性更差,这既让人惊讶又让人担忧,因为这可能意味着我做错了些什么)。
一些注释:Core 2 duo笔记本电脑,80GB磁盘,3GB DDR 800内存,Windows 7 64位版,.Net 4,没有开启电源选项。
30000行、5列、1个短语、每个短语10个字符或更少的数据,在第一次运行后,使用尾调用递归可以使性能提高3倍(它似乎缓存了文件)。
300000行相同的数据重复出现,尾调用递归的性能优势是2倍,F#的可变实现略微胜出,但性能特征表明我正在访问磁盘而不是将整个文件放在RAM磁盘中,这会导致半随机的性能峰值。
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循环...
一个主要的问题是我正在访问磁盘,因此一些特殊性质可以通过这个来解释。我打算重写这段代码以从内存流中读取(假设我不开始交换),这样应该更加一致。
但是我所学到/阅读到的所有内容都说while循环/for循环比尾调用优化/递归更快,而我运行的每个实际基准测试都表明相反。
那么我的问题是,我应该质疑传统智慧吗?
在.net生态系统中,尾调用递归真的比while循环更好吗?
在Mono上如何运行?