1

我正在尝试向我的应用程序界面添加“传输速率: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));

4

1 回答 1

4

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

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

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

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

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

于 2014-06-20T17:37:20.777 回答