函数中的日志文件变量会占用所有内存

log file variables in function eat up all memory

我有以下简单函数,该函数在脚本中多次使用,该脚本遍历目录并检查其中文件的存在时间。

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 来修复 ram 问题,但想知道如何解决这个 ram 问题,以及为什么函数中的变量不会自动销毁。

我只是想排除 RAM 使用量是由文件前置引起的。您是否尝试过不将日志内容存储在变量中?即

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

编辑 2020 年 5 月 8 日 - 事实证明,前置(高效完成时)并不像我想象的那么慢 - 它与使用 -Append 的顺序相同。然而 又长又丑,但如果你真的需要在文件前添加,这就是这样做的方法。

我稍微修改了 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);
      }
    }
}

这是我的原始答案,展示了如何使用秒表测试时间。

当您添加到日志文件之前时,您是将整个日志文件读入内存,然后将其写回。

您真的应该使用追加 - 这会使脚本 运行 快很多。

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 s

尽管如评论所述,我很难相信这个函数会占用你的内存,你可以优化它:

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
}

Powershell 或 .Net 有垃圾收集器,因此释放内存不是即时的。 Garbage Collection in Powershell to Speed Scripts 此外,Powershell 7 中的内存管理可能更好。我尝试多次重复您的功能,但内存使用量没有超过几百兆。

可能有一些更有效的 .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