You can’t trust the system clock's timing of the CPUID instruction, because the hypervisor can simply mask the cycle overhead of any VM trap. But you might be able to trust the timing of side effects that don’t cause VM traps...
For instance, you can saturate any of the caches in the system, and baseline the cycles it takes to read known cached values. Then you cause VM trap (for instance, by executing CPUID). The VM exit and entrance, along with the software that runs in the hypervisor to emulate CPUID, will evict cache entries. When you get control back from the hypervisor, you loop timing the "cached" values again; if they differ significantly, you’re virtualized.
Hiding/detecting virtualization is an arms race, and I'm not sure who would eventually win. But FWIW Joanna, the author of the original "blue pill" undetectable rootkit, says that we should stop this arms race in its tracks by having CPU manufacturers include a CHECK instruction that cannot be trapped, so a program can always tell if it's being run under virtualization.