【问题标题】:Fast way to generate random sample data using delphi使用delphi生成随机样本数据的快速方法
【发布时间】:2011-07-27 10:28:06
【问题描述】:

我有这样的结构

const
  MaxSignalRecords=255;
type
  TSignalRecord=record
   signal1  : integer;
   signal2  : integer;
   signal3  : integer;
   signal4  : integer;
   signal5  : integer;
   signal6  : integer;
   bsignal1 : Boolean;
   bsignal2 : Boolean;
   bsignal3 : Boolean;
   bsignal4 : Boolean;
   bsignal5 : Boolean;
   bsignal6 : Boolean;
  end;

TListSignals = Array[0..MaxSignalRecords-1] of TSignalRecord;

以及生成随机样本数据的过程

Procedure FillRandomListSignals(var ListSignals:TListSignals);
var
  i :Integer;
begin
  for i := 0 to MaxSignalRecords - 1 do
  with ListSignals[i] do
  begin
   signal1   :=Random(MaxInt);
   signal2   :=Random(MaxInt);
   signal3   :=Random(MaxInt);
   signal4   :=Random(MaxInt);
   signal5   :=Random(MaxInt);
   signal6   :=Random(MaxInt);
   bsignal1  :=Boolean(Random(2));
   bsignal2  :=Boolean(Random(2));
   bsignal3  :=Boolean(Random(2));
   bsignal4  :=Boolean(Random(2));
   bsignal5  :=Boolean(Random(2));
   bsignal6  :=Boolean(Random(2));
  end;
end;

如何提高FillRandomListSignals 过程的性能?

编辑:这个结构用于进行数千(可能是数百万)次计算

for i:=1 to 1000000 do
begin
  CleartheList(MyList);
  FillRandomListSignals(MyList);
  DotheMath(MyList);
  DotheChart(MyList);
end;

【问题讨论】:

    标签: delphi


    【解决方案1】:

    当您生成随机数据时,速度并不是您唯一关心的问题,您实际上希望该数据是随机的,您不希望您的实验受到重复数据或其他伪随机生成器问题的困扰。如果您更关心速度而不是随机性,您可以随时使用function like this one,这将是超快的! </joke>.

    Here's a post by Barry Kelly on Stack Overflow 描述内置随机数生成器可能存在的问题。这里就不引用了,自己去读吧,挺好的。

    为了得出结论,当我需要一个足以生成大量随机数据的 PRNG 时,我使用了由 Delphi 的 PRNG 播种的 Mersenne Twister (wikipedia link)

    引用自维基百科关于 Mersene Twister 的内容:

    对于许多应用,Mersenne twister 正迅速成为首选的伪随机数生成器。 Mersenne Twister 在设计时考虑了蒙特卡罗模拟和其他统计模拟。研究人员主要需要高质量的数字,但也希望从其速度和便携性中受益。

    为了打破我在每个帖子的链接数的所有记录,我使用了this Delphi implementation

    我最后的想法是:除非你的数学非常好,否则不要使用自制的 PRNG 实现。就像哈希函数一样,很容易出错,而且很难分析。


    编辑

    使用以下代码进行了一些计时。使用 Mersenne Twister 生成 10,000,000 条记录需要 1480 毫秒。同样的代码,使用 Delphi 内置的随机数生成器只用了 250 毫秒,同样的 10M 记录。有些东西告诉我,需要优化的不是随机生成器,而是代码中的其他内容。

    procedure TForm1.Button1Click(Sender: TObject);
    var InitArray:array[0..99] of LongInt;
    
        i, N:Integer;
        TSR: TSignalRecord;
    
        CStart, CStop: Int64;
    
    begin
      Randomize;
      for i:=0 to 99 do InitArray[i] := Random($effffff);
      InitMTbyArray(InitArray, Length(InitArray));
    
      CStart := GetTickCount;
    
      for i:=1 to 10000000 do
      begin
        TSR.signal1 := IRanMT;
        TSR.signal2 := IRanMT;
        TSR.signal3 := IRanMT;
        TSR.signal4 := IRanMT;
        TSR.signal5 := IRanMT;
        TSR.signal6 := IRanMT;
    
        N := IRanMT;
    
        TSR.bsignal1 := (N and 1) <> 0;
        TSR.bsignal2 := (N and 2) <> 0;
        TSR.bsignal3 := (N and 4) <> 0;
        TSR.bsignal4 := (N and 8) <> 0;
        TSR.bsignal5 := (N and 16) <> 0;
        TSR.bsignal6 := (N and 32) <> 0;
      end;
    
      CStop := GetTickCount;
    
      Caption := IntToStr(CStop - CStart);
    end;
    

    【讨论】:

    • 有趣的是,结果最慢的答案已被验证为满足加速要求的正确答案。我想这是因为链接了最快的基于骰子的随机生成器。 ;)
    【解决方案2】:

    您不能对每个字段使用内置的 Random() 函数,而是全局使用一些具有流水线优化访问的代码,使用随机预生成的数组:

    var
      crc32tab: array[byte] of cardinal;
    
    procedure InitCrc32Tab;
    var i,n: integer;
        crc: cardinal;
    begin // this code size is only 105 bytes, generating 1 KB table content
      for i := 0 to 255 do begin
        crc := i;
        for n := 1 to 8 do
          if (crc and 1)<>0 then
            // $edb88320 from polynomial p=(0,1,2,4,5,7,8,10,11,12,16,22,23,26)
            crc := (crc shr 1) xor $edb88320 else
            crc := crc shr 1;
        crc32tab[i] := crc;
      end;
    end;
    
    type NativeUInt = cardinal; // before Delphi 2007
    
    procedure RandomData(P: PAnsiChar; Len: integer);
    var i: integer;
        seed0, seed1, seed2, seed3: cardinal;
    begin
      if Len>=16 then
      begin
        seed0 := Random(maxInt);
        seed1 := seed0*$8088405;
        seed2 := seed1*$8088405;
        seed3 := seed2*$8088405;
        for i := 1 to Len shr 4 do begin  // pipelined loop for 16 bytes at once
          PCardinalArray(P)[0] := crc32tab[byte(seed0)] xor seed0;
          seed0 := seed0 xor NativeUInt(P);
          PCardinalArray(P)[1] := crc32tab[byte(seed1)] xor seed1;
          seed1 := seed1 xor NativeUInt(P);
          PCardinalArray(P)[2] := crc32tab[byte(seed2)] xor seed2;
          seed2 := seed3 xor NativeUInt(P);
          PCardinalArray(P)[3] := crc32tab[byte(seed3)] xor seed3;
          seed3 := seed3 xor NativeUInt(P);
          inc(P,16);
        end;
      end;
      for i := 1 to Len and 15 do begin
        P^ := PAnsiChar(@crc32tab)[NativeUInt(P) and 1023];
        inc(P);
      end;
    end;
    

    上面的函数可以这样调用(你必须在你的程序中调用一次InitCrc32Tab过程):

    procedure FillRandomListSignals(var ListSignals: TListSignals);
    begin
      RandomData(@ListSignals,sizeof(ListSignals));
    end;
    

    它会比使用 Random() 函数更快,因为这个函数使用两个整数乘法,并且根本没有流水线。上面的循环将一次处理 16 个字节,没有乘法,每个 CPU 时钟有多个操作,因为我优化了它以使用尽可能多的 CPU 管道。我们也许可以和种子一起玩?变量,或者使用一些优化的 asm,但你已经明白了。

    后记:

    由于您使用随机数据填充列表,因此之前无需清除它。只是浪费时间。

    【讨论】:

      【解决方案3】:

      如果您只有 256 条记录,我看不出这段代码怎么会花费超过几毫秒的时间,所以何必费心呢。请记住阿姆达尔定律:-)

      【讨论】:

      • 认为它一定是这样的 :-) 但无论如何,您的代码看起来相当快速且精简。我不认为那里有太多的收获。你量过吗? FillRandomListSignals 使用了多少时间,其他三种方法使用了多少时间?从名字来看,我会研究 DoTheMath 和 DoTheChart。
      • 程序调用外部 dll 和 com 对象,所以没有太大的改进空间。
      猜你喜欢
      • 1970-01-01
      • 2010-11-05
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 2023-03-22
      • 2015-07-17
      • 2020-05-23
      • 2012-03-13
      相关资源
      最近更新 更多