views:

166

answers:

3

Hi,

The following piece of code was given to us from our instructor so we could measure some algorithms performance:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    access_counter(&ncyc_hi, &ncyc_lo);

    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;

    result = (double) hi * (1 << 30) * 4 + lo;

    return result;
}

However, I need this code to be portable to machines with different CPU frequencies. For that, I'm trying to calculate the CPU frequency of the machine where the code is being run like this:

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

The problem is that the result is always 0 and I can't understand why. I'm running Linux (Arch) as guest on VMware.

On a friend's machine (MacBook) it is working to some extent; I mean, the result is bigger than 0 but it's variable because the CPU frequency is not fixed (we tried to fix it but for some reason we are not able to do it). He has a different machine which is running Linux (Ubuntu) as host and it also reports 0. This rules out the problem being on the virtual machine, which I thought it was the issue at first.

Any ideas why this is happening and how can I fix it?

A: 

Okay, since the other answer wasn't helpful, I'll try to explain on more detail. The problem is that a modern CPU can execute instructions out of order. Your code starts out as something like:

rdtsc
push 1
call sleep
rdtsc

Modern CPUs do not necessarily execute instructions in their original order though. Despite your original order, the CPU is (mostly) free to execute that just like:

rdtsc
rdtsc
push 1
call sleep

In this case, it's clear why the difference between the two rdtscs would be (at least very close to) 0. To prevent that, you need to execute an instruction that the CPU will never rearrange to execute out of order. The most common instruction to use for that is CPUID. The other answer I linked should (if memory serves) start roughly from there, about the steps necessary to use CPUID correctly/effectively for this task.

Of course, it's possible that Tim Post was right, and you're also seeing problems because of a virtual machine. Nonetheless, as it stands right now, there's no guarantee that your code will work correctly even on real hardware.

Edit: as to why the code would work: well, first of all, the fact that instructions can be executed out of order doesn't guarantee that they will be. Second, it's possible that (at least some implementations of) sleep contain serializing instructions that prevent rdtsc from being rearranged around it, while others don't (or may contain them, but only execute them under specific (but unspecified) circumstances).

What you're left with is behavior that could change with almost any re-compilation, or even just between one run and the next. It could produce extremely accurate results dozens of times in a row, then fail for some (almost) completely unexplainable reason (e.g., something that happened in some other process entirely).

Jerry Coffin
I just dug into the VMWare time keeping spec, his code _should_ be working. From the docs, rdtsc _should_ work as expected, though I'm quite sure accuracy is better when paravirtualized.
Tim Post
Out of order execution will NOT account for his problem. Sleep is not a single instruction, and no amount of out of order execution will re-order both RDTSC to be right next to each other. Even if it did, on an intel CPU rdtsc increments for every clock cycle of the chip. It is impossible for two calls, even if consecutive, to return the same value.
SoapBox
I agree with SoapBox - after all, inside the `sleep()` function is a call into the kernel; there's a very large number of instructions hidden behind that `call`.
caf
@caf: if he was using a single-core processor, that would be true. With two (or more) cores, it's entirely possible for two executions of RDTSC to return exactly the same value, one from each core (though this would be rare and isn't likely the source of what he's seeing).
Jerry Coffin
My laptop is 5 years old, running on a single core here...
Nazgulled
@Nazgulled: if you're truly getting a difference of 0 (not just a number than seems smaller than reasonable), then I'd have to say it sounds like a problem being caused by the virtual machine, regardless of what their docs say. On a (properly functioning) single core, no two executions of RDTSC should return the same value (without using `WRMSR` to modify it in between).
Jerry Coffin
@Jerry Coffin - It took a little while to dig it up, but yes, an emulated RDTSC could be the cause.
Tim Post
My guess is that the problem lies somewhere on the `sleep()` call and not on RDTSC. I'm saying this because the difference is 0 only with `sleep()`. If I implement a loop that takes 1s to execute, I'll have a time difference of more than 0.
Nazgulled
+1  A: 

As for VMWare, take a look at the time keeping spec (PDF Link), as well as this thread. TSC instructions are (depending on the guest OS):

  • Passed directly to the real hardware (PV guest)
  • Count cycles while the VM is executing on the host processor (Windows / etc)

Note, in #2 the while the VM is executing on the host processor. The same phenomenon would go for Xen, as well, if I recall correctly. In essence, you can expect that the code should work as expected on a paravirtualized guest. If emulated, its entirely unreasonable to expect hardware like consistency.

Tim Post
+1  A: 

I can't say for certain what exactly is wrong with your code, but you're doing quite a bit of unnecessary work for such a simple instruction. I recommend you simplify your rdtsc code substantially. You don't need to do 64-bit math carries your self, and you don't need to store the result of that operation as a double. You don't need to use separate outputs in your inline asm, you can tell GCC to use eax and edx.

Here is a greatly simplified version of this code:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

Also you should consider printing out the values you're getting out of this so you can see if you're getting out 0s, or something else.

SoapBox
I get an error compiling your code: `expected string literal before ')' token`.
Nazgulled
Fixed it, sorry.
SoapBox
It compiles now, thanks for the example. However, I still get a frenquency of zero. I don't think the problem is not on how rdtsc is implemented. And since this doesn't fix my problem, I rather use the code provided by the instructor.
Nazgulled