Проблема скорости нежелательной генератора

Я ищу в создании файла (750 МБ), заполненного случайными байтами. Код, который я использую в отдельном потоке, выглядит следующим образом:

Я выделил буфер такого размера, поскольку запись на диске занимает больше времени:

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;

Проблема заключается в том, что для завершения процесса требуется много времени. Любые идеи для более быстрого метода? Я попытаюсь реализовать его в сборке, если нет альтернативы.

Ответы

Ответ 1

Это звучало как хорошая практика, поэтому я пошел вперед и реализовал параллельное решение. Он использует чуть более 3 секунд для генерации 750 МБ файла и использует более 90% процессора во время своей работы. (SSD-диск тоже помогает. Для генерации файла на пару дисков RAID0 потребовалось 3,5 секунды и 4 секунды для создания файла на более медленном диске емкостью 512 ГБ.)

Все повторно используемые коды доступны с лицензией 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;

EDIT: использование ForEach в этом случае было не очень элегантным решением, поэтому я расширил OmniThreadLibrary с помощью Parallel.ParallelTask ​​и с лучшим IOmniCounter. Используя выпуск 993 (или новее) из SVN, вы можете решить эту проблему с несколькими производителями-одиночными потребителями следующим образом.

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: более длинное сообщение в блоге об этой проблеме: Жизнь после 2.1: Производство параллельных данных (Представляем Parallel.Task)

Ответ 2

Я не знаю о Delphi, но может быть тратить время на вызов Random(256). Почему бы вам не подписать что-то псевдослучайное по отношению к эффекту

n = (n * 1103515245 + 12345) & 0xff;

Пусть n запустится где-нибудь и воспользуемся рекурсией, такой как этот, чтобы сгенерировать следующий n. Это не так уж и случайный, но он должен делать для создания случайных файлов.

ИЗМЕНИТЬ Некоторая пища для размышлений. Если вы создаете этот файл в надежде, что он не будет легко сжиматься, то описанный выше метод не так хорош, из-за части & 0xff. Лучше тогда делать

n = (n * 1103515245 + 12345) & 0x7fffffff;

as 0x7fffffff = 2147483647 - простое число. И сохраните точное большее значение n и выполните n % 256 при назначении. У меня были хорошие результаты с этим выбором констант, и я предпочитаю его как источник энтропии для встроенной альтернативы .NET, поскольку он во много раз быстрее, и в любом случае вам редко нужны действительно случайные или лучшие псевдослучайные числа.

Ответ 3

Проблема заключается в том, что Random() имеет ограниченную энтропию. И если вы создадите 750MiB данных, вы получите только одну из возможных t21 возможных строк (так как это период RNG), а не 2^(750*1024*1024*8), что будет иметь место, если генератор был идеальным. Это огромное несоответствие.

Короче говоря, если вы используете Random(), ваши данные не являются случайными вообще. Любой может угадать все 750MiB данных из 4MB образца/фрагмента файла.

Вы должны сделать это по-другому. Если у вас есть Linux-машина, выполните эту команду из вашей программы:

dd if=/dev/urandom of=file.img bs=1M count=750

Он заканчивается менее чем через полминуты на моем старом ноутбуке.

Ответ 4

Так как функция Random не имеет никакого хорошего распределения, вы можете уменьшить свой код почти в четыре раза со следующим:

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 секунд.

Ответ 5

Вы можете попробовать RandomRange(Low(Integer), High(Integer)) и посмотреть, работает ли он. Это будет генерировать 4 байта случайных данных за раз (помните, что он подписан, и что я предполагаю, что Integer равен 4 байтам, но The Integer type is an Integer whose size is not guaranteed (http://www.delphibasics.co.uk/RTL.asp? Name = Integer).

Ответ 6

Помимо вашей собственной функции Random() и/или использования дополнительных процессоров, для циклов быстрый подход:

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

    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;

Работает через 3 ~ 4 секунды на накопителе SSD. Путь проще.

Ответ 8

За исключением других факторов, основные проблемы со скоростью, которые я вижу с кодом в исходном сообщении, следующие:

1) запуск Random для каждого байта. Эта функция учитывает большую часть обработки. Обработка каждых четырех байтов будет выгодной. 2) минимизировать вычисления в цикле. Я бы установил границы указателя, а затем запустил цикл while (inc или dec by 4), пока разница между верхней границей и нижней границей не будет меньше 4, а затем inc или dec на 1 остальную часть пути. Я, вероятно, не буду рассматривать цикл for в любой точке этого. 3) Я бы не запускал это против огромного количества данных - я бы не стал делать 750 МБ одновременно, потому что ухудшение скорости обработки такого количества данных имеет тенденцию перевесить любые улучшения производительности с помощью кода.

Очень легко протестировано и, вероятно, многое предстоит улучшить, но основная идея, которую я здесь, была здесь:

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;