¿PowerShell es lento(mucho más lento que Python) en operaciones grandes de búsqueda/reemplazo?
performance replace (5)
En realidad, me enfrento a un problema similar en este momento. Con mi nuevo trabajo, tengo que analizar enormes archivos de texto para extraer información en función de ciertos criterios. La secuencia de comandos powershell (optimizada hasta el borde) tarda 4 horas en devolver un archivo csv totalmente procesado. Escribimos otro script en python que tomó poco menos de 1 hora ...
Por mucho que amo a powershell, me rompí el corazón. Para su diversión, intente esto: Powershell:
$num = 0
$string = "Mary had a little lamb"
while($num -lt 1000000){
$string = $string.ToUpper()
$string = $string.ToLower()
Write-Host $string
$num++
}
Pitón:
num = 0
string = "Mary had a little lamb"
while num < 1000000:
string = string.lower()
string = string.upper()
print(string)
num+=1
y desencadenar los dos trabajos. Incluso puede encapsular en el comando de medida {} para mantenerlo "científico".
Además, link , lectura loca ..
Tengo 265 archivos CSV con más de 4 millones de registros (líneas), y necesito hacer una búsqueda y reemplazo en todos los archivos CSV. Tengo un fragmento de mi código de PowerShell a continuación que hace esto, pero toma 17 minutos realizar la acción:
ForEach ($file in Get-ChildItem C:/temp/csv/*.csv)
{
$content = Get-Content -path $file
$content | foreach {$_ -replace $SearchStr, $ReplaceStr} | Set-Content $file
}
Ahora tengo el siguiente código de Python que hace lo mismo pero toma menos de 1 minuto realizarlo:
import os, fnmatch
def findReplace(directory, find, replace, filePattern):
for path, dirs, files in os.walk(os.path.abspath(directory)):
for filename in fnmatch.filter(files, filePattern):
filepath = os.path.join(path, filename)
with open(filepath) as f:
s = f.read()
s = s.replace(find, replace)
with open(filepath, "w") as f:
f.write(s)
findReplace("c:/temp/csv", "Search String", "Replace String", "*.csv")
¿Por qué el método Python es mucho más eficiente? ¿Es mi código de PowerShell ineficiente o Python es un lenguaje de programación más poderoso cuando se trata de la manipulación de texto?
Es posible que desee probar el siguiente comando:
gci C:/temp/csv/*.csv | % { (gc $_) -replace $SearchStr, $ReplaceStr | out-file $_}
Además, algunas cadenas pueden requerir caracteres de escape, por lo tanto, debe usar [regex] Escape para generar cadenas con caracteres de escape incorporados. El código se vería así:
gci C:/temp/csv/*.csv | % { (gc $_) -replace $([regex]::Escape($SearchStr)) $([regex]::Escape($ReplaceStr)) | out-file $_}
No sé Python, pero parece que estás haciendo reemplazos de cadenas literales en el script de Python. En Powershell, el operador -replace
es una expresión / búsqueda de expresiones regulares. Convertiría el Powershell al uso del método de reemplazo en la clase de cadena (o para responder a la pregunta original, creo que su Powershell es ineficiente).
ForEach ($file in Get-ChildItem C:/temp/csv/*.csv)
{
$content = Get-Content -path $file
# look close, not much changes
$content | foreach {$_.Replace($SearchStr, $ReplaceStr)} | Set-Content $file
}
EDITAR Tras una revisión adicional, creo que veo otra diferencia (quizás más importante) en las versiones. La versión de Python parece estar leyendo el archivo completo en una sola cadena. La versión de Powershell, por otro lado, está leyendo en una serie de cadenas .
La ayuda en Get-Content
menciona un parámetro ReadCount
que puede afectar el rendimiento. Establecer esta cuenta en -1 parece leer todo el archivo en una sola matriz. Esto significará que está pasando una matriz a través de la tubería en lugar de cadenas individuales, pero un simple cambio en el código tratará de eso:
# $content is now an array
$content | % { $_ } | % {$_.Replace($SearchStr, $ReplaceStr)} | Set-Content $file
Si desea leer el archivo completo en una sola cadena como parece la versión de Python, simplemente llame directamente al método .NET:
# now you have to make sure to use a FULL RESOLVED PATH
$content = [System.IO.File]::ReadAllText($file.FullName)
$content.Replace($SearchStr, $ReplaceStr) | Set-Content $file
Esto no es tan "Powershell-y" ya que usa las API de .NET directamente en lugar de los cmdlets similares, pero ponen la capacidad allí para cuando lo necesite.
Prueba este script de PowerShell. Debería funcionar mucho mejor. También se utiliza mucho menos RAM, ya que el archivo se lee en una secuencia almacenada.
$reader = [IO.File]::OpenText("C:/input.csv")
$writer = New-Object System.IO.StreamWriter("C:/output.csv")
while ($reader.Peek() -ge 0) {
$line = $reader.ReadLine()
$line2 = $line -replace $SearchStr, $ReplaceStr
$writer.writeline($line2)
}
$reader.Close()
$writer.Close()
Esto procesa un archivo, pero puede probar el rendimiento con él y, si es más aceptable, agregarlo a un bucle.
Alternativamente, puede usar Get-Content
para leer un número de líneas en la memoria, realizar el reemplazo y luego escribir el fragmento actualizado utilizando el canal de PowerShell.
Get-Content "C:/input.csv" -ReadCount 512 | % {
$_ -replace $SearchStr, $ReplaceStr
} | Set-Content "C:/output.csv"
Para exprimir un poco más el rendimiento, también puede compilar la expresión regular ( -replace
usa expresiones regulares) de la siguiente manera:
$re = New-Object Regex $SearchStr, ''Compiled''
$re.Replace( $_ , $ReplaceStr )
Veo esto mucho
$content | foreach {$_ -replace $SearchStr, $ReplaceStr}
El operador -reubicación manejará una matriz completa a la vez:
$content -replace $SearchStr, $ReplaceStr
y hacerlo mucho más rápido que iterar a través de un elemento a la vez. Sospecho que hacerlo puede acercarte más a una comparación de manzanas con manzanas.