59
$\begingroup$

I've always wondered why processors stopped at 32 registers. It's by far the fastest piece of the machine, why not just make bigger processors with more registers? Wouldn't that mean less going to the RAM?

$\endgroup$
7
  • 2
    $\begingroup$ I guess beyond a certain point all your local variables fit into the registers. The actual data you are working with is probably too large anyway $\endgroup$
    – Niklas B.
    Commented Mar 13, 2014 at 15:28
  • 16
    $\begingroup$ Diminishing returns. Clearly, registers are "more expensive" (in various senses) than RAM or we'd just have 8GB of registers. $\endgroup$ Commented Mar 13, 2014 at 16:22
  • 6
    $\begingroup$ One of the reason its so fast is because there arent many of them. $\endgroup$
    – stackErr
    Commented Mar 13, 2014 at 21:55
  • 5
    $\begingroup$ There is a difference between how many registers the cpu has in total, and how many you can use at once. $\endgroup$ Commented Mar 14, 2014 at 14:48
  • $\begingroup$ CPUs and GPUs hide latency primarily by caches and multithreading respectively. So, CPUs have few registers, whereas GPUs have tens of thousands on registers. See my survey paper on GPU register file which discusses all these trade-offs and factors. $\endgroup$
    – user984260
    Commented Mar 21, 2016 at 15:12

7 Answers 7

92
$\begingroup$

First, not all processor architectures stopped at 32 registers. Almost all the RISC architectures that have 32 registers exposed in the instruction set actually have 32 integer registers and 32 more floating point registers (so 64). (Floating point "add" uses different registers than integer "add".) The SPARC architecture has register windows. On the SPARC you can only access 32 integer registers at a time, but the registers act like a stack and you can push and pop new registers 16 at a time. The Itanium architecture from HP/Intel had 128 integer and 128 floating point registers exposed in the instruction set. Modern GPUs from NVidia, AMD, Intel, ARM and Imagination Technologies, all expose massive numbers of registers in their register files. (I know this to be true of the NVidia and Intel architectures, I am not very familiar with the AMD, ARM and Imagination instruction sets, but I think the register files are large there too.)

Second, most modern microprocessors implement register renaming to eliminate unnecessary serialization caused by needing to reuse resources, so the underlying physical register files can be larger (96, 128 or 192 registers on some machines.) This (and dynamic scheduling) eliminates some of the need for the compiler to generate so many unique register names, while still providing a larger register file to the scheduler.

There are two reasons why it might be difficult to further increase the number of registers exposed in the instruction set. First, you need to be able to specify the register identifiers in each instruction. 32 registers require a 5 bit register specifier, so 3-address instructions (common on RISC architectures) spend 15 of the 32 instruction bits just to specify the registers. If you increased that to 6 or 7 bits, then you would have less space to specify opcodes and constants. GPUs and Itanium have much larger instructions. Larger instructions come at a cost: you need to use more instruction memory, so your instruction cache behavior is less ideal.

The second reason is access time. The larger you make a memory the slower it is to access data from it. (Just in terms of basic physics: the data is stored in 2-dimensional space, so if you are storing $n$ bits, the average distance to a specific bit is $O(\sqrt{n})$.) A register file is just a small multi-ported memory, and one of the constraints on making it larger is that eventually you would need to start clocking your machine slower to accommodate the larger register file. Usually in terms of total performance this is a lose.

$\endgroup$
6
  • 1
    $\begingroup$ I would have mentioned SPARC64 VIIIfx's 256 FPRs and 32 extra non-window GPRs, accomplished by adding a Set XAR instruction which provides 13 bits each for the next one or two instructions. It was targeted at HPC, so the register count is more understandable. I would also have been tempted to expound on some of the trade-offs and techniques associated with more registers; but you showed the wisdom to avoid a more exhausting (and even then not exhaustive) answer. $\endgroup$
    – user4577
    Commented Mar 14, 2014 at 0:06
  • 2
    $\begingroup$ Adding a bit on the diminishing benefit of more registers for "general purpose" code might be worthwhile, though finding meaningful measurements is not easy. I think Mitch Alsup mentioned on comp.arch that extending x86 to 32 registers rather than 16 would have gained about 3% in performance compared to (ISTR) 10-15% for the 8 to 16 register extension that was chosen. Even for a load-store ISA, going to 64 probably provides little benefit (at least for current GP code). (BTW, GPUs often share registers across threads: e.g., one thread with 250 leaving on 16 total private for other threads.) $\endgroup$
    – user4577
    Commented Mar 14, 2014 at 0:23
  • $\begingroup$ Interesting to see that environment management (hence alpha-conversion), often associated with high-level languages, is actually used down at the register level. $\endgroup$
    – babou
    Commented Mar 14, 2014 at 9:16
  • $\begingroup$ @PaulA.Clayton I always thought that IA-64 is the architecture that has the largest number of ISA registers $\endgroup$
    – phuclv
    Commented Mar 14, 2014 at 14:48
  • $\begingroup$ @LưuVĩnhPhúc The SPARC64 VIIIfx was HPC-specific. FYI, the Am29k (introduced around 1987-8) had 64 global and 128 windowed GPRs which is more GPRs than Itanium (which does have 8 branch registers and a loop count register whose function would be in GPRs in some other ISAs). $\endgroup$
    – user4577
    Commented Mar 14, 2014 at 16:19
18
$\begingroup$

Just two more reasons for limiting the number of registers:

  • Little gain to be expected: CPU such as current Intel/AMD x64 models have 32kByte and more of L1-D cache, and access to the L1 cache usually takes only one clock cycle (compared to about a hundred clock cycles for a complete single RAM access). So there is little to be gained from having more data in registers compared to having data in the L1 cache
  • Additional computational costs: Having more registers creates an overhead that may actually make a computer slower:
    • In multitasking-environments, a task switch usually has to save the contents of all registers of the process that is left to memory, and has to load those of the process to be entered. The more registers you have, the longer this takes.
    • Similarly, in architectures without register windows, cascaded function calls use the same set of registers. So a function A calling a function B uses the same set of registers as B itself. Therefore, B has to save the contents of all registers it uses (which still hold A's values) and has to write them back before returning (in some calling conventions it is the job of A to save its register contents before calling B, but the overhead is similar). The more registers you have, the longer does this saving take, and thus the more expensive a function call becomes.
$\endgroup$
4
  • $\begingroup$ How does it work for the L1 cache so that we do not have the same problem as for the registers? $\endgroup$
    – babou
    Commented Mar 14, 2014 at 9:07
  • 4
    $\begingroup$ On high performance processors L1 Dcache latency is more typically 3 or 4 cycles (including address generation), e.g., Intel's Haswell has 4 cycle latency (not having a data dependence register latency is also easier to hide in the pipeline). Dcache also tends to support fewer accesses per cycle (e.g., 2 read, 1 write for Haswell) than a register file (e.g., 4 read, 6 write for Alpha 21264 which replicated the file, 2 files with 4 reads is faster than 1 with 8). $\endgroup$
    – user4577
    Commented Mar 14, 2014 at 12:15
  • $\begingroup$ @PaulA.Clayton: If the L1 cache has a 3-4 cycle latency, that would suggest that there might be some benefit to having e.g. a few sets of 64 words of single-cycle memory with its own 64-word address space, and dedicated "load/store direct" instructions, especially if there were a way to push all the non-zero values followed by a word saying which words were non-zero, and then a way to pop them back (zeroing any registers not popped). Many methods have between 16 and 60 words of local variables, so cutting access time for those from 3-4 cycles to one would seem helpful. $\endgroup$
    – supercat
    Commented Mar 14, 2014 at 22:35
  • $\begingroup$ @supercat Various stack (and global/TLS [e.g., Knapsack]) cache ideas have been presented in academic papers as well as mechanisms like the signature buffer (PDF) Actual use, not so much (it seems). This is getting chatty (so should probably end or go elsewhere). $\endgroup$
    – user4577
    Commented Mar 15, 2014 at 2:42
4
$\begingroup$

A lot of code has lots of memory accesses (30% is a typical figure). Out of that, typically about 2/3rds are read accesses and 1/3rds are write accesses. This is not due to running out of registers as much as accessing arrays, accessing object member variables etc.

This HAS to be done in memory (or data cache) due to how C/C++ is made (everything you can get a pointer needs to have an address to has to be potentially stored in memory). If the compiler can guess that you won't write to variables willy-nilly using crazy indirect pointer tricks it will put them in registers, and this works great for function variables but not for globally accessible ones (generally, everything that comes out of malloc()) because it's essentially impossible to guess how the global state will change.

Because of this, it's not common that the compiler will be able to do anything with more than about 16 general usage registers anyways. Which is why all popular architectues have about that many (ARM has 16).

MIPS and other RISCs tend to have 32 because it's not very hard to have that many registers - the cost is low enough so it's a bit of a "why not?". More than 32 is mostly useless and has the downside of making the register file longer to access (each doubling in the number of registers potentially adds an extra layer of multiplexers which adds a bit more delay...). It also makes instructions slightly longer on average - which means that when running the kind of programs that depend on instruction memory bandwidth, your extra registers are actually slowing you down!

If your cpu is in-order and doesn't do register renaming and you're trying to do lots of operations per cycle (more than 3), then in theory you need more registers as your numbers of ops per cycle goes up. This is why the Itanium has so many registers! But in practice, apart from numerical-floating-point or SIMD oriented code (which Itanium was really good at), most code will have lots of memory reads/writes and jumps which make this dream of more-than-3 ops per cycle impossible (especially in server-oriented software like databases, compilers, high-level language execution like javascript, emulation etc...). This is what sank Itanium.

It all comes down to the difference between computation and execution!

$\endgroup$
2
$\begingroup$

Who tells you that processor always have 32 registers? x86 has 8, ARM 32-bit and x86_64 have 16, IA-64 has 128, and many more other numbers. You can have a look here. Even MIPS, PPC or any architectures that have 32 general purpose registers in the instruction set, the number is much larger than 32 since there are always still flag registers (if any), control registers... not including renamed registers and hardware registers

Everything has its price. The larger the number of registers, the more work you have when doing task switching, the more space you need in the instruction encoding. If you have less register, you don't have to store and restore much when calling and returning from functions or switching tasks with the trade off of lacking of registers in some compute-extensive code

Moreover, the larger the register file, the more expensive and complex it will be. SRAM is the fastest and most expensive RAM so it is only used in CPU cache. But it's still much cheaper and takes less area than a register file with the same capacity.

$\endgroup$
2
$\begingroup$

For example, a typical Intel processor has "officially" 16 integer and 16 vector registers. But in reality, there are many more: The processor uses "register renaming". If you have an instruction reg3 = reg1 + reg2 you would have a problem if another instruction using reg3 hadn't finished yet - you couldn't execute the new instruction in case it overwrites reg3 before it has been read by the previous instruction.

Therefore there are about 160 or so real registers. So the simple instruction above is changed to "regX = reg1 + reg2, and remember that regX contains reg3". Without rename registers, out of order execution would be absolutely dead in the water.

$\endgroup$
1
$\begingroup$

I am not an electrical engineer, but I think another possibility for the reason to limit the number of registers, is routing. There are a limited number of arithmetic units, and they must be able to take input from every register, and output to every register. This is especially true when you have pipelined programs that can execute many instructions per cycle.

A simple version of this would have $\mathcal O(n^2)$ complexity, making increasing the number of registers unscalable, or otherwise requiring a redesign of the routing to something a lot more complicated to route everything with a better complexity.

I got the idea for this answer from watching some of Ivan Godard's talks on the Mill CPU. Part of the innovation of the Mill CPU is that you cannot output to arbitrary registers - the outputs are all pushed onto a register stack or "belt", which thus reduces routing problems, because you always know where the output will go. Note they still have the routing problem for getting the input registers to the arithmetic units.

See The Mill CPU Architecture - the Belt (2 of 9) for the problem statement, and Mill's solution.

$\endgroup$
2
  • $\begingroup$ "They must be able to take input from every register, and output to every register." - I'd expect this is typically implemented with a bus, there doesn't have to be a separate connection to the ALU(s) for every register. $\endgroup$
    – user20574
    Commented Aug 6, 2016 at 9:34
  • 1
    $\begingroup$ @immibis: If you want to move data around in 300 picoseconds a bus won't do it. And if you want to move lots of data around (for example to perform three instructions with two operands and one result each in the same cycle) a bus will absolutely, absolutely not work. $\endgroup$
    – gnasher729
    Commented Aug 15, 2016 at 8:08
1
$\begingroup$

As for the MIPS ISA, Hennessy and Patterson, Computer Organization and Design 4th edition p. 176, answers this specific question directly:

Smaller is faster. The desire for speed is the reason that MIPS has 32 registers rather than many more.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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