【问题标题】:Trying to create data transfer rate calculator using Lazarus and Freepascal尝试使用 Lazarus 和 Freepascal 创建数据传输率计算器
【发布时间】:2014-06-20 15:56:13
【问题描述】:

我正在尝试向我的应用程序界面添加“传输率:XGb p\min”功能。我正在使用 Lazarus 1.2.2 和 Freepascal 2.6.4 以毫秒级计算它。

我有一个循环,它从磁盘读取 64Kb 块,对每个 64Kb 块进行处理,然后重复直到读取整个磁盘或直到用户单击中止按钮。我正在尝试计算每次读取 64Kb 需要多长时间,然后计算每分钟读取数据的平均速率,即“每分钟 3Gb”。

我有一个名为“GetTimeInMilliseconds”的自定义函数,它计算标准时间到毫秒。

另外,为了避免界面在部分毫秒内被刷新,我有一个计数器试图确保界面仅在循环中每 50 次刷新一次,即当 64Kb 块被读取 50 次时,计数器被重置为 0。

问题是,传输显示要么未填充,要么填充了不准确的数字,例如 RAID0 设备的“234Mb p\min”!在其他时候,它会得到更真实的东西,比如“3.4Gb p\min”。如果在同一台 PC 和同一磁盘上重复运行,它应该始终准确,而不是不一致不准确。

这是我的 Try...finally 循环,它执行循环。它也称为 FormatByteSize,这是一个自定义函数,可以计算整数大小并将其转换为 XMb、XGb、XTb 等。

var

ProgressCounter, BytesTransferred, BytesPerSecond : integer;

Buffer : array [0..65535] of Byte;   // 65536, 64Kb buffer

ctx : TSHA1Context;

Digest : TSHA1Digest;

NewPos, ExactDiskSize, SectorCount, TimeStartRead, TimeEndRead,
  MillisecondsElapsed, BytesPerMillisecond, BytesPerMinute : Int64;

StartTime, EndTime, TimeTaken : TDateTime;
...
begin
... // Various stuff related to handles on disks etc
 try
    SHA1Init(ctx);
    FileSeek(hSelectedDisk, 0, 0);
    repeat
      ProgressCounter := ProgressCounter + 1; // We use this update the progress display occasionally, instead of every buffer read
      TimeStartRead   := GetTimeInMilliSeconds(Time); // Starting time, in Ms

      // The hashing bit...

      FileRead(hSelectedDisk, Buffer, 65536);  // Read 65536 = 64kb at a time
      NewPos := NewPos + SizeOf(Buffer);
      SHA1Update(ctx, Buffer, SizeOf(Buffer));
      FileSeek(hSelectedDisk, NewPos, 0);
      lblBytesLeftToHashB.Caption := IntToStr(ExactDiskSize - NewPos) + ' bytes, ' + FormatByteSize(ExactDiskSize - NewPos);

      // End of the hashing bit...

      TimeEndRead      := GetTimeInMilliSeconds(Time);;  // End time in Ms
      MillisecondsElapsed := (TimeEndRead - TimeStartRead); // Record how many Ms's have elapsed 

      // Only if the loop has gone round a while (50 times?), update the progress display
      if ProgressCounter = 50 then
        begin
          if (TimeStartRead > 0) and (TimeEndRead > 0) and (MillisecondsElapsed > 0) then // Only do the divisions and computations if all timings have computed to a positive number
            begin
              BytesTransferred := SizeOf(Buffer);
              BytesPerMillisecond := BytesTransferred DIV MilliSecondsElapsed; // BytesPerMillisecond if often reported as zero, even though BytesTRansferred and MilliSecondsElapsed are valid numbers? Maybe this is the fault? 
              BytesPerSecond := BytesPerMillisecond * 1000;  // Convert the bytes read per millisecond into bytes read per second
              BytesPerMinute := BytesPerSecond * 60; // Convert the bytes read per second into bytes read per minute
              lblSpeedB.Caption := FormatByteSize(BytesPerMinute) + ' p\min'; // now convert the large "bytes per minute" figure into Mb, Gb or Tb

              // Reset the progress counters to zero for another chunk of looping
              ProgressCounter := 0;
              BytesPerMillisecond := 0;
              BytesPerSecond := 0;
              BytesPerMinute := 0;
              ProgressCounter := 0;
            end;
        end;
      Application.ProcessMessages;
    until (NewPos >= ExactDiskSize) or (Stop = true); // Stop looping over the disk
    // Compute the final hash value
    SHA1Final(ctx, Digest);
    lblBytesLeftToHashB.Caption:= '0';
  finally
    // The handle may have been released by pressing stop. If not, the handle will still be active so lets close it.
    if not hSelectedDisk = INVALID_HANDLE_VALUE then CloseHandle(hSelectedDisk);
    EndTime := Now;
    TimeTaken := EndTime - StartTime;
    lblEndTimeB.Caption := FormatDateTime('dd/mm/yy hh:mm:ss', EndTime);
    if not stop then edtComputedHash.Text := SHA1Print(Digest);
    Application.ProcessMessages;
...

Function GetTimeInMilliSeconds(theTime : TTime): Int64;
// http://www.delphipages.com/forum/archive/index.php/t-135103.html
var
  Hour, Min, Sec, MSec: Word;
begin
  DecodeTime(theTime,Hour, Min, Sec, MSec); 
  Result := (Hour * 3600000) + (Min * 60000) + (Sec * 1000) + MSec;
end;

例子:

Form1.Caption := IntToStr(GetTimeInMilliSeconds(Time));

【问题讨论】:

    标签: delphi freepascal lazarus tdatetime


    【解决方案1】:

    我觉得你让这件事变得比它需要的复杂得多。

    将您的文件处理代码移动到单独的线程中。在该代码中,当您完成处理文件中的字节时,使用InterlockedAdd 来记录处理的字节总数。

    在您的 GUI 线程中,使用某种计时器,使用 InterlockedExchange 读取 InterlockedAdd 一直在修改的值并将其重置为零。然后计算自上次计时器滴答以来经过的时间量。给定在经过的时间内处理的总字节数,计算每分钟的字节数并更新您的 GUI。

    由于文件操作是在单独的线程上进行的,因此您无需担心更新 GUI 的频率(尽管过于频繁会浪费时间)。

    如果您想计算整个操作中每分钟的平均字节数,请不要在每次滴答时重置总字节计数器。

    【讨论】:

    • +1 不需要任何联锁操作。 GUI 线程可以读取读取的总字节数的瞬时计数。然后它可以与之前读取的值进行比较并进行数学运算。读和写都不需要是原子的。
    • @DavidHeffernan 同意,只要他无视我将字节计数器重置为零的建议,就不需要联锁操作。
    • 男士们...感谢您的建议,尽管我想我已经发现了问题所在。 TimeStartRead 和 TimeEndRead 实际上不到一毫秒。因此,如果 TimeStartRead = 12345,并且读取时间不到一毫秒,那么 TimeEndRead 也 = 12345。所以,TimeEndRead - TimeStartRead = 0!我使用epikTimer发现了这一点,它允许纳秒(它报告的时间为0.000160,并且是基于MS的),但是,它确实减慢了我的程序。不知道为什么,但它让它慢了 10 倍!所以,我需要的是一个内置于 freepascal 中的纳秒计算器。 DecodeTime 仅适用于 MS。
    • @Gizmo_the_Great 你应该听威廉的话。总结测量单个“块”读取的纳秒时间是错误的方法,因为您宁愿测量噪声而不是信号,它不是科学可靠的测量方法en.wikipedia.org/wiki/Observational_error。顺便说一句,还有其他线程和进程以及上下文切换会消耗 CPU 时间。因此,除非您的进程以非常高(不可抢占)的优先级运行,否则您不会以这种方式测量纯数据传输率
    • 好的——这一切都说得通。我想我可能会尝试使用一段时间内的块读取平均值——比如 100 次左右——而不是计算每个块读取的每分钟时间。感谢您提供所有信息 - 直到现在我才意识到纳秒计数存在这么多问题。答案被接受并受到赞赏,尽管它的实现看起来有点超出我的范围。
    猜你喜欢
    • 1970-01-01
    • 2015-12-02
    • 1970-01-01
    • 1970-01-01
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多