我试图理解为什么在导入一个约16MB的文件作为变量时,PowerShell的内存如此之大。我可以理解该变量周围还有其他内存结构,但我只是想了解为什么它那么高。这是我在下面做的-只是任何人都可以运行的另一个脚本的简化片段。
注释/问题
我的测试代码
Invoke-WebRequest -uri "http://s3.amazonaws.com/alexa-static/top-1m.csv.zip" -OutFile C:\top-1m.csv.zip
Expand-Archive -Path C:\top-1m.csv.zip -DestinationPath C:\top-1m.csv
$alexaTopMillion = Import-Csv -Path C:\top-1m.csv
对于回答此问题的任何人:谢谢您的时间,并帮助我每天学习更多!
一般来说,iRon在对该问题的评论中的建议值得关注(具体问题在此问题后面的部分中进行了阐述):
也就是说,不要这样做:
# !! Collects ALL objects in memory, as an array.
$rows = Import-Csv in.csv
foreach ($row in $rows) { ... }
做这个:
# Process objects ONE BY ONE.
# As long as you stream to a *file* or some other output stream
# (as opposed to assigning to a *variable*), memory use should remain constant,
# except for temporarily held memory awaiting garbage collection.
Import-Csv in.csv | ForEach-Object { ... } # pipe to Export-Csv, for instance
但是,即使那样,您似乎仍然可以用非常大的文件来耗尽内存-请参见此问题-可能与尚未被垃圾回收的不再需要的对象建立内存有关;因此,周期性地调用[GC]::Collect()
在ForEach-Object
脚本块可以解决这个问题。
Import-Csv
一次收集内存中所有输出的对象,请执行以下操作:您观察到的过度内存使用来自如何实现[pscustomobject]
实例(Import-Csv
的输出类型),如本GitHub问题(强调)中所述:
内存压力很可能来自
PSNoteProperty
[[pscustomobject]
属性的实现方式]的成本。每个PSNoteProperty
属性都有48个字节的开销,因此,如果每个属性仅存储几个字节,则将变得很大。
相同的问题提出了一种减少内存消耗的解决方法(也如Wasif Hasan的答案所示):
阅读CVS的第一行,并使用来动态创建一个代表这些行的自定义类Invoke-Expression
。
注意:虽然此处安全使用,Invoke-Expression
但通常应避免使用。
如果您事先知道列结构,则可以class
按常规方式创建自定义方式,这还允许您为属性使用适当的数据类型(默认情况下为所有字符串)。例如,将适当的属性定义为[int]
(System.Int32
)可进一步减少内存消耗。
管Import-Csv
的ForEach-Object
,其将每个呼叫[pscustomobject]
创建动态创建的类,它更有效地存储数据的一个实例。
注意:此解决方法的代价是执行速度大大降低。
$csvFile = 'C:\top-1m.csv'
# Dynamically define a custom class derived from the *first* row
# read from the CSV file.
# Note: While this is a legitimate use of Invoke-Expression,
# it should generally be avoided.
"class CsvRow {
$((Import-Csv $csvFile | Select-Object -first 1).psobject.properties.Name -replace '^', '[string] $$' -join ";")
}" | Invoke-Expression
# Import all rows and convert them from [pscustomobject] instances
# to [CsvRow] instances to reduce memory consumption.
# Note: Casting the Import-Csv call directly to [CsvRow[]] would be noticeably
# faster, but increases *temporary* memory pressure substantially.
$alexaTopMillion = Import-Csv $csvFile | ForEach-Object { [CsvRow] $_ }
从长远来看,一个更好的解决方案(也将是更快的)是使Import-Csv
支持输出具有给定输出类型的已解析行,例如通过此GitHub问题中提出的-OutputType
参数。如果您对此感兴趣,请在此处表示您对提案的支持。
以下代码将正常Import-Csv
导入([pscustomobject]
s的数组)的内存使用与变通方法(自定义类实例的数组)进行比较。
测量是不精确的,因为仅查询PowerShell的进程工作内存,它可以显示后台活动的影响,但是粗略了解使用自定义类需要多少内存。
样本输出,表明自定义类的解决方法仅需要样本10列CSV输入文件的内存的大约五分之一,下面使用了大约166,000行-具体比率取决于输入行和列的数量:
MB Used Command
------- -------
384.50 # normal import…
80.48 # import via custom class…
基准代码:
# Create a sample CSV file with 10 columns about 16 MB in size.
$tempCsvFile = [IO.Path]::GetTempFileName()
('"Col1","Col2","Col3","Col4","Col5","Col6","Col7","Col8","Col9","Col10"' + "`n") | Set-Content -NoNewline $tempCsvFile
('"Col1Val","Col2Val","Col3Val","Col4Val","Col5Val","Col6Val","Col7Val","Col8Val","Col9Val","Col10Val"' + "`n") * 1.662e5 |
Add-Content $tempCsvFile
try {
{ # normal import
$all = Import-Csv $tempCsvFile
},
{ # import via custom class
"class CsvRow {
$((Import-Csv $tempCsvFile | Select-Object -first 1).psobject.properties.Name -replace '^', '[string] $$' -join ";")
}" | Invoke-Expression
$all = Import-Csv $tempCsvFile | ForEach-Object { [CsvRow] $_ }
} | ForEach-Object {
[gc]::Collect(); [gc]::WaitForPendingFinalizers() # garbage-collect first.
$before = (Get-Process -Id $PID).WorkingSet64
# Execute the command.
& $_
# Measure memory consumption and output the result.
[pscustomobject] @{
'MB Used' = ('{0,4:N2}' -f (((Get-Process -Id $PID).WorkingSet64 - $before) / 1mb)).PadLeft(7)
Command = $_
}
}
} finally {
Remove-Item $tempCsvFile
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句