Подтвердить что ты не робот

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

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

Однако через некоторое время я немного устал ждать, пока файлы будут обработаны. (У меня есть 4gb 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 строк. Ничего особенного.

4b9b3361

Ответ 1

Я не уверен, насколько это важно, но...

Проверка кода для 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 отлично подходит для написания простого типа безопасного вывода, но если вы хотите, чтобы хороший перф во внутреннем цикле, вы можете использовать API-интерфейс более низкого уровня для записи вывода. Я не сделал свой собственный бенчмаркинг, чтобы видеть.

Ответ 2

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'

Ответ 3

Теперь, когда F # 3.1 был выпущен, производительность printf, как утверждается, увеличилась на 40x. Вы можете посмотреть на это:

F # 3.1 Добавления компилятора/библиотеки

Производительность печати

В основной библиотеке F # 3.1 наблюдается улучшение производительности семейства printf функций для безопасного форматирования. Например, печать с использованием следующая строка формата теперь работает быстрее 40x (хотя ваш точный пробег может меняться):

sprintf "%d: %d, %x %X %d %d %s" Никаких изменений в коде не требуется. воспользуйтесь этой улучшенной производительностью, хотя вам нужно быть используя компонент времени выполнения F # 3.1 FSharp.Core.dll.

Ответ 4

EDIT: этот ответ действителен только для строк простого формата, таких как "% 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 мс.