我正在研究生成一个充满随机字节的文件(750 MB)。我在一个单独的线程中使用的代码如下所示:
我分配了一个大小的缓冲区,因为在磁盘上写入会消耗更多时间:
function Generate(buf:Pointer):DWORD;stdcall;
var
i:DWORD;
begin
for i := 0 to keysize -1 do
PByte(DWORD(buf) + i)^ := Random(256);
Result:=0;
end;
问题是这个过程完成需要很长时间。有什么想法更快的方法?如果没有其他选择,我会尝试在汇编中实现它。
答案 0 :(得分:23)
这听起来像是一个很好的练习题,所以我继续实施并行解决方案。它使用稍微超过3秒来生成750 MB文件,并在其工作期间使用超过90%的CPU。 (SSD磁盘也有帮助。在RAID0磁盘对上生成文件需要3.5秒,在较慢的512 GB磁盘上生成文件需要4秒。)
所有重用的代码都可以使用OpenBSD许可证(几乎“按照您的意愿使用”):DSiWin32,GpStuff,GpRandomGen,Otl*。
uses
DSiWin32,
GpStuff,
GpRandomGen,
OtlCommon,
OtlCollections,
OtlParallel;
{$R *.dfm}
procedure FillBuffer(buf: pointer; bufSize: integer; randomGen: TGpRandom);
var
buf64: PInt64;
buf8 : PByte;
i : integer;
rnd : int64;
begin
buf64 := buf;
for i := 1 to bufSize div SizeOf(int64) do begin
buf64^ := randomGen.Rnd64;
Inc(buf64);
end;
rnd := randomGen.Rnd64;
buf8 := PByte(buf64);
for i := 1 to bufSize mod SizeOf(int64) do begin
buf8^ := rnd AND $FF;
rnd := rnd SHR 8;
Inc(buf8);
end;
end; { FillBuffer }
procedure CreateRandomFile(fileSize: integer; output: TStream);
const
CBlockSize = 1 * 1024 * 1024 {1 MB};
var
buffer : TOmniValue;
lastBufferSize: integer;
memStr : TMemoryStream;
numBuffers : integer;
outQueue : IOmniBlockingCollection;
begin
outQueue := TOmniBlockingCollection.Create;
numBuffers := (fileSize - 1) div CBlockSize + 1;
lastBufferSize := (fileSize - 1) mod CBlockSize + 1;
Parallel.ForEach(1, numBuffers).NoWait
.NumTasks(Environment.Process.Affinity.Count)
.OnStop(
procedure
begin
outQueue.CompleteAdding;
end)
.Initialize(
procedure(var taskState: TOmniValue)
begin
taskState := TGpRandom.Create;
end)
.Finalize(
procedure(const taskState: TOmniValue)
begin
taskState.AsObject.Free;
end)
.Execute(
procedure(const value: integer; var taskState: TOmniValue)
var
buffer : TMemoryStream;
bytesToWrite: integer;
begin
if value = numBuffers then
bytesToWrite := lastBufferSize
else
bytesToWrite := CBlockSize;
buffer := TMemoryStream.Create;
buffer.Size := bytesToWrite;
FillBuffer(buffer.Memory, bytesToWrite, taskState.AsObject as TGpRandom);
outQueue.Add(buffer);
end);
for buffer in outQueue do begin
memStr := buffer.AsObject as TMemoryStream;
output.CopyFrom(memStr, 0);
FreeAndNil(memStr);
end;
end;
procedure TForm43.btnRandomClick(Sender: TObject);
var
fileStr: TFileStream;
time : int64;
begin
time := DSiTimeGetTime64;
try
fileStr := TFileStream.Create('e:\0\random.dat', fmCreate);
try
CreateRandomFile(750*1024*1024, fileStr);
finally FreeAndNil(fileStr); end;
finally Caption := Format('Completed in %d ms', [DSiElapsedTime64(time)]); end;
end;
编辑:在这种情况下使用ForEach并不是一个非常优雅的解决方案,所以我使用Parallel.ParallelTask和更好的IOmniCounter增强了OmniThreadLibrary。使用SVN中的版本993(或更新版本),您可以解决此多生产者 - 单一消费者问题,如下所示。
procedure CreateRandomFile(fileSize: integer; output: TStream);
const
CBlockSize = 1 * 1024 * 1024 {1 MB};
var
buffer : TOmniValue;
memStr : TMemoryStream;
outQueue : IOmniBlockingCollection;
unwritten: IOmniCounter;
begin
outQueue := TOmniBlockingCollection.Create;
unwritten := CreateCounter(fileSize);
Parallel.ParallelTask.NoWait
.NumTasks(Environment.Process.Affinity.Count)
.OnStop(Parallel.CompleteQueue(outQueue))
.Execute(
procedure
var
buffer : TMemoryStream;
bytesToWrite: integer;
randomGen : TGpRandom;
begin
randomGen := TGpRandom.Create;
try
while unwritten.Take(CBlockSize, bytesToWrite) do begin
buffer := TMemoryStream.Create;
buffer.Size := bytesToWrite;
FillBuffer(buffer.Memory, bytesToWrite, randomGen);
outQueue.Add(buffer);
end;
finally FreeAndNil(randomGen); end;
end
);
for buffer in outQueue do begin
memStr := buffer.AsObject as TMemoryStream;
output.CopyFrom(memStr, 0);
FreeAndNil(memStr);
end;
end;
EDIT2:关于此问题的较长篇博文:Life after 2.1: Parallel data production (Introducing Parallel.Task)
答案 1 :(得分:6)
我不知道Delphi,但可能是在Random(256)
电话上浪费时间。你为什么不把伪随机的东西手工编码到
n = (n * 1103515245 + 12345) & 0xff;
让n
从某处开始并使用递归(例如此递归)来生成下一个n
。它不是 随机,但它应该用于创建随机文件。
修改强>
一些思考的食物。如果您正在创建此文件,希望它不易被压缩,那么上面概述的方法并不是那么好,因为& 0xff
部分。这样做更好
n = (n * 1103515245 + 12345) & 0x7fffffff;
因为0x7fffffff = 2147483647
是素数。并存储n
的确切较大值,并在分配时执行n % 256
。我对这些常量的选择有一些好的运行,并且更喜欢它作为内置.NET替代品的熵源,因为它的速度快了很多倍,而且你很少需要真正随机或更好的伪随机数。
答案 2 :(得分:4)
问题是Random()
的熵有限。如果您生成 750MiB 数据,您将只获得2^31
个可能的不同字符串中的一个(因为这是RNG的周期),而不是2^(750*1024*1024*8)
,这将是如果发电机是完美的那样的话。这是一个巨大的差异。
简而言之,如果您使用Random(),您的数据根本不是随机的。任何人都可以从4MB样本/文件中猜出所有750MiB的数据。
你必须采取不同的方式。如果你有linux机器,请从程序中执行以下命令:
dd if=/dev/urandom of=file.img bs=1M count=750
在我的旧笔记本电脑上完成了不到半分钟。
答案 3 :(得分:3)
由于随机函数无论如何都没有良好的分布,您可以将代码减少近四倍,具体如下:
function Generate(buf: Pointer): DWORD; stdcall;
var
i: DWORD;
p: PInteger;
begin
p := buf;
for i := 0 to (keysize div 4) - 1 do begin
p^ := Random(MaxInt);
Inc(p);
end;
Result := 0;
end;
更新:我的系统上面的代码需要大约650毫秒,而原始代码大约需要3秒。
答案 4 :(得分:2)
您可以尝试RandomRange(Low(Integer), High(Integer))
,看看它是否有效。这将一次生成4个字节的随机数据(请注意它已经签名,我假设整数是4个字节,但是The Integer type is an Integer whose size is not guaranteed
(http://www.delphibasics.co.uk/RTL .ASP?名称=整数)。
答案 5 :(得分:2)
var
F: TFileStream;
I: Cardinal;
index: integer;
a: array[1..10240] of Cardinal;
IndexA: integer;
T1: TDateTime;
begin
T1 := Now;
F := TFileStream.Create( 'D:\filler.fil', fmCreate);
try
for index := 1 to (650 * MByte) div (sizeof( A)) do begin
for indexA := 1 to 10240 do begin
a[ IndexA] := Random( 4294967295 );
end;
F.WriteBuffer( A, SizeOf( A));
end;
finally
F.Free;
end;
ShowMessage( SecondsBetween( T1, Now));
end;
在SSD驱动器上工作3~4秒。方式更容易。
答案 6 :(得分:1)
除了做你自己的Random()函数和/或使用aditional CPU,for循环一个快速的方法是:
procedure Generate(p: pointer; size: integer);
type
TCardinalArray = array[0..0] of cardinal;
PCardinalArray = ^TCardinalArray;
var
i: integer;
begin
i := (size div 4) - 1;
while i >= 0 do
begin
PCardinalArray(p)[i] := Random(MaxInt) * 2;
Dec(i);
end;
end;
由于不需要递增指针,因此将循环索引与TEST操作进行比较。
Unit6.pas.46: i := (size div 4) - 1;
0045209C 8BD9 mov ebx,ecx
0045209E 85DB test ebx,ebx
004520A0 7903 jns $004520a5
004520A2 83C303 add ebx,$03
004520A5 C1FB02 sar ebx,$02
004520A8 4B dec ebx
Unit6.pas.47: while i >= 0 do
004520A9 85DB test ebx,ebx
004520AB 7C14 jl $004520c1
Unit6.pas.49: PCardinalArray(p)[i] := Random(MaxInt) * 2;
004520AD B8FFFFFF7F mov eax,$7fffffff
004520B2 E8C50EFBFF call Random
004520B7 03C0 add eax,eax
004520B9 89049E mov [esi+ebx*4],eax
Unit6.pas.50: Dec(i);
004520BC 4B dec ebx
Unit6.pas.47: while i >= 0 do
004520BD 85DB test ebx,ebx
004520BF 7DEC jnl $004520ad
当然没有太大区别,但这是......
答案 7 :(得分:0)
除了其他因素外,我在原帖中看到的主要速度问题是:
1)为每个字节运行Random。此功能适用于大多数处理。每四个字节处理将是有利的。 2)最小化循环内的计算。我将建立指针边界,然后运行while循环(inc或dec by 4),直到上限和下限之间的差值小于4,然后在剩下的时间内将inc或dec减1。我可能不会在任何一点考虑for循环。 3)我不会针对大量数据运行此操作 - 我不会同时执行750MB,因为处理该数据量的速度降低往往超过代码中的任何性能增强。
非常轻微的测试,可能有很多需要改进,但我的基本想法是:
function Generate(buf: Pointer): DWord; stdcall;
var
inbuf, uplimit: Cardinal;
begin
inbuf := Cardinal(buf);
uplimit := inbuf + keysize - 1;
while (uplimit - inbuf) >= 4 do
begin
PDWord(inbuf)^ := Random(MAXINT);
inc(inbuf, 4);
end;
while inbuf <= uplimit do
begin
PByte(inbuf)^ := Random(256);
inc(inbuf, 1);
end;
Result := 0;
end;