Скорость выдачи при операции -match для большого текстового файла - PullRequest
1 голос
/ 02 апреля 2019

У меня есть база данных из 36 .log-файлов, которые мне нужно предварительно обработать, чтобы загрузить их в фрейм данных pandas для визуализации данных в рамках python.

Чтобы привести пример одной строки в одном из файлов .log:

[16:24:42]: Downloaded 0 Z_SYSTEM_FM traces from DEH, clients (282) from 00:00:00,000 to 00:00:00,000 

Из нескольких источников и постов здесь я считаю, что следующий код будет наиболее эффективным:

foreach ($f in $files){

    $date = $f.BaseName.Substring(22,8)

    ((Get-Content $f) -match "^.*\bDownloaded\b.*$") -replace "[[]", "" -replace "]:\s", " " 
    -replace "Downloaded " -replace "Traces from " -replace ",.*" -replace "$", " $date" 
    | add-content CleanedLogs.txt

}

Переменная $date содержит дату, соответствующий лог-файл регистрируется.

Я не могу изменить вводимые текстовые данные. Я попытался прочитать в 1,55 ГБ, используя -raw, но мне не удалось разделить полученную единственную строку после обработки всех операций. Кроме того, я попытался использовать больше выражений регулярных выражений, но общее время выполнения не уменьшилось. Может быть, есть способ использовать grep для этих операций?

Может быть, у кого-то есть гениальная настройка, чтобы ускорить эту операцию. На данный момент эта операция занимает около 20 минут. Большое спасибо!

Ответы [ 3 ]

1 голос
/ 02 апреля 2019

У меня была похожая проблема в прошлом. Короче говоря, использование .NET напрямую намного быстрее при использовании больших типов файлов. Вы можете узнать больше, прочитав соображения производительности .

Самый быстрый способ, вероятно, будет с использованием IO.FileStream. Например:

$File = "C:\Path_To_File\Logs.txt"
$FileToSave = "C:\Path_To_File\result.txt"
$Stream = New-Object -TypeName IO.FileStream -ArgumentList ($File), ([System.IO.FileMode]::Open), ([System.IO.FileAccess]::Read), ([System.IO.FileShare]::ReadWrite)
$Reader = New-Object -TypeName System.IO.StreamReader -ArgumentList ($Stream, [System.Text.Encoding]::ASCII, $true)
$Writer = New-Object -TypeName System.IO.StreamWriter -ArgumentList ($FileToSave)
while (!$Reader.EndOfStream)
{
    $Box = $Reader.ReadLine()
    if($Box -match "^.*\bDownloaded\b.*$")
    {
        $ReplaceLine = $Box -replace "1", "1234" -replace "[[]", ""
        $Writer.WriteLine($ReplaceLine)
    }
}
$Reader.Close()
$Writer.Close()
$Stream.Close()

Вы сможете легко отредактировать приведенный выше код для своих нужд. Для получения списка файлов вы можете использовать Get-ChildItem .

Также я советую вам прочитать эту запись stackoverflow.

1 голос
/ 02 апреля 2019

Ключ к повышению производительности:

  • Избегайте использования конвейера и командлетов, особенно для файлового ввода-вывода (Get-Content, Add-Content)
    • Использованиевместо этого используются методы типа System.IO.File.
  • Избегайте зацикливания в коде PowerShell.
    • Вместо этого, операторы с поддержкой цепочек, такие как -match и -replace, - которые вы уже делаете.
    • Объедините свои регулярные выражения, чтобы сделать меньше -replace вызовов.
    • Используйте скомпилированные регулярные выражения.

Чтобы собрать все это вместе:

# Create precompiled regexes.
# Note: As written, they make the matching that -replace performs
#       case-*sensitive* (and culture-sensitive), 
#       which speeds things up slightly.
#       If you need case-*insensitive* matching, use option argument
#       'Compiled, IgnoreCase' instead.
$reMatch    = New-Object regex '\bDownloaded\b', 'Compiled'
$reReplace1 = New-Object regex 'Downloaded |Traces from |\[', 'Compiled'
$reReplace2 = New-Object regex '\]:\s', 'Compiled'
$reReplace3 = New-Object regex ',.*', 'Compiled'

# The platform-appropriate newline sequence.
$nl = [Environment]::NewLine

foreach ($f in $files) {

  $date = $f.BaseName.Substring(22,8)

  # Read all lines into an array, filter and replace, then join the
  # resulting lines with newlines and append the resulting single string
  # to the log file.
  [IO.File]::AppendAllText($PWD.ProviderPath + '/CleanedLogs.txt',
    ([IO.File]::ReadAllLines($f.FullName) -match
      $reMatch -replace 
        $reReplace1 -replace 
          $reReplace2, ' ' -replace 
            $reReplace3, " $date" -join 
              $nl) + $nl
  )

}

Обратите внимание, что каждый файл должен вписываться впамять в целом как массив строк плюс ее часть (как массив и как одна многострочная строка), размер которой зависит от того, сколько строк отфильтровано.

0 голосов
/ 02 апреля 2019

Возможно, это ускорит ваши дела:

$outFile = Join-Path -Path $PSScriptRoot -ChildPath 'CleanedLogs.txt'
$files   = Get-ChildItem -Path '<YOUR ROOTFOLDER>' -Filter '*.txt' -File
foreach ($f in $files){
    $date = $f.BaseName.Substring(22,8)
    [string[]]$lines = ([System.IO.File]::ReadAllLines($f.FullName) | Where-Object {$_ -match '^.*\bDownloaded\b.*$'} | ForEach-Object {
        ($_ -replace '\[|Downloaded|Traces from|,.*', '' -replace ']:\s', ' ' -replace '\s+', ' ') + " $date"
    })
    [System.IO.File]::AppendAllLines($outFile, $lines)
}
...