Почему printf в F# так медленно работает?
Я просто был очень удивлен тем, как медленно printf из F#. У меня есть несколько программ на C#, которые обрабатывают большие файлы данных и записывают несколько CSV-файлов. Я изначально начал с использования fprintf writer "%s,%d,%f,%f,%f,%s"
, думая, что это будет просто и достаточно эффективно.
Когда я прогонял свои приложения через профилировщик, я был поражен, увидев printf как один из действительно медленных методов.
Я изменил код, чтобы не использовать printf, и теперь производительность намного лучше. Производительность Printf убивала мою общую производительность приложения.
Чтобы привести пример, мой исходный код:
fprintf sectorWriter ""%s",%f,%f,%d,%d,"%s","%s","%s",%d,%d,%d,%d,"%s",%d,%d,%d,%d,%s,%d"
sector.Label sector.Longitude sector.Latitude sector.RNCId sector.CellId
siteName sector.Switch sector.Technology (int sector.Azimuth) sector.PrimaryScramblingCode
(int sector.FrequencyBand) (int sector.Height) sector.PatternName (int sector.Beamwidth)
(int sector.ElectricalTilt) (int sector.MechanicalTilt) (int (sector.ElectricalTilt + sector.MechanicalTilt))
sector.SectorType (int sector.Radius)
И я изменил его на следующий
seq {
yield sector.Label; yield string sector.Longitude; yield string sector.Latitude; yield string sector.RNCId; yield string sector.CellId;
yield siteName; yield sector.Switch; yield sector.Technology; yield string (int sector.Azimuth); yield string sector.PrimaryScramblingCode;
yield string (int sector.FrequencyBand); yield string (int sector.Height); yield sector.PatternName; yield string (int sector.Beamwidth);
yield string (int sector.ElectricalTilt); yield string (int sector.MechanicalTilt);
yield string (int (sector.ElectricalTilt + sector.MechanicalTilt));
yield sector.SectorType; yield string (int sector.Radius)
}
|> writeCSV sectorWriter
Вспомогательные функции
let writeDelimited delimiter (writer:TextWriter) (values:seq<string>) =
values
|> Seq.fold (fun (s:string) v -> if s.Length = 0 then v else s + delimiter + v) ""
|> writer.WriteLine
let writeCSV (writer:TextWriter) (values:seq<string>) = writeDelimited "," writer values
Я пишу файлы примерно с 30 000 строк. Ничего особенного.
4 ответа:
Я не уверен, насколько это важно, но...
Проверка кода для printf:
Https://github.com/fsharp/fsharp/blob/master/src/fsharp/FSharp.Core/printf.fs
Я вижу
И я думаю, что слово "отражение", вероятно, отвечает на этот вопрос.// The general technique used this file is to interpret // a format string and use reflection to construct a function value that matches // the specification of the format string.
printf
отлично подходит для записи простых типобезопасных выходных данных, но если вы хотите получить хороший perf во внутреннем цикле, вы можете использовать более низкоуровневый .NET API для записи выходных данных. Я не проводил свой собственный сравнительный анализ, чтобы видеть.
TextWriter
уже буферизует его выход. Я рекомендую использоватьWrite
для вывода каждого значения по одному, вместо форматирования всей строки и передачи ее вWriteLine
. На моем ноутбуке написание 100 000 строк занимает почти минуту с помощью вашей функции, в то время как с помощью следующей функции оно выполняется за полсекунды.let writeRow (writer:TextWriter) siteName (sector:Sector) = let inline write (value:'a) (delim:char) = writer.Write(value) writer.Write(delim) let inline quote s = "\"" + s + "\"" write (quote sector.Label) ',' write sector.Longitude ',' write sector.Latitude ',' write sector.RNCId ',' write sector.CellId ',' write (quote siteName) ',' write (quote sector.Switch) ',' write (quote sector.Technology) ',' write (int sector.Azimuth) ',' write sector.PrimaryScramblingCode ',' write (int sector.FrequencyBand) ',' write (int sector.Height) ',' write (quote sector.PatternName) ',' write (int sector.Beamwidth) ',' write (int sector.ElectricalTilt) ',' write (int sector.MechanicalTilt) ',' write (int (sector.ElectricalTilt + sector.MechanicalTilt)) ',' write sector.SectorType ',' write (int sector.Radius) '\n'
Теперь, когда F# 3.1 был выпущен предварительный просмотр, производительность
printf
, как утверждается, увеличилась в 40 раз.]}F# 3.1 Дополнения Компилятора/Библиотеки
Производительность Printf
Библиотека ядра F# 3.1 видит улучшенную производительность семейства printf функций для типобезопасного форматирования. Например, печать с использованием следующая строка формата теперь работает до 40x быстрее (хотя ваш точный пробег может варьировать):
sprintf "%d: %d, %x %X %d %d %s"
никаких изменений в коде не требуется, чтобы воспользуйтесь преимуществами этой улучшенной производительности, хотя вам и нужно быть использование F# 3.1 FSharp.Ядро.компонент среды выполнения dll.
Изменить: этот ответ действителен только для простых строк формата, таких как" %s "или"%d". См. комментарии ниже.
Также интересно отметить, что если вы можете сделать функцию Карри и повторно использовать ее, отражение будет выполнено только один раз. Пример:
let w = new System.IO.StringWriter() :> System.IO.TextWriter let printer = fprintf w "%d" let printer2 d = fprintf w "%d" d let print1() = for i = 1 to 100000 do printer 2 let print2() = for i = 1 to 100000 do printer2 2 let time f = let sw = System.Diagnostics.Stopwatch() sw.Start() f() printfn "%s" (sw.ElapsedMilliseconds.ToString()) time print1 time print2
Print1 занимает 48 МС на моей машине, а print2-1158 МС.