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.
 

David Cook

Well-known member
I can confirm that _Microseconds does not exist in 6.0.8 or 6.0.8L, but it does exist in System 7.0 (first release, no tune-up). I tested on a Mac IIci to rule out it being a ROM improvement.

The improved time manager is documented in Inside Macintosh VI (the System 7 updates). However, no mention is made of the _Microseconds trap. Not sure why not.
 

joevt

Well-known member
Microseconds is discussed in "Operating System Utilities" Chapter 4 "Date, Time, and Measurement Utilities".

The Revised Time Manager is discussed in "Processes" Chapter 3 "Time Manager" (System 6.0.3). It also discusses the Original Time Manager (earlier systems), and the Extended Time Manager (System 7.0).

These are later books than the Inside Macintosh ones.
 

David Cook

Well-known member
These are later books than the Inside Macintosh ones.

Thank you for referencing these. It will help in future searches.

I was more expressing curiosity as to why _Microseconds wasn't listed in Inside Macintosh VI even though it was introduced in System 7.0.
 

Snial

Well-known member
From Inside Macintosh Processes:

"When you remove an active task from the revised Time Manager's queue, any time remaining until the scheduled execution time is returned in the tmCount field."

This could be pretty useful for creating a microsecond substitute when running System 6.0.3 to 6.0.8. The tmCount microseconds value can represent 2147.483648s (2^31 µs). I think this kind of approach could be used to simulate Microseconds():

C:
uint32_t gMicroSecondSecondBase=0, gMicroSecondUsBase=0;
#define kMicroSecondUpdatePeriodS (1000L) /* 1000s */
#define kMicroSecondsPerSecond (1000000L)
#define kMicroSecondUpdatePeriodUs (kMicroSecondUpdatePeriodS*kMicroSecondsPerSecond)

typedef struct {
    TMTask iTask;
    uint32_t iA5;
}MicroSecondInfo;

MicroSecondInfo gMicroSecondInfo;

void MicroSecondTask(void)
{
    uint32_t oldA5;
    MicroSecondInfo *info;
    asm {
        movea.l a1,(info)
    } // equivalent of the Pascal function on page 3-31 for THINK C.
    oldA5=SetA5(info->iA5); // set A5, returning the old one.
    gMicroSecondBase+=kMicroSecondUpdatePeriodS;
    oldA5=SetA5(oldA5); // Restore previous A5.
}

void MicroSecondTaskInit(void)
{
    MicroSecondInfo *info=&gMicroSecondInfo;
    info->iTask.tmAddr=(ProcPtr)&MicroSecondTask;
    info->iTask.tmWakeUp=0;
    info->iTask.tmReserved=0;
    info->iA5=GetCurrentA5;
    gMicroSecondBase=0L;
    InsTime((TMTask*)info);
    PrimeTime((TMTask*)info,-kMicroSecondUpdatePeriod);
}

uint32_t MicroSecondGet(uint32_t *aSeconds)
{
    MicroSecondInfo *info=&gMicroSecondInfo;
    uint32_t usLeft;
    RmvTime((TMTask*)info); // Remaining time in tmCount, -ve value.
    usLeft=kMicroSecondUpdatePeriod-info->iTask.tmCount;
    gMicroSecondUsBase+=usLeft;
    if(gMicroSecondUsBase>=kMicroSecondUpdatePeriodUs) { // overflow?
        gMicroSecondSecondBase+=gMicroSecondUsBase/kMicroSecondsPerSecond;
        gMicroSecondUsBase%=kMicroSecondsPerSecond;
    }
    *aSeconds=gMicroSecondSecondBase;
    InsTime((TMTask*)info);
    PrimeTime((TMTask*)info,-kMicroSecondUpdatePeriod);
    return gMicroSecondUsBase;
}

I haven't bothered to adjust for the TMOverhead, but that's covered on page 3-15 and could be easily incorporated. MicroSecondGet() isn't a fast routine, but for microsecond resolution timings that aren't needed very often (which might defeat the purpose), at least you'd get a result to a resolution of 20µs.

Ultimately, a VIA timer will be being used to provide the 20µs resolution; so a proper Microseconds() substitute can be used if this is reverse engineered, though I guess with different Macintosh models, different VIA timers might be being used.
 
Last edited:
Top