• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Better than TickCount() timing?

ymk

Well-known member
I've been looking for more precise timing than the 60Hz resolution TickCount() provides. I'm using THINK C 6.0.1. This is what I've found so far:


Is there some memory address I can read to get a raw timer? I can account for frequency differences between machines.

Thanks
 

cheesestraws

Well-known member
The System 7 Microseconds() function, which I'd be happy to use, but I'm not sure if it's possible with THINK C 6.0.1

If it's not in the headers that come with THINK C, I think you ought to be able to invoke Microseconds() with something along the vague lines of

Code:
pascal void Microseconds (UnsignedWide* microTickCount) = (0xA193, 0x225F, 0x22C8, 0x2280);

(Syntax may be wobbly, I've barely used THINK C and it's late at night here, but that's the inline from the Universal Headers I have here)
 

cheesestraws

Well-known member
The System 7 Microseconds() function, which I'd be happy to use, but I'm not sure if it's possible with THINK C 6.0.1. I'm willing to give up System 6 support for this.

OK, here's some THINK C that will do it. Tested in THINK C 5.0, so ought to work in 6.x. Attachment contains a THINK C Project and a CodeWarrior project to verify that both produce the same kind of results.

C:
#include <stdio.h>

struct UnsignedWide {
    unsigned long hi;
    unsigned long lo;
};
typedef struct UnsignedWide UnsignedWide;


pascal void Microseconds(UnsignedWide* m) = {0xA193, 0x225F, 0x22C8, 0x2280};


main()
{
    UnsignedWide one;
    UnsignedWide two;
    long i;
    long j;
    
    one.lo = 0;
    two.lo = 0;

    printf("start\n");
    Microseconds(&one);
    
    for (i = 0; i < 10000000; i++) { j += 1; }
    for (i = 0; i < 10000000; i++) { j += 1; }
    for (i = 0; i < 10000000; i++) { j += 1; }
    for (i = 0; i < 10000000; i++) { j += 1; }
    for (i = 0; i < 10000000; i++) { j += 1; }
    for (i = 0; i < 10000000; i++) { j += 1; }

    
    Microseconds(&two);
    
    printf("one: (%lu, %lu); two: (%lu, %lu)", one.hi, one.lo, two.hi, two.lo);
}
 

Attachments

  • microseconds.sit
    152.5 KB · Views: 0

ymk

Well-known member
Thanks for the great example!

I'm not familiar with defining a Pascal function this way. Is the 64-bit value an A-line instruction?
 

cheesestraws

Well-known member
I'm not familiar with defining a Pascal function this way.

The 'pascal' bit is just a calling convention, rather like __stdcall in modern Microsoft C++. It tells the compiler that:
  • Parameters need to be be pushed onto the stack left to right (as opposed to C compilers using right to left).
  • The callee will clean up the stack before returning.
It doesn't inherently have much to do with Pascal as a language; it's so-called just because it's the calling convention that Pascal compilers standardised on and that the Pascal-originating bits of the OS use. C compilers didn't use it by default because it mucks up the ability to have varargs.

Is the 64-bit value an A-line instruction?

This bit?
Code:
= {0xA193, 0x225F, 0x22C8, 0x2280};
?

If so, the = followed by hex means it's inlined machine code. It's four instructions: 0xA193 is, indeed, the A-line instruction that calls the _Microseconds trap. The three beginning 0x22 are move instructions, presumably to clean up after the trap call. I didn't write this code, it's lifted straight from a later version of the Apple headers than came with THINK C, and Inside Macintosh doesn't give much indication how to call _Microseconds from assembly, so I'm guessing a bit here.
 

Crutch

Well-known member
I’m not sure when the _Microseconds trap was added (it’s not in Inside Mac Volume VI, I think?), but you can also do this without _Microseconds in a way that works under System 6.

Under System 6.0.3 and above, _RmvTime returns any unused time in the tmCount field of the task record. So for microsecond timing, set up a Time Manager task that will run for a very long time, and time it in microseconds. Call _InsTime and _PrimeTime. (You don’t need a completion routine callback.) When the thing you want to time is done (before the timer has expired), call _RmvTime. Look at how much time is left on the timer and subtract it from your original timer duration. That’s the elapsed time in microseconds.

See Inside Mac, Volume IV, p. 23-15 for a detailed example (in assembly, but you could easily do this from C) that takes the further step of computing and subtracting toolbox call overhead.
 

ymk

Well-known member
This bit?

Yes, that makes sense. It's popping a pointer off the stack and moving two longs to that address.

I’m not sure when the _Microseconds trap was added (it’s not in Inside Mac Volume VI, I think?), but you can also do this without _Microseconds in a way that works under System 6.

So Microseconds() is either an undocumented toolbox routine, or added in System 7. If it's the latter, I expect I'll get a bomb in System 6.

Under System 6.0.3 and above, _RmvTime returns any unused time in the tmCount field of the task record. So for microsecond timing, set up a Time Manager task that will run for a very long time, and time it in microseconds. Call _InsTime and _PrimeTime. (You don’t need a completion routine callback.) When the thing you want to time is done (before the timer has expired), call _RmvTime. Look at how much time is left on the timer and subtract it from your original timer duration. That’s the elapsed time in microseconds.

Your way seems simpler than the document I linked. Overhead is important since I may be calling it over a hundred times per second. I'll take a look at the IM example.

Thanks for the help. :)
 

cheesestraws

Well-known member
So Microseconds() is either an undocumented toolbox routine, or added in System 7. If it's the latter, I expect I'll get a bomb in System 6.

It's certainly in the revised IM under 'OS Utilities', so I'd bet System 7+ if it's not in the numbered ones
 

Crutch

Well-known member
Agree, though I checked in vain in IM:OS Utilities for any info on when it was added/which system versions are supported. Browsing around here it looks like it was added in July 1990 though appears to have initially been a private routine and required a patch on some machines with older ROMs (IIci and Portable are mentioned).

 

Crutch

Well-known member
Cool! I am curious to know if it works under System 6 (if indeed you are trying to make that work).
 

Crutch

Well-known member
Thanks for confirming. I would love to find out when _Microseconds was added. Meanwhile if you need it, the solution I noticed above using _RmvTime should work under System 6.
 
Top