RAD Studio 2010 Review #13: – The Persistence of Time

Water_droplet_blue_bg05Speaking about the most glamorous features of Delphi 2010 we overlooked some other features which, perhaps, seems smaller but sometimes are very useful in the day-by-day work. This is the new Diagnostics.pas unit which,… at least for the time being, has only one record, TStopwatch.

It’s public part is as following:

TStopwatch = record
    class function Create: TStopwatch; static;
    class function GetTimeStamp: Int64; static;
    procedure Reset;
    procedure Start;
    class function StartNew: TStopwatch; static;
    procedure Stop;
    property Elapsed: TTimeSpan read GetElapsed;
    property ElapsedMilliseconds: Int64 read GetElapsedMilliseconds;
    property ElapsedTicks: Int64 read GetElapsedTicks;
    property IsRunning: Boolean read FRunning;
  public class var Frequency: Int64;
  public class var IsHighResolution: Boolean;

The most interesting thing above is the new TTimeSpan data type which is declared in the new TimeSpan.pas which of course has a bunch of interesting things inside (comments mine):

  TTimeSpan = record
  public const //classical time constants
    TicksPerMillisecond = 10000;
    TicksPerSecond = 1000 * Int64(TicksPerMillisecond);
    TicksPerMinute = 60 * Int64(TicksPerSecond);
    TicksPerHour = 60 * Int64(TicksPerMinute);
    TicksPerDay = 24 * TIcksPerHour;
  //different 'record constructors' for data initialization
    constructor Create(ATicks: Int64); overload;
    constructor Create(Hours, Minutes, Seconds: Integer); overload;
    constructor Create(Days, Hours, Minutes, Seconds: Integer); overload;
    constructor Create(Days, Hours, Minutes, Seconds, Milliseconds: Integer); overload;

  //misc operations via methods
    function Add(const TS: TTimeSpan): TTimeSpan; overload;
    function Duration: TTimeSpan;
    function Negate: TTimeSpan;
    function Subtract(const TS: TTimeSpan): TTimeSpan; overload;

  //conversions from temporal representations in different time units
    class function FromDays(Value: Double): TTimeSpan; static;
    class function FromHours(Value: Double): TTimeSpan; static;
    class function FromMinutes(Value: Double): TTimeSpan; static;
    class function FromSeconds(Value: Double): TTimeSpan; static;
    class function FromMilliseconds(Value: Double): TTimeSpan; static;
    class function FromTicks(Value: Int64): TTimeSpan; static;

  //'convert' from string - the Unit hides a TTimeSpanParser record type in it.
    class function Parse(const S: string): TTimeSpan; static;
    class function TryParse(const S: string; out Value: TTimeSpan): Boolean; static;

  //operator overloading
    class operator Add(const Left, Right: TTimeSpan): TTimeSpan;
    class operator Subtract(const Left, Right: TTimeSpan): TTimeSpan;
    class operator Equal(const Left, Right: TTimeSpan): Boolean;
    class operator NotEqual(const Left, Right: TTimeSpan): Boolean;
    class operator GreaterThan(const Left, Right: TTimeSpan): Boolean;
    class operator GreaterThanOrEqual(const Left, Right: TTimeSpan): Boolean;
    class operator LessThan(const Left, Right: TTimeSpan): Boolean;
    class operator LessThanOrEqual(const Left, Right: TTimeSpan): Boolean;
    class operator Negative(const Value: TTimeSpan): TTimeSpan;
    class operator Positive(const Value: TTimeSpan): TTimeSpan;

 //wowza!! seamless conversion to string - that's nice...
    class operator Implicit(const Value: TTimeSpan): string; 
    class operator Explicit(const Value: TTimeSpan): string;

  //data representation in different formats
    property Ticks: Int64 read FTicks;
    property Days: Integer read GetDays;
    property Hours: Integer read GetHours;
    property Minutes: Integer read GetMinutes;
    property Seconds: Integer read GetSeconds;
    property Milliseconds: Integer read GetMilliseconds;
    property TotalDays: Double read GetTotalDays;
    property TotalHours: Double read GetTotalHours;
    property TotalMinutes: Double read GetTotalMinutes;
    property TotalSeconds: Double read GetTotalSeconds;
    property TotalMilliseconds: Double read GetTotalMilliseconds;

  //different constants exposed via properties
    class property MinValue: TTimeSpan read FMinValue;
    class property MaxValue: TTimeSpan read FMaxValue;
    class property Zero: TTimeSpan read FZero;

Perhaps we must stress that this is the Delphi representation for a time interval. It is not a replacement for TDateTime which represents a single date – a single point in time.

And now the commented example:

procedure TfrmRibbonDemo.Button1Click(Sender: TObject);
  rStopwatch: TStopwatch;
  I: Integer;

  rStopwatch.Reset; //very good to be here ;-), especially if we want to run this procedure many times
  rStopwatch.Start; //let's profile!
  for I := 0 to 100 do //something to do - which will last a little
  rStopwatch.Stop; //stop the timer
  ShowMessage(rStopwatch.Elapsed); //show what we got - we use the implicit conversion to string

Well, I think that the main advantage of using TStopwatch instead of old ways of time measuring besides of having clean and readable code – one who read the code will understand immediately what’s going on – is that it is abstract. No variables to manage and what’s more interesting is that it tries the best timing method available. See for yourself (code snipped from Diagnostics.pas):

class procedure TStopwatch.InitStopwatchType;
  if Frequency = 0 then
    if not QueryPerformanceFrequency(Frequency) then
      IsHighResolution := False;
      Frequency := TicksPerSecond;
      TickFrequency := 1.0;
    end else
      IsHighResolution := True;
      TickFrequency := 10000000.0 / Frequency;

In other words it tries to use the high resolution timer if available, but if it doesn’t succeed it goes on the common path of GetTickCount – not shown in the above snippet.

Well, it seems that there are enough enhancements out there regarding to time handling. What do You think?

20 thoughts on “RAD Studio 2010 Review #13: – The Persistence of Time

  1. Cool. I was just fighting with this stuff. However, QueryPerformanceFrequency is a piece of complete crap on some hardware. Windows is a pile of steaming crap. Go look up QueryPerformanceFrequency bugs caused by motherboard issues.
    The RTDSC instructions also glitch on some multi-core CPUs.

    So for certain uses (profiling etc) it is okay to use QPF/QPC, and for others, if you have to widely deploy your application, such as a game, and have it not glitch and screw up on you, you can’t afford to use QueryPerformanceCounter/QueryPerformanceFrequency. Even if it appears to work on your PC when you test it, you can’t deploy your app to the field and expect Windows to just work.

    There’s an MS KB article that suggests that if you need it to “always work” you should always poll both GetTickCount and the high frequency path, and in case of a variance >100 msec, fall back.

    The implementation of TStopwatch follows generally the .NET standard library stopwatch public interface, but does not appear to implement this “safety fallback”.

    I personally got tired of all that crap and decided to use the multimedia timers, which are higher resolution and lower jitter than GetTickCount, but without the horrific motherboard-specific failure modes of QueryPerformanceFrequency.


  2. ROTFL!!!!! That’s 2010, I would expect a profiler for “diagnostics”, not this simplistic turbo-pascal-5.5-like class (and IIRC TP6 *had* a profiler).
    We are using AQTime (a Delphi built app, BTW), and profiling with it it’s a real pleasure – first do a high-level profiling, find what area of code takes more time, and then drill down until you find the reason. And everything without *modifying* the code, just change compiler/linker options, and then use AQTime to inspect what you need.
    This class can be useful to time something to show the user, i.e. “your query took xx.yy seconds”, but if you’re a real professional developer, profiling code this way is really old, old fashion and time consuming.
    Embarcadero should really to rethink Delphi as a professional tool ASAP, hobbyist can waste time profiling code this way, professionals often can’t.

    • Why do you pay for AQTime?
      You can use Eric Grange’s Sampling Profiler which is Free.
      Of course, this if you want sampling, because as perhaps you know there are drawbacks and situations in which sampling simply doesn’t apply.

      • Why do I pay for AQ Time? Because I am a professional developer and the time saved by a very good tool like AQ Time – which does a lot, not only profiling, in a very powerful and friendly way, pays for the tool cost in a short time. Moreover AQTime is not a Delphi-only tool (it supports a wide range of languages and compilers, including .NET) thereby I can use it in many scenarios, and even if I abandoned Delphi it wouldn’t be wasted money.

        • What has AQTime and Eric Profiler doesn’t? I mean a ‘must have’ feature. Do you have experience with Eric’s Sampling Profiler? And, for sure, you won’t “waste money” with that. 🙂

          • Sampling Profiler is a nice piece of work, but its name already implies the limitation: as your code is being sampled this means you may have to run your app for quite some time to acquire enough samples for the pieces of code you are interested in. With AQTime your app will be running much slower, because of all the logging going on, but you can still get accurate timings quickly, telling you how long each method took.

            Also, with AQTime you can see the full call path, so you will know where the calls came from. This info is generally very useful, because often the biggest performance improvements lie not in making one method faster, but in calling that method less often. If your busy method is called form lots of different places in the program, AQTime will tell you on which calling method to focus, whereas Sampling Profiler won’t.

            • Sampling profilers, outside of CPU emulators, are the only kinds of profilers which can give you a reliable picture of the highest-cost code paths in your application. Instrumenting profilers can dramatically alter the execution performance in boundary conditions and should be used primarily for collecting discrete statistics on runtime behaviour – discrete as in the discrete mathematics sense.

              • Agree. However sampling cannot be applied when one has to deal with certain situations like eg. with user input (IOW a simple MessageDlg(‘Item already exists. Confirm delete?’…) will blow the entire profiling procedure.

          • Just check here: http://www.automatedqa.com/products/aqtime/features/

            Briefly, while Sampling Profiler just profile code execution time (with a different approach than AQTime, thereby they don’t overlap fully), AQTime offers more profilers (performance, memory, coverage, exception and function trace, platform compliance, statical analisys and others, there’s even a BDE SQL profiler!), all in a very well designed GUI, can integrate with both RAD and VS, and supports Win32, Win64 and managed code – not only Delphi/CBuilder. Results can be stored and compared. And it has good support.
            “Must have” features depends on how you work and what you work on. I find AQTime very valuable for my development – and I understand other people like to be paid for their work. I sell my applications, can’t understand why I shouldn’t buy those I find useful and really help me to release better code. Between metrics and profiling, I’d by far find the latter more useful to ensure code quality.

    • @LDS
      ROTFL!!! “just change compiler/linker options” is so 1990’s. Modern profilers like GlowCode let you profile without recompiling–all you need is the .PDB files which can be produced for both release and debug builds.

      If CodeGear wants to help customers create faster code, do two things:

      1. Generate .PDB files so we can use dozens of modern tools that support .PDB files.

      2. Bundle a popular assembler. NASM switched to BSD License recently, and YASM is coming along nicely. Don’t try to build a 64-bit proprietary assembler from scratch.

      3. Give us the option of producing native binaries that leverage SSE, etc. and modern CPU intrinsics.

      Why bundle a popular assembler aside from huge cost savings at CodeGear? Try compiling Crypto++ 5.6 from C++Builder and compare the benchmarks to the one compiled by MSVC (the speed difference is both shocking and depressing for a CodeGear fan like myself). Bundling a popular assembler means having open source projects provide optimized results.

      • I guess pdb files are easy to read via DIA, but difficult to write because AFAIK the format is proprietary and not documented – don’t know if Embarcadero can license its format easily. At least it could support the .dbg format, that should be documented.

        I don’t understand your issue with the speed of code produced with an assembler – you’re writing the assembler code – if you can write fast code it will output fast code too 🙂

        If we are talking about the compiler code generator back-end, well, this is another issue. Anyway, being tied to somebody’s else work usually may not be the best options, especially if you have to output more than assembler code. Delphi/C++ need a far better compiler, that’s true, I’ve been asking it for years. They’re writing it, hope they will finish it soon.

    • You’ll quickly stop rolling over the floor laughing once you’ve tried AQTime on Windows 7 because it crashes. If you’re going to profile Direct2D for example you’ll find yourself using TimeGetTime before you know it 😉

      • For the matter, it doesn’t support D2010 either. Guess support will come soon – anyway AFAIK Windows 7 is not released yet – and we don’t support unsupported operating systems.

        And that does not change my opinion: a tool aimed a professional developers should have better tool than a timing class in 2010.

        Embarcadero didn’t get rid of the silly SKUs dating back to 1995 yet. IMHO the “Professional” one should allow for web development and C/S applications, while the Enterprise one should include support for multi-tier (a real enterprise solution, not the toy they offer now) and tools to develop and test highly scalable and reliable applications (profilers, static and dynamic analysis, etc.). It’s a bit funny to have metrics, but not a real GUI driven profiler.
        It would justify the high price tags, and will make Delphi much more attractive than it is now, compared to competitors.
        They didn’t ever bother to integrate it with version control systems really… and now they are going to support only one, just because they use it. The world outside Scotts Valley is a bit more variegated.

  3. This looks like a handy little class! Am I right in thinking it is supposed to be a singleton class, that is you only have one instance of the stopwatch at any time?

    • Yes, is nice to use because it hides many boring details of profiling. But you can have as many stopwatches want in order to measure different intervals (which, of course, can overlap) – think about multithreading.

  4. Nice class but I agree with Warren – QueryPerformanceCounter is not to depend upon. We had many problems with our server code on various hardware (mostly HP machines) until we replaced QPC with multimedia timers (timeGetTime API).

  5. Pingback: Paweł Głowacki : Delphi 2010 Launch in the Netherlands and my Top 3 blog posts

  6. Pingback: Paweł Głowacki : Converting to grayscale with TBitmap.ScanLine property

  7. Just for anyone passing by here – there’s an ommision in the example code :

    TStopWatch.Create has to be called at least once, since that triggers class procedure TStopwatch.InitStopwatchType, which otherwise wouldn’t be called when just using the TStopwatch Reset / Start methods.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s