На эту тему уже много дискуссий, но я все о порке мертвых лошадей, особенно когда обнаруживаю, что они все еще дышат.
Я работал над синтаксическим анализом необычного и экзотического формата файлов, который представляет собой CSV, и для интереса я решил охарактеризовать производительность в сравнении с двумя известными мне языками .net, C # и F #.
Результаты были ... тревожными. F # выиграл, с большим отрывом, в 2 раза или более (и я на самом деле думаю, что это больше похоже на .5n, но получить реальные тесты сложно, так как я тестирую аппаратный ввод / вывод).
Отличающиеся характеристики производительности в чем-то столь же распространенном, как чтение 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 #, выигравшей немного, но сигнатуры производительности предполагают, что я бью по диску, а не разрываю оперативную память всего файла, что вызывает полу случайные скачки производительности.
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 в экосистеме .net?
Как это работает на Mono?