I'm doing a long loop downloading thousands of files. I would like to display an estimated time remaining, since it could take hours. However, with what I've written, I get an average number of milliseconds. How do I convert this average download time from milliseconds to a TDateTime?
See where I'm setting Label1.Caption:
procedure DoWork;
const
AVG_BASE = 20; //recent files to record for average, could be tweaked
var
Avg: TStringList; //for calculating average
X, Y: Integer; //loop iterators
TS, TE: DWORD; //tick counts
A: Integer; //for calculating average
begin
Avg:= TStringList.Create;
try
for X:= 0 to FilesToDownload.Count - 1 do begin //iterate through downloads
if FStopDownload then Break; //for cancelling
if Avg.Count >= AVG_BASE then //if list count is 20
Avg.Delete(0); //remove the oldest average
TS:= GetTickCount; //get time started
try
DownloadTheFile(X); //actual file download process
finally
TE:= GetTickCount - TS; //get time elapsed
end;
Avg.Add(IntToStr(TE)); //add download time to average list
A:= 0; //reset average to 0
for Y:= 0 to Avg.Count - 1 do //iterate through average list
A:= A + StrToIntDef(Avg[Y], 0); //add to total download time
A:= A div Avg.Count; //divide count to get average download time
Label1.Caption:= IntToStr(A); //<-- How to convert to TDateTime?
end;
finally
Avg.Free;
end;
end;
PS - I'm open to different ways of calculating the average speed of the last 20 (or AVG_BASE) downloads, because I'm sure my string list solution isn't the best. I don't want to calculate it based on all downloads, because speed may change over that time. Therefore, I'm just checking the last 20.
A TDateTime value is essentially a double, where the integer part is the number of days and fraction is the time.
In a day there are 24*60*60 = 86400 seconds (SecsPerDay constant declared in SysUtils) so to get A as TDateTime do:
dt := A/(SecsPerDay*1000.0); // A is the number of milliseconds
A better way to clock the time would be to use the TStopWatch construct in the unit Diagnostics.
Example:
sw.Create;
..
sw.Start;
// Do something
sw.Stop;
A := sw.ElapsedMilliSeconds;
// or as RRUZ suggested ts := sw.Elapsed; to get the TimeSpan
To get your average time, consider using this moving average record:
Type
TMovingAverage = record
private
FData: array of integer;
FSum: integer;
FCurrentAverage: integer;
FAddIx: integer;
FAddedValues: integer;
public
constructor Create(length: integer);
procedure Add( newValue: integer);
function Average : Integer;
end;
procedure TMovingAverage.Add(newValue: integer);
var i : integer;
begin
FSum := FSum + newValue - FData[FAddIx];
FData[FAddIx] := newValue;
FAddIx := (FAddIx + 1) mod Length(FData);
if (FAddedValues < Length(FData)) then
Inc(FAddedValues);
FCurrentAverage := FSum div FAddedValues;
end;
function TMovingAverage.Average: Integer;
begin
Result := FCurrentAverage;
end;
constructor TMovingAverage.Create(length: integer);
var
i : integer;
begin
SetLength( FData,length);
for i := 0 to length - 1 do
FData[i] := 0;
FSum := 0;
FCurrentAverage := 0;
FAddIx := 0;
FAddedValues := 0;
end;
Instead of a TDateTime you can use the TTimeSpan record, you can create a new instance passing the ticks elapsed to the constructor and from here you can use the Days, Hours, minutes, seconds and Milliseconds properties to display the elapsed time. Now for calculate the remaining time you need the total bytes to download and the current downloaded bytes.
Related
I'm trying to count down to a time of the day (24-hour clock format). This is my solution so far:
function TimeDiffStr(const s1, s2: string): string;
var
t1, t2: TDateTime;
secs: Int64;
begin
t1 := StrToDateTime(s1);
t2 := StrToDateTime(s2);
secs := SecondsBetween(t1, t2);
Result := Format('%2.2d:%2.2d:%2.2d', [secs div 3600, (secs div 60) mod 60, secs mod 60]);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
TargetTime: TTime;
s: string;
begin
s := TimeDiffStr(TimeToStr(Now), TimeToStr(TargetTime));
end;
If Now is, for example, 15:35:02 and the target time is 21:44:59, the output is correct (06:09:57). However, if Now is 15:35:02 and the target time is 01:32:23, instead of counting down from 09:57:21, it will count upwards, because the function does not know that the target time is on a different day.
How can I work out the difference between two times when the times are on different days?
First off, there is no need to pass strings around. If you start with TTime and convert to TTime, then simply pass TTime around.
Second, since you are dealing with just time values, if the target time is meant to be on the next day, you need to add 24 hours so that you have a TDateTime that actually represents the next day.
Try this:
uses
..., DateUtils;
function TimeDiffStr(const t1, t2: TTime): string;
var
d1, d2: TDateTime;
secs: Int64;
begin
d1 := t1;
if t2 < t1 then
d2 := IncDay(t2) // or IncHour(t2, 24)
else
d2 := t2;
secs := SecondsBetween(d1, d2);
Result := Format('%2.2d:%2.2d:%2.2d', [secs div 3600, (secs div 60) mod 60, secs mod 60]);
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
TargetTime: TTime;
s: string;
begin
TargetTime := ...;
s := TimeDiffStr(Time(), TargetTime);
end;
I have created a dynamic array, and have passed values to it. Is there a shortcut for finding mean of dynamic array.
var
TheMin, TheMax: Integer;
x: array of Integer; //Dynamic array declaration
....
TheMin := MinIntValue(x);//I am able to retrieve the minium value of the dynamic array
TheMax := MaxIntValue(x);//I am able to retrieve the maximum value of the dynamic array
Is there a other way to get mean using Math library.
It is very easy to write such a function.
function Mean(const Data: array of Integer): Double; overload;
var
i: Integer;
begin
Result := 0.0;
for i := low(Data) to high(Data) do
Result := Result + Data[i];
Result := Result / Length(Data);
end;
I overloaded this so that it could sit alongside the same named functions in the Math unit.
If you wish to use built in library code you can use SumInt from the Math unit:
TheMean := SumInt(x) / Length(x);
SumInt performs the summation using an Integer accumulator. This is probably faster than the bespoke function that uses a floating point accumulator. However, an Integer accumulator is potentially subject to overflow which may be off-putting. On the other hand, an Integer accumulator is potentially more accurate than a floating point accumulator. Depending on your usage requirements these issues may be important to you.
In bother cases, if the input array is of length zero a runtime floating point divide by zero error will be raised.
If the array has additions or deletions, recalculating the average from scratch can get rather time consuming.
In that case it may be worthwhile to calculate a running average instead.
function RecalcAverage(OldAverage: double; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
i:= Length(OldArray) + Length(Additions) - Length(Deletions);
WeighingFactor := 1 / i;
Result:= OldAverage;
for i:= 0 to Length(Deletions) -1 do begin
Result:= Result - (Deletions[i] * WeighingFactor);
end;
for i:= 0 to Length(Additions) -1 do begin
Result:= Result + (Additions[i] * WeighingFactor);
end;
end;
If you have a running sum handy, you can avoid the rounding errors and calculate an exact average.
function RecalcAverage(var RunningTotal: Int64; const OldArray, Additions, Deletions: TIntArray): double; overload;
var
i: integer;
begin
for i:= 0 to Length(Deletions) -1 do begin
RunningTotal:= RunningTotal - Deletions[i];
end;
for i:= 0 to Length(Additions) -1 do begin
RunningTotal:= RunningTotal + Additions[i];
end;
Result:= RunningTotal / (Length(OldArray) + Length(Additions) - Length(Deletions));
end;
If performance is an issue, it would make much more sense to calculate all the needed values in a single loop.
type
TStats = record
MaxVal: integer;
MinVal: integer;
Average: double;
end;
function CalcStats(const input: TIntArray): TStats;
var
MinVal, MaxVal: integer;
Total: Int64;
i: integer;
begin
Assert(Length(input) > 0);
MinVal:= input[0];
MaxVal:= MinVal;
Total:= MinVal;
for i:= 1 to Length(input) -1 do begin
MinVal:= Min(MinVal, input[i]);
MaxVal:= Max(MinVal, input[i]);
Total:= Total + input[i];
end;
Result.MinVal:= MinVal;
Result.MaxVal:= MaxVal;
Result.Average:= Total / Length(input);
end;
I am looking for a timer in milliseconds or nanoseconds in Delphi7. I have to check the speeds of three ISAM files with sequential search. The first ind file contains 50 strings like "record_0" to "record_50". The second - "record_0" to "record_500" and the third - "record_0" to "record_5000". I've implemented everything but I don't know how to make the timer. I am comparing a string with the last item in each ISAM file. Here is my code for the first ind file:
procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
var content : String[20];
var indexCounter:integer;
var keyword:string;
begin
//First ISAM file
AssignFile(indF1, 'index1.ind');
ReWrite(indF1);
Reset(indF1);
for i:=0 to 49 do begin
content := 'record_';
content := content + IntToStr(i+1);
index1.index1 := content;
index1.position1 := FileSize(indF1);
Seek(indF1, FileSize(indF1));
write(indF1, index1);
end;
CloseFile(indF1);
Label12.Caption := FileSizeStr('index1.ind');
//Sequential search in first ind file
Reset(indF1);
keyword := 'record_50';
indexCounter := 0;
//start timer
while not Eof(indF1) do begin
Seek(indF1, indexCounter);
Read(indF1, Index1);
if (keyword = Index1.index1) then begin
//stop timer;
//Label20 := milliseconds/nanoseconds;
//return/break while loop (result := -1; exit;) ???
end;
indexCounter := indexCounter + 1;
end;
I need a procedure/function so that when I call it it should start counting in milliseconds or nanoseconds and stop when the string is found (it's the last string in each ind file) and show the elapsed time for traversing through all the file. Also I don't know how to break the while loop. Thanks in advance.
The TStopWatch class described here "delphi-high-performance-timer-tstopwatch" has all functions needed (for Delphi-7).
It's implemented in later Delphi versions (Delphi-2010) as an advanced record in unit diagnostics.
Example:
var
sw : TStopWatch;
elapsedMilliseconds : cardinal;
begin
...
sw := TStopWatch.Create() ;
try
sw.Start;
while not Eof(indF1) do begin
Seek(indF1, indexCounter);
Read(indF1, Index1);
if (keyword = Index1.index1) then begin
sw.Stop;
Label20.Caption := IntToStr(sw.ElapsedMilliseconds);
break; // break while loop
end;
indexCounter := indexCounter + 1;
end;
...
finally
sw.Free;
end;
end;
To break the while loop, just do break; inside your conditional test.
Use QueryPerformanceFrequency and QueryPerformanceCounter. The first function returns a number of units per second, and the second function returns a value.
lFreq: Int64;
InitialF, FinalF: Int64;
if QueryPerformanceFrequency(lFreq) then
// hi-res timer is supported
else
// hi-res timer is not supported
QueryPerformanceCounter(InitialF);
// do something you want to time
QueryPerformanceCounter(FinalF);
// duration of the time of something is FinalF - InitialF in "units"
// divide by lFreq to get the amount of time in seconds,
// this will be an Extended type.
found this simple code:
var
StartTime : Cardinal;
begin
StartTime := GetTickCount;
//code to do
ShowMessage(Format('Elapsed time %d ms', [GetTickCount - StartTime]));
Use JclCounter from Jedi JCL. Or if you don't want to go with Jedi, use Win Api QueryPerformanceCounter.
Hi I am using QueryperformanceCounter to time a block of code in Delphi. For some reason, the
Millisecond number I got by using QueryPerformanceCounter is quite different from my wall clock time by using a stopwatch. For example The stopwatch give me about 33 seconds, which seems right if not accuracy, but using QueryPerofomanceCounter will give me a number like 500 Milliseconds.
When step though my code, I can see that QueryPerformanceFrequencygives me correct CPU frequency for my CPU, 2.4G for Core2 E6600. So if the tick number is correct, (tick number / Freq) * 1000 should give me correct execution time for the code I am timing, but why not?
I know that for the code I am trying to timing, QeuryPerformanceCounter is probably over-killing as it took seconds rather than MillionSeconds, but I am more interested in understanding the reason for the time difference between wall clock and QueryPerormanceCounter.
My Hardware is E6600 Core2 and OS is Windows 7 X64 if it is relevant.
unit PerformanceTimer;
interface
uses Windows, SysUtils, DateUtils;
type TPerformanceTimer = class
private
fFrequency : TLargeInteger;
fIsRunning: boolean;
fIsHighResolution: boolean;
fStartCount, FstopCount : TLargeInteger;
procedure SetTickStamp(var lInt : TLargeInteger) ;
function GetElapsedTicks: TLargeInteger;
function GetElapsedMiliseconds: TLargeInteger;
public
constructor Create(const startOnCreate : boolean = false) ;
procedure Start;
procedure Stop;
property IsHighResolution : boolean read fIsHighResolution;
property ElapsedTicks : TLargeInteger read GetElapsedTicks;
property ElapsedMiliseconds : TLargeInteger read GetElapsedMiliseconds;
property IsRunning : boolean read fIsRunning;
end;
implementation
constructor TPerformanceTimer.Create(const startOnCreate : boolean = false) ;
begin
inherited Create;
fIsRunning := false;
fIsHighResolution := QueryPerformanceFrequency(fFrequency) ;
if NOT fIsHighResolution then
fFrequency := MSecsPerSec;
if startOnCreate then
Start;
end;
function TPerformanceTimer.GetElapsedTicks: TLargeInteger;
begin
result := fStopCount - fStartCount;
end;
procedure TPerformanceTimer.SetTickStamp(var lInt : TLargeInteger) ;
begin
if fIsHighResolution then
QueryPerformanceCounter(lInt)
else
lInt := MilliSecondOf(Now) ;
end;
function TPerformanceTimer.GetElapsedMiliseconds: TLargeInteger;
begin
result := (MSecsPerSec * (fStopCount - fStartCount)) div fFrequency;
end;
procedure TPerformanceTimer.Start;
begin
SetTickStamp(fStartCount) ;
fIsRunning := true;
end;
procedure TPerformanceTimer.Stop;
begin
SetTickStamp(fStopCount) ;
fIsRunning := false;
end;
end.
This code just works for me, maybe you can try it:
var
ifrequency, icount1, icount2: Int64;
fmsec: Double;
begin
QueryPerformanceFrequency(ifrequency);
QueryPerformanceCounter(icount1);
Sleep(500);
QueryPerformanceCounter(icount2);
fmsec := 1000 * ((icount2 - icount1) / ifrequency);
end;
fmsec is about 499.6 or something like that.
Note: Don't rely on Now or TickCount for small numbers: they have an interval of about 10ms (depending on Windows version)! So duration of "sleep(10)" can give 0ms if you use Now and DateUtils.MillisecondsBetween
Note 2: Don't rely on QueryPerformanceCounter for long durations, because it's time can slowly go away during a day (about 1ms diff per minute)
If your hardware supports dynamic frequency scaling, it implies that QueryPerformanceFrequency cannot return a static value continuously describing a dynamically changing one. Whenever something computationally aggressive starts, the adapting CPU speed will prevent exact measurements.
At least, it was experienced with my notebook - as it changed to the higher clock rate, QueryPerformanceCounter based measurements were messed up.
So, regardless of the higher accuracy offered, I still use GetTickCount most of the time for such purposes (but DateTime based measurements are also OK, as mentioned before, except if time zone switches may occur), with some "warm-up" code piece that starts eating up the CPU power so the CPU speed is at its (constant) maximum as the relevant code piece starts executing.
You should post a code snippet demonstrating the problem...but I would assume an error on your part:
Milliseconds := 1000 * ((StopCount - StartCount) / Frequency);
If you are comparing to a stop watch, you can likely take the easier route and just capture the TDateTime before and after (by using Now()) and then use the DateUtils MilliSecondSpan() method to calculate difference:
var
MyStartDate:TDateTime;
MyStopDate:TDateTime;
MyTiming:Double;
begin
MyStartDate := Now();
DoSomethingYouWantTimed();
MyStopDate := Now();
MyTiming := MilliSecondSpan(MyStopDate, MyStartDate);
DoSomethingWithTiming(MyTiming);
end;
I use an NTP server to sync the PC clock periodically, the PC clock over a large amount of time to adjust the QueryPerformanceCounter "tick" time, and the calibrated QueryPerformanceCounter time for precise time measurements. On a good server where the clock drift is low it means that I have accuracy over time periods to much less than a millisecond and the clock times of all my machines synchronised to within a millsecond or two. Some of the relevant code is attached below:
function NowInternal: TDateTime;
const
// maximum time in seconds between synchronising the high-resolution clock
MAX_SYNC_TIME = 10;
var
lPerformanceCount: Int64;
lResult: TDateTime;
lDateTimeSynchronised: Boolean;
begin
// check that the the high-resolution performance counter frequency has been
// initialised
fDateTimeCritSect.Enter;
try
if (fPerformanceFrequency < 0) and
not QueryPerformanceFrequency(fPerformanceFrequency) then
fPerformanceFrequency := 0;
if fPerformanceFrequency > 0 then begin
// get the return value from the the high-resolution performance counter
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then
lResult := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency / SecsPerDay
else
lResult := CSI_NULL_DATE_TIME;
if (MilliSecondsBetween(lResult, Now) >= MAX_CLOCK_DIFF) or
(SecondsBetween(Now, fLastSyncTime) >= MAX_SYNC_TIME) then begin
// resynchronise the high-resolution clock due to clock differences or
// at least every 10 seconds
lDateTimeSynchronised := SyncDateTime;
// get the return value from the the high-resolution performance counter
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then
lResult := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency / SecsPerDay;
end else
lDateTimeSynchronised := False;
if MilliSecondsBetween(lResult, Now) >= (MAX_CLOCK_DIFF * 2) then
// default the return value to the standard low-resolution value if
// anything has gone wrong
Result := Now
else
Result := lResult;
end else begin
lDateTimeSynchronised := False;
// default the return value to the standard low-resolution value because
// we cannot use the high-resolution clock
Result := Now;
end;
finally
fDateTimeCritSect.Leave;
end;
if lDateTimeSynchronised then
CsiGlobals.AddLogMsg('High-resolution clock synchronised', CSI_LC_CLOCK);
end;
function SyncDateTime: Boolean;
var
lPriorityClass: Cardinal;
lThreadPriority: Integer;
lInitTime: TDateTime;
lNextTime: TDateTime;
lPerformanceCount: Int64;
lHighResCurrentTime: TDateTime;
lLowResCurrentTime: TDateTime;
begin
// synchronise the high-resolution date/time structure (boost the thread
// priority as high as possible during synchronisation)
lPriorityClass := CsiGetProcessPriorityClass;
lThreadPriority := CsiGetCurrentThreadPriority;
try
CsiSetProcessPriorityClass(REALTIME_PRIORITY_CLASS);
CsiSetCurrentThreadPriority(THREAD_PRIORITY_TIME_CRITICAL);
// loop until the low-resolution date/time value changes (this will load the
// CPU, but only for a maximum of around 15 milliseconds)
lInitTime := Now;
lNextTime := Now;
while lNextTime = lInitTime do
lNextTime := Now;
// adjust the high-resolution performance counter frequency for clock drift
if (fWindowsStartTime <> CSI_NULL_DATE_TIME) and
QueryPerformanceCounter(lPerformanceCount) then begin
lHighResCurrentTime := fWindowsStartTime +
lPerformanceCount / fPerformanceFrequency /
SecsPerDay;
lLowResCurrentTime := Now;
if MilliSecondsBetween(lHighResCurrentTime, lLowResCurrentTime) <
(MAX_CLOCK_DIFF * 2) then
fPerformanceFrequency := Round((1 +
(lHighResCurrentTime -
lLowResCurrentTime) /
(lLowResCurrentTime - fLastSyncTime)) *
fPerformanceFrequency);
end;
// save the Windows start time by extrapolating the high-resolution
// performance counter value back to zero
if QueryPerformanceCounter(lPerformanceCount) then begin
fWindowsStartTime := lNextTime -
lPerformanceCount / fPerformanceFrequency /
SecsPerDay;
fLastSyncTime := Now;
Result := True;
end else
Result := False;
finally
CsiSetCurrentThreadPriority(lThreadPriority);
CsiSetProcessPriorityClass(lPriorityClass);
end;
end;
Using DelphiXE, I'm trying to show the length of a wav file on a label. This is a wav file at fixed bit rate of 64kbps that is loaded into a tMediaPlayer.
A previous SO post on the task is HERE. But no code is shown and the link to Devhood no longer appears to work so I was unable to try that method.
I also tried the code from HERE but it gives incorrect results as follows.
type
HMSRec = record
Hours: byte;
Minutes: byte;
Seconds: byte;
NotUsed: byte;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TheLength: LongInt;
begin
{ Set time format - note that some devices don’t support tfHMS }
MediaPlayer1.TimeFormat := tfHMS;
{ Store length of currently loaded media }
TheLength := MediaPlayer1.Length;
with HMSRec(TheLength) do { Typecast TheLength as a HMSRec record }
begin
Label1.Caption := IntToStr(Hours); { Display Hours in Label1 }
Label2.Caption := IntToStr(Minutes); { Display Minutes in Label2 }
Label3.Caption := IntToStr(Seconds); { Display Seconds in Label3 }
end;
end;
This code gives a value of 24:23:4, when it should be 0:04:28.
Is there an obvious problem with that code, or is there some more elegant way to accomplish this?
As always, thanks for your help.
Why not just do some simple elementary-school math?
var
sec,
min,
hr: integer;
begin
MediaPlayer1.TimeFormat := tfMilliseconds;
sec := MediaPlayer1.Length div 1000;
hr := sec div SecsPerHour;
min := (sec - (hr * SecsPerHour)) div SecsPerMin;
sec := sec - hr * SecsPerHour - min * SecsPerMin;
Caption := Format('%d hours, %d minutes, and %d seconds', [hr, min, sec]);
But why don't HMS work? Well, according to the official documentation:
MCI_FORMAT_HMS
Changes the time format
to hours, minutes, and seconds.
Recognized by the vcr and videodisc
device types.
MCI_FORMAT_MILLISECONDS
Changes the
time format to milliseconds.
Recognized by all device types.