переменные файла журнала в функции съедают всю память - PullRequest
3 голосов
/ 08 мая 2020

У меня есть следующая простая функция, которая используется несколько раз в сценарии, который выполняет итерацию по каталогу и проверяет возраст файлов в нем.

function log($Message) {
  $logFilePath = 'C:\logPath\myLog.txt'

  $date = Get-Date -Format 'yyyyMMddHHmmss'
  $logMessage = "{0}_{1}" -f $date,$Message
  if(Test-Path -Path $logFilePath) {
    $logFileContent = Get-Content -Path $logFilePath
  } else {
    $logFileContent = ''
  }
  $logMessage,$logFileContent | Out-File -FilePath $logFilePath
}

Я выяснил, что это съедает всю память . я не понимаю почему. Я думал, что объем переменных в функции уничтожается после запуска функции. Я исправил проблему с оперативной памятью, добавив Remove-Variable logMessage,logFileContent,logFilePath,date в самый конец функции, но хотел бы знать, как эта проблема с оперативной памятью может быть решена в противном случае и почему переменные внутри функции не уничтожаются автоматически.

Ответы [ 3 ]

3 голосов
/ 08 мая 2020

Powershell или. Net имеет сборщик мусора, поэтому освобождение памяти не происходит мгновенно. Сборка мусора в Powershell для ускорения скриптов Также управление памятью, вероятно, лучше в Powershell 7. Я пытался повторить вашу функцию много раз, но использование памяти не go превышало несколько сотен мегабайт.

Вероятно, есть более эффективный. net способ добавить строку к файлу: Добавить "!" в начало первой строки файла

У меня есть странный способ добавить строку. Я не уверен, насколько хорошо это будет работать с большим файлом. С файлом размером 700 мегабайт память рабочего набора оставалась на уровне 76 мегабайт.

$c:file
one
two
three

$c:file = 'pre',$c:file

$c:file
pre
one
two
three

ps powershell

Handles NPM(K) PM(K) WS(K) CPU(s)   Id SI ProcessName
------- ------ ----- ----- ------   -- -- -----------
    607     27 67448 76824   1.14 3652  3 powershell

0 голосов
/ 08 мая 2020

Хотя, как прокомментировано, я с трудом могу поверить, что эта функция поглощает вашу память, вы можете оптимизировать ее:

function log ([string]$Message) {
    $logFilePath = 'C:\logPath\myLog.txt'
    # prefix the message with the current date
    $Message = "{0:yyyyMMddHHmmss}_{1}" -f (Get-Date), $Message
    if (Test-Path -Path $logFilePath -PathType Leaf) {
        # newest log entry on top: append current content
        $Message = "{0}`r`n{1}" -f $Message, (Get-Content -Path $logFilePath -Raw)
    }

    Set-Content -Path $logFilePath -Value $Message
}
0 голосов
/ 08 мая 2020

Я просто хочу исключить использование ОЗУ из-за добавления к файлу. Вы пробовали не сохранять содержимое журнала в переменной? т.е.

$logMessage,(Get-Content -Path $logFilePath) | Out-File -FilePath $logFilePath

Edit 5/8/20 - Оказывается, что добавление в начало (при эффективном выполнении) не так медленно, как я думал - оно в том же порядке, что и использование -Append. Однако код (на который указывает js2010) длинный и некрасивый, но если вам действительно нужно добавить в файл, это способ сделать это.

Я изменил код OP немного, чтобы автоматически вставить новую строку.

function log-prepend{
  param(
    $content,
    $filePath = 'C:\temp\myLogP.txt'
  )
    $file = get-item $filePath
    if(!$file.exists){
      write-error "$file does not exist";
      return;
    }
    $filepath = $file.fullname;
    $tmptoken = (get-location).path + "\_tmpfile" + $file.name;
    write-verbose "$tmptoken created to as buffer";
    $tfs = [System.io.file]::create($tmptoken);
    $fs = [System.IO.File]::Open($file.fullname,[System.IO.FileMode]::Open,[System.IO.FileAccess]::ReadWrite);
    try{
      $date = Get-Date -Format 'yyyyMMddHHmmss'
      $logMessage = "{0}_{1}`r`n" -f $date,$content
      $msg = $logMessage.tochararray();
      $tfs.write($msg,0,$msg.length);
      $fs.position = 0;
      $fs.copyTo($tfs);
    }
    catch{
      write-verbose $_.Exception.Message;
    }
    finally{

      $tfs.close();
      $fs.close();
      if($error.count -eq 0){
        write-verbose ("updating $filepath");
        [System.io.File]::Delete($filepath);
        [System.io.file]::Move($tmptoken,$filepath);
      }
      else{
        $error.clear();
        [System.io.file]::Delete($tmptoken);
      }
    }
}

Вот мой исходный ответ, который показывает, как проверить время с помощью секундомера.

Когда вы добавляете файл журнала в начало, вы ' повторное чтение всего файла журнала в память, а затем запись его обратно.

Вам действительно следует использовать append - это заставит скрипт работать намного быстрее.

function log($Message) {
  $logFilePath = 'C:\logPath\myLog.txt'

  $date = Get-Date -Format 'yyyyMMddHHmmss'
  $logMessage = "{0}_{1}" -f $date,$Message
  $logMessage | Out-File -FilePath $logFilePath -Append
}

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

function logAppend($Message) {
  $logFilePath = 'C:\temp\myLogA.txt'

  $date = Get-Date -Format 'yyyyMMddHHmmss'
  $logMessage = "{0}_{1}" -f $date,$Message
  $logMessage | Out-File -FilePath $logFilePath -Append
}

function logPrepend($Message) {
  $logFilePath = 'C:\temp\myLogP.txt'

  $date = Get-Date -Format 'yyyyMMddHHmmss'
  $logMessage = "{0}_{1}" -f $date,$Message
  if(Test-Path -Path $logFilePath) {
    $logFileContent = Get-Content -Path $logFilePath
  } else {
    $logFileContent = ''
  }
  $logMessage,$logFileContent | Out-File -FilePath $logFilePath 
}

$processes = Get-Process

$stopwatch =  [system.diagnostics.stopwatch]::StartNew()
foreach ($p in $processes)
{
  logAppend($p.ProcessName)
}

$stopwatch.Stop()
$stopwatch.Elapsed


$stopwatch =  [system.diagnostics.stopwatch]::StartNew()
foreach ($p in $processes)
{
  logPrepend($p.ProcessName)
}

$stopwatch.Stop()
$stopwatch.Elapsed

Я запускал это несколько раз, пока не получил несколько тысяч строк в файле журнала . Переходя от: 1603 к 1925 строкам, мои результаты были:

Дополнение: 7.0167008 с Предоставление: 21.7046793 с

...