垃圾发电机速度问题

opc*_*0de 16 delphi

我正在寻找生成一个充满随机字节的文件(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;
Run Code Online (Sandbox Code Playgroud)

问题是该过程完成需要很长时间.有什么想法更快的方法?如果没有其他选择,我会尝试在汇编中实现它.

gab*_*abr 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;
Run Code Online (Sandbox Code Playgroud)

编辑:在这种情况下使用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;
Run Code Online (Sandbox Code Playgroud)

EDIT2:关于这个问题的更长篇博文:2.1之后的生活:并行数据生成(Parallel.Task简介)

  • @David:没错.目前,NoWait使ForEach仅使用N-1个线程(将最后一个核心留给主线程).但是,您可以通过指定(例如).NumTasks(Environment.Process.Affinity.Count)来覆盖该行为.如果您将所有核心用于后台任务(至少使用较慢的磁盘),实际上速度会略有提高,所以我已经更新了上面的示例. (2认同)

Gle*_*eno 6

我不知道德尔福,但它可能会浪费时间在Random(256)电话上.你为什么不手工编码伪随机的东西

n = (n * 1103515245 + 12345) & 0xff;
Run Code Online (Sandbox Code Playgroud)

让我们n从某个地方开始并使用递归(例如这个)来生成下一个n.这不是真的随机的,但它应该用于创建随机文件执行.

编辑 一些值得思考的东西.如果您创建此文件希望它不易被压缩,那么上面概述的方法并不是那么好,因为该& 0xff部分.这样做更好

n = (n * 1103515245 + 12345) & 0x7fffffff;
Run Code Online (Sandbox Code Playgroud)

作为0x7fffffff = 2147483647素数.并存储确切的较大值n,并进行n % 256分配.我已经有了一些很好的运行选择常量,并且更喜欢它作为内置.NET替代品的熵源,因为它的速度快了很多倍,而且你很少需要真正随机或更好的伪随机数.