Как правило, говоря, совет iRon в комментарии к вопросу заслуживает внимания (конкретный вопрос c рассматривается в следующем разделе): 1005 *
Чтобы поддерживать низкий уровень использования памяти, используйте потоковое объектов в конвейере , а не , сначала собирая их в памяти - если возможно:
То есть вместо этого:
# !! 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
Однако, даже тогда, казалось бы, может закончиться памяти с очень большими файлами - см. этот вопрос - возможно , связанный с накоплением памяти из ненужных объектов, которые еще не были мусором -collected; поэтому, периодический вызов [GC]::Collect()
в блоке сценария ForEach-Object
может решить проблему.
Если вам do необходимо собрать все объекты, выведенные с помощью Import-Csv
в памяти за один раз :
Использование неправильного памяти, которое вы наблюдаете, происходит из того, как [pscustomobject]
экземпляров (тип вывода Import-Csv
) ) реализованы , как обсуждалось в этой проблеме GitHub (выделение добавлено):
Скорее всего, нехватка памяти возникает из-за стоимости PSNoteProperty
[то есть как [pscustomobject]
свойства реализованы]. Каждый PSNoteProperty
имеет накладные расходы в 48 байтов , поэтому , когда вы просто сохраняете несколько байтов на свойство, это становится огромным .
Та же проблема предлагает обходной путь для уменьшения потребления памяти (как также показано в ответ Васифа Хасана ):
Считайте первую строку 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
поддерживает вывод проанализированных строк с данным типом вывода , скажем, через параметр -OutputType
, как предложено в этом выпуске GitHub .
Если это представляет интерес покажите свою поддержку этому предложению.
Тесты использования памяти:
Следующий код сравнивает использование памяти с обычным Import-Csv
импортом (массивом [pscustomobject]
с ) к обходному пути (массив экземпляров пользовательского класса).
Измерение не является точным, поскольку рабочая память процесса PowerShell просто запрашивается, что может показать влияние фоновых операций, но дает приблизительное представление о том, насколько меньше памяти требуется пользовательскому классу.
Пример вывода, который показывает, что обходной путь пользовательского класса требует только около одной 5-й памяти с образцом входного файла CSV с 10 столбцами и примерно 166 000 строк, используемых ниже - коэффициент c зависит от количества входных данных строки и столбцы:
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
}