提高 Powershell 性能以生成随机文件

Improve Powershell Performance to Generate a Random File

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

这是我的代码示例,在现代 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 示例)。我希望文件输出被分段写入(一次一行或多行),这样我们就永远不需要存储在内存中的输出文件大小的字符串。

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

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

减少到 ~1ms。

同意@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 与 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

如果你不介意标点符号,你可以使用这个:

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$O85F.QLvUzA{@X2-w%5(3k;BE2Qi

使用流写入器而不是 Out-File -Append 避免了 open/close 周期并将其降低到 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 数组和输出文件进行索引 - 附加打开和关闭文件是一个重大的减速。

我没有按照 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 在末尾强制换行,这就是每次写入文件时需要删除 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"

}

如果您不关心所有块都是 运行dom,您可以简单地调用()一次生成 1kb 块。这会大大提高速度,但不会使整个文件 运行dom.

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"

}

测量-命令所有这些更改以生成一个 10mb 文件:

执行 New-RandomFile:35.7688241 秒。

执行 New-RandomFile_1kChunks:25.1463777 秒。

执行 New-RandomFile_Fast:1.1626236 秒。