10

I'm looking at this summary of the Intel x86 integer instruction set (though the same data seems to have been published in a number of places), which gives instruction timings on a per-instruction-variant basis for the Intel CPUs from 8088 to Pentium.

For example, take the CBW mnenomic (opcode 98 hex):

CBW     Convert byte to word   (AL --> AX)

                bytes   8088    186     286     386     486     Pentium
                 1       2       2       2       3       3       3   NP

        Example:        cbw

What is included in the 2 clock cycles stated for the 8088? Or the 3 cycles for the 386?

As one specific example to hopefully give an idea of what I'm looking for: do those clock cycles include the memory fetch of the instruction, or is that assumed to have happened before one starts counting cycles?

Also, what about instructions such as CALL which do implicit memory accesses, such as CALL's PUSH(es) to the stack for the return address? Do the 23 cycles stated for a near CALL on an 8088 include that, considering that memory access would be at the mercy of RAM latency, which (particularly on later CPUs) is not necessarily directly related to the CPU clock?

14

The documented execution times count the cycles required to execute the instruction itself, including any memory accesses caused by the instruction, and ignoring everything else. The Intel manuals provide pseudo-code showing what each instruction does; you can consider that the cycle counts cover that, only.

Thus in CBW’s case, the iAPX88 manual shows:

if (AL) < 80H then (AH) ← 0 else (AH) ← FFH

In CALL’s case:

if Inter-Segment then
  (SP) ← (SP) - 2
  ((SP) + 1 : (SP)) ← (CS)
  (CS) ← SEG
(SP) ← (SP) - 2
((SP) + 1 : (SP)) ← (IP)
(IP) ← DEST

which covers the pushes. When moving to the 386, both instructions gain 32-bit variants (98h is CWDE in 32-bit mode) which can explain the added cycles: you need additional checks to choose between 16- and 32-bit behaviour. (The instruction mix changed quite a bit anyway so different cycle counts aren’t surprising on a 386 or 486.)

In particular, the execution times ignore three of the main “cycle eaters” (up to the 486):

  • the prefetch queue, which takes time before an execution starts running and is variable based on circumstances outside the executed instruction’s control;
  • DRAM refresh, which steals memory cycles independently of executing instructions;
  • wait states, which delay instructions in variable ways again outside the execution’s control;

and eaters relevant on later CPUs, including:

  • the effects of caches;
  • alignment penalties (which can be calculated using the number of memory transfers, which is documented in some timing tables).

This explains why the cycles given for any specific opcode are constant (or deterministic formulas), at least before Pentiums: any variability is excluded. It also explains most of the differences between 8088 and 8086 instruction times: any two-byte access takes four cycles more on the 8088 than on the 8086. It explains the CALL timings too: 23 cycles is for a near direct call on the 8088, where the relative displacement is in the prefetch buffer and doesn’t need to be retrieved; the time taken here is the push required for the 16-bit near return address, which explains the four-cycle difference compared to the 8086. A far direct call takes 36 cycles on the 8088 v. 28 cycles on the 8086, i.e. the difference in time taken to push two words to the stack.

The ultimate reference for all this, up to the 486 era, is Michael Abrash’s In the Lair of the Cycle-Eaters chapter in the Graphics Programming Black Book (which is adapted from Zen of Assembly Language). After that you should check out Agner Fog’s optimisation manuals (and starting with the Pentium, things get quite a bit more complicated, even more on the latest CPUs which have many other sources of variability). The timing table you link to is greatly simplified, there are much more detailed tables available in Intel’s documentation or in books such as The Processor and Coprocessor.

1

On the original 8088 processor, the easiest way to estimate execution speed is generally to ignore cycle counts and instead count memory accesses, including instruction fetches, and multiply by four. Although some instructions spend enough time executing internally that the memory bus ends up being idle for a few cycles, unless the average execution time for a sequence of code ends up exceeding the memory-fetch time by more than 12 cycles, the memory-access times will dominate.

  • 3
    This may very well be correct as stated, but I'm not sure it's an answer to the question as asked. – a CVn Jul 11 '17 at 21:44
  • @MichaelKjörling: On many of the 8088's relatives, execution time will be the larger of the time required to perform the internal operations or the instruction fetches, but internal execution time is often dominant. On the original 8088, however, the memory bus time is so dominant that the nominal cycle count, and what's "included" in it, are often irrelevant. – supercat Jul 11 '17 at 22:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.