Почему printf в F# так медленно работает?


Я просто был очень удивлен тем, как медленно printf из F#. У меня есть несколько программ на C#, которые обрабатывают большие файлы данных и записывают несколько CSV-файлов. Я изначально начал с использования fprintf writer "%s,%d,%f,%f,%f,%s", думая, что это будет просто и достаточно эффективно.

Однако через некоторое время мне уже надоело ждать, пока файлы будут обработаны. (У меня есть 4 ГБ XML-файлов, чтобы пройти и записать записи из них.).

Когда я прогонял свои приложения через профилировщик, я был поражен, увидев 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 16

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 МС.