PowerShell で中身がランダムな大きめのファイルを高速に作る

Pocket

色々テストをしていると、ユニークなランダムの中身で、任意サイズの、ダミーファイルが欲しくなることがままある。

デバイスを全てファイルとして扱う Unix や Linux なら、 /dev/urandom 擬似デバイスを使って dd if=/dev/urandom of=out.bin bs=1M count=64 みたいに任意サイズでファイルをコピーして作れば良い。

しかし API ベースの Windows だとそういうわけにいかないので、何らかのスクリプトから API を呼んでやる必要がある。

呼ばせるスクリプトは、 Windows に標準で搭載されていて追加でインストール不要な、毎度おなじみ PowerShell が良いだろう。
作りたいランダムなファイルのサイズが小さければ、 PowerShell 上で以下のような雑なコードを実行すればよい。

$Size = 16KB;
[byte[]]$bin = &{foreach($i in 1..$Size) { Get-Random -Maximum 255 }};
[System.IO.File]::WriteAllBytes((Join-Path (Get-Location) .\out.bin), $bin);

しかし、 MiB オーダーを越えてくると、上記のコードでは速度が実用的ではなくなる。

と言うことで、出力速度も気にしながら、大きめなランダムなファイルを作成するコードを考えてみる。

コード

function Make-RandomFile ([Parameter(Mandatory=$true)][string]$OutPath, [Parameter(Mandatory=$true)][long]$FileSize, [int]$ChunkSize = 16MB) {
    $bin1 = [byte[]]::new($ChunkSize);
    $bin2 = [byte[]]::new($ChunkSize);
    $r = [System.Random]::new();
    [long]$progress = 0;
    [int]$nextLen = 0;
    $fs = [System.IO.File]::Create([System.IO.Path]::Combine($PWD, $OutPath));
    try {
        do {
            $task = $fs.WriteAsync($bin1, 0, $nextLen);
            $r.NextBytes($bin2);
            $task.Wait();
            $bin1, $bin2 = $bin2, $bin1;
            $progress += $nextLen;
            $nextLen = [System.Math]::Min([long]$ChunkSize, $FileSize - $progress);
        } while ($nextLen -gt 0);
    } finally {
        $fs.Dispose();
    }
}

上記のような関数をコンソールに貼り付けて定義し、あとは以下のように実行すれば良い。

Make-RandomFile .\out.bin -FileSize 1GB;

解説

大きなファイルを作る際、最初に挙げたような雑なコードだと、一旦全てのデータをメモリ (RAM) に置いてしまうため、メモリ不足になったり動作が遅くなったりしかねない。
そこで、適当なチャンクサイズ (上記コードだと標準では 16MiB) 毎にランダムなデータを作って、それを書き込んでいる。

また、ランダムな配列を作るのも、その配列をファイルに書き込むのも、それぞれ時間がかかるため、それらを同期的に逐次実行すると効率が悪い。
そこでバッファを2つ用意して、片方のバッファでファイルを非同期的に書き込んでいる間に、もう一方のバッファでランダムなデータを生成することで、効率を上げている。

補足

ちなみに、 .NET 5 以前の System.Random の擬似乱数生成器は、あまり乱数精度が高くないと言われているので、セキュリティ的に精度の高いランダム性であることが重要な場合は、より乱数精度の高い別の API を使った方が良いかもしれない。

とりあえず、ちょっとしたテストファイルでランダムなデータが欲しいだけなら、 System.Random で十分だと思うが。

また、ストレージの速度が十分に速い状況でより速く実行させたいなら、 Windows 10/11 標準の Windows PowerShell 5.1 より PowerShell 7.2 以降で実行したほうが、圧倒的に速いはずだ。 1


  1. Windows PowerShell 5.1 では .NET Framework 4.8 が、 PowerShell 7.2 LTS では .NET 6 が、それぞれ使われているが、 その .NET 6 以降では、擬似乱数生成器が性能の良いものに切り替わった為 (.NET 6 (Preview) における System.Random の実装変更 - 屋根裏工房改

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください