【问题标题】:Improve Powershell Performance to Generate a Random File提高 Powershell 性能以生成随机文件
【发布时间】:2015-01-27 22:11:22
【问题描述】:

我想使用 Powershell 创建一个随机文本文件,用于基本系统测试(上传、下载、校验和等)。 我使用了以下文章,并提出了我自己的代码 sn-p 来创建一个随机文本文件,但性能很糟糕。

这是我的代码示例,在现代 Windows 7 戴尔笔记本电脑上生成 1MB 随机文本文件大约需要 227 秒。运行时间是使用 Measure-Command cmdlet 确定的。我在不同的系统负载期间重复了几次测试,得到了类似的长时间运行结果。

# select characters from 0-9, A-Z, and a-z
$chars = [char[]] ([char]'0'..[char]'9' + [char]'A'..[char]'Z' + [char]'a'..[char]'z')
# write file using 128 byte lines each with 126 random characters
1..(1mb/128) | %{-join (1..126 | %{get-random -InputObject $chars }) } `
  | out-file test.txt -Encoding ASCII

我正在寻找讨论为什么此代码性能不佳的答案以及我可以进行简单更改以改进运行时以生成类似的随机文本文件的建议( 126 个随机字母数字字符的 ASCII 文本行 - 128 个字节,带有“\r\n” EOL,输出文件为偶数兆字节,例如上述 1MB 示例)。我希望将文件输出分段写入(一次一行或多行),这样我们就不需要存储在内存中的输出文件大小的字符串。

【问题讨论】:

  • 使用来自@mjolinor 的技术,我们将系统上的运行时间减少到每 MB 大约 30 秒。为了改进这一点,我想我可能想使用 Powershell 以外的语言 - 测试其他一些针对相同输出要求的文件编写建议产生了微小的改进。

标签: file powershell random


【解决方案1】:

同意@dugas 的观点,即瓶颈是为每个字符调用Get-Random

如果您增加字符数组集并使用Get-Random 的-count 属性,您应该能够获得几乎相同的随机性。

如果您有 V4,.foreach 方法比 foreach-object 快得多。

还将Out-File 换成Add-Content,这也应该有所帮助。

# select characters from 0-9, A-Z, and a-z
$chars = [char[]] ([char]'0'..[char]'9' + [char]'A'..[char]'Z' + [char]'a'..[char]'z')
$chars = $chars * 126
# write file using 128 byte lines each with 126 random characters
(1..(1mb/128)).foreach({-join (Get-Random $chars -Count 126) | add-content testfile.txt }) 

在我的系统上大约 32 秒完成。

编辑:Set-Content vs Out-File,使用生成的测试文件:

$x = Get-Content testfile.txt

(Measure-Command {$x | out-file testfile1.txt}).totalmilliseconds
(Measure-Command {$x | Set-Content testfile1.txt}).totalmilliseconds

504.0069
159.0842

【讨论】:

  • 我喜欢通过重复增加字符选择集大小的想法。我同意生成的随机内容应该是相似的。有趣地提到了 Powershell v4 和 .foreach 方法——我也会尝试一下。您是否有关于为什么 Add-Content 可能比 Out-File 更快的参考?我可能会根据 StephenP 的 StreamWriter 建议进行基准测试。
  • 请参阅:stackoverflow.com/questions/10655788/… 以讨论差异。 Set-Content 对文件持有写锁,避免@StephenP 提到的重复文件打开和关闭。
  • 仅通过增加选择集大小并用一次调用 (get-random $chars -count 126) 替换内部循环,运行时间就减少了 93%。我对 Add-Content 与 Out-File 的测试没有那么令人印象深刻,当我用 Add-Content 替换 Out-File ASCII 时,只节省了大约 4% 的时间。我喜欢单次调用 get-random 并增加选择集大小 - 使代码更短更清晰,同时更快地创建随机文件。
  • 此解决方案将 $chars 的大小从 62 增加到 7812。由于 Get-Random 以非顺序顺序从输入对象中获取随机元素,因此仅使用 @987654332 不是一个好主意@ 为计数/除数?这样,您每次都会以随机顺序获得最大数量的元素。
【解决方案2】:

如果你对标点符号没问题,你可以使用这个:

Add-Type -AssemblyName System.Web
#get a random filename in the present working directory
$fn = [System.IO.Path]::Combine($pwd, [GUID]::NewGuid().ToString("N") + '.txt')
#set number of iterations
$count = 1mb/128
do{
  #Write the 1267 chars plus eol
  [System.Web.Security.Membership]::GeneratePassword(126,0) | Out-File $fn -Append ascii
  #decrement the counter
  $count--
}while($count -gt 0)

这可以让您达到大约 7 秒。样本输出:

0b5rc@EXV|e{kftc+1+Xn$-c%-*9q_9L}p=I=k@zrDg@HaJDcl}B(38i&m{lV@vlq%5h/a?m2X!yo]qs0=pEw:Tn4wb5F$k$O85$8F.QLvUzA{@X2-w%5(3k;BE2Qi

使用流写入器而不是 Out-File -Append 可避免打开/关闭周期并将其缩短至 62 毫秒。

Add-Type -AssemblyName System.Web
#get a random filename in the present working directory
$fn = [System.IO.Path]::Combine($pwd, [GUID]::NewGuid().ToString("N") + '.txt')
#set number of iterations
$count = 1mb/128
#create a filestream
$fs = New-Object System.IO.FileStream($fn,[System.IO.FileMode]::CreateNew)
#create a streamwriter
$sw = New-Object System.IO.StreamWriter($fs,[System.Text.Encoding]::ASCII,128)
do{
     #Write the 1267 chars plus eol
     $sw.WriteLine([System.Web.Security.Membership]::GeneratePassword(126,0))
     #decrement the counter
     $count--
}while($count -gt 0)
#close the streamwriter
$sw.Close()
#close the filestream
$fs.Close()

您还可以使用字符串生成器和 GUID 来生成伪随机数和小写字母。

#get a random filename in the present working directory
$fn = [System.IO.Path]::Combine($pwd, [GUID]::NewGuid().ToString("N") + '.txt')
#set number of iterations
$count = 1mb/128
#create a filestream
$fs = New-Object System.IO.FileStream($fn,[System.IO.FileMode]::CreateNew)
#create a streamwriter
$sw = New-Object System.IO.StreamWriter($fs,[System.Text.Encoding]::ASCII,128)
do{
    $sb = New-Object System.Text.StringBuilder 126,126
    0..3 | %{$sb.Append([GUID]::NewGuid().ToString("N"))} 2> $null
    $sw.WriteLine($sb.ToString())
    #decrement the counter
    $count--
}while($count -gt 0)
#close the streamwriter
$sw.Close()
#close the filestream
$fs.Close()

这大约需要 4 秒并生成以下示例:

1fef6ccabc624e4dbe13a0415764fd2c58aa873377c7465eaecabdf6ba6fdf71c55496600a374c4c8cff75be46b1fe474230231ffccc4e3aa2753391afb32c

如果您想使用与示例中相同的字符,您可以使用以下方法:

#get a random filename in the present working directory
$fn = [System.IO.Path]::Combine($pwd, [GUID]::NewGuid().ToString("N") + '.txt')
#array of valid chars
$chars = [char[]] ([char]'0'..[char]'9' + [char]'A'..[char]'Z' + [char]'a'..[char]'z')
#create a random object
$rand = New-Object System.Random
#set number of iterations
$count = 1mb/128
#get length of valid character array
$charslength = $chars.length
#create a filestream
$fs = New-Object System.IO.FileStream($fn,[System.IO.FileMode]::CreateNew)
#create a streamwriter
$sw = New-Object System.IO.StreamWriter($fs,[System.Text.Encoding]::ASCII,128)
do{
    #get 126 random chars This is the major slowdown
    $randchars = 1..126 | %{$chars[$rand.Next(0,$charslength)]}
    #Write the 1267 chars plus eol
    $sw.WriteLine([System.Text.Encoding]::ASCII.GetString($randchars))
    #decrement the counter
    $count--
}while($count -gt 0)
#close the streamwriter
$sw.Close()
#close the filestream
$fs.Close()

这需要大约 27 秒并生成以下示例:

Fev31lweOXaYKELzWOo1YJn8LpZoxonWjxQYhgZbR62EmgjHit5J1LrvqniBB7hZj4pNonIpoCZSHYLf5H63iUUN6UhtyOQKPSViqMTvbGUomPeIR36t1drEZSHJ6O

索引 char 数组和输出文件 - 每次都追加打开和关闭文件会大大降低速度。

【讨论】:

  • 对伪或半随机序列的密码和 GUID 生成器的有趣使用。我也很欣赏有关使用 StreamWriter 而不是 Powershell 文件输出 cmdlet 的提示 - 我肯定会尝试这种类型的输出以获得一些性能提升。我还将考虑按照 dugas 和 mjolinor 的建议减少对随机数生成器的独立调用次数。
【解决方案3】:

瓶颈之一是在循环中调用 get-random cmdlet。在我的机器上,加入大约需要 40 毫秒。如果您更改为:

%{ -join ((get-random -InputObject $chars -Count 62) + (get-random -InputObject $chars -Count 62) + (get-random -InputObject $chars -Count 2)) }

减少到~1ms。

【讨论】:

  • 好点,我会尽量减少对随机数生成器的调用次数。我感谢您通过连接三个调用的输出来获得超过 $chars.count 的创造性方法。感谢您就我的问题发表第一篇文章 :-)
【解决方案4】:

我没有按照 mjolinor 的建议使用 Get-Random 生成文本,而是使用 GUID 提高了速度。

Function New-RandomFile {
    Param(
        $Path = '.', 
        $FileSize = 1kb, 
        $FileName = [guid]::NewGuid().Guid + '.txt'
        ) 
    (1..($FileSize/128)).foreach({-join ([guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid -Replace "-").SubString(1, 126) }) | set-content "$Path\$FileName"
}

我已经使用 Measure-Command 运行了这两个版本。原始代码耗时 1.36 秒。

这个耗时 491 毫秒。运行:

New-RandomFile -FileSize 1mb

更新:

我已经更新了我的函数以使用 ScriptBlock,因此你可以用任何你想要的方法替换 'NewGuid()' 方法。

在这种情况下,我制作了 1kb 的块,因为我知道我从不创建更小的文件。这大大提高了我的功能速度!

Set-Content 会在末尾强制使用 NewLine,这就是为什么每次写入文件时都需要删除 2 个字符的原因。我已将其替换为 [io.file]::WriteAllText()。

Function New-RandomFile_1kChunks {
    Param(
        $Path = (Resolve-Path '.').Path, 
        $FileSize = 1kb, 
        $FileName = [guid]::NewGuid().Guid + '.txt'
        ) 

    $Chunk = { [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid -Replace "-" }

    $Chunks = [math]::Ceiling($FileSize/1kb)

    [io.file]::WriteAllText("$Path\$FileName","$(-Join (1..($Chunks)).foreach({ $Chunk.Invoke() }))")

    Write-Warning "New-RandomFile: $Path\$FileName"

}

如果您不关心所有块都是随机的,您可以简单地 Invoke() 生成 1kb 块一次。这会大大提高速度,但不会使整个文件随机。

Function New-RandomFile_Fast {
    Param(
        $Path = (Resolve-Path '.').Path, 
        $FileSize = 1kb, 
        $FileName = [guid]::NewGuid().Guid + '.txt'
        ) 

    $Chunk = { [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid +
               [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid + [guid]::NewGuid().Guid -Replace "-" }
    $Chunks = [math]::Ceiling($FileSize/1kb)
    $ChunkString = $Chunk.Invoke()

    [io.file]::WriteAllText("$Path\$FileName","$(-Join (1..($Chunks)).foreach({ $ChunkString }))")

    Write-Warning "New-RandomFile: $Path\$FileName"

}

Measure-Command 所有这些更改以生成 10mb 文件:

执行 New-RandomFile:35.7688241 秒。

执行 New-RandomFile_1kChunks:25.1463777 秒。

执行 New-RandomFile_Fast:1.1626236 秒。

【讨论】:

    猜你喜欢
    • 2015-12-30
    • 2014-05-29
    • 2012-04-28
    • 2017-02-05
    • 1970-01-01
    • 2015-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多