函数中的日志文件变量会占用所有内存
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
我有以下简单函数,该函数在脚本中多次使用,该脚本遍历目录并检查其中文件的存在时间。
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