Parallelism in Hardware
Lecture Slides (pptx/pdf)
- “Real” parallelism (as opposed to just pipelining)
- has multiple “execution units” (ALU, load, store unit, etc.)
- maybe even multiple processing elements/”cores”
- has multiple “execution units” (ALU, load, store unit, etc.)
Resources + How to use the resources = Parallel Architecture
In general, to scale better, partition resources instead of sharing them
Partition ↑ scaling ↑ utilization ↓
Sharing ↑ scaling ↓ utilization ↑
Resources
Communication
- bus
- point-to-point wires
- interconnection networks
- shared storage (cache-coherent shared memory)
Synchronization
- clock
- handshaking protocol
- mutex/lock
- barrier
- explicit signaling (e.g. reset, interrupt)
Control
- instructions carry control information around, tell the resources what to do
- instruction sequencer produces instructions
- instructions tell HW what to do, but the HW can rearrange instructions as it sees fit
We didn’t include power as a resource, since it’s not clear how we can partition power; however, it’s definitely still something we need to worry about
(aside)
Registers → statically addressed, “baked” into the instruction
Memory → dynamically addressed
ILP Architectures
- multiple instructions going to multiple ALUs
- dependencies limit ILPs
In-order Superscalar
- sequence of instructions go to sequence of ALUs - in order issue, in order complete, no reorder buffer
Out-of-order Superscalar
- sequencer issues/dispatches instructions into out-of-order scheduler (reservation stations), which then dispatches/issues instructions to the ALUs; each instruction possibly accesses memory and eventually retires to the register file
- concurrency achieved with multiple ALUs
- communication resources:
- complex bypass network
- instructions communicate with each other through register writes/reads
- completion signals that tell the scheduler an instruction is finished
- synchronizations
- register names used to sync between instructions (implicit synchronization from software level, explicit synchronization from hardware level)
- also need to sync on interrupts/exceptions
- memory operations (loads/stores) need to be synchronized to enforce whatever memory consistency model being supported
- also have other explicit synchronizations: memory fences, barriers, software constructs (locks, semaphores, etc.)
- what’s partitioned/shared?
- ALUs partitioned (although it’s “shared” in the slides - we’re trying to demonstrate the fact that any instruction can go to any ALU)
- instructions partitioned
- everything else shared
- most number of ALUs possible? 4? 8? Beyond this, things get too hard to build (for example, bypass-network complexity grows as n2 in the number of ALUs)
SMT/TLS
- multiple threads of instructions feeding an out-of-order scheduler (i.e. multiple sequencers feeding 1 scheduler)
- better chance of 100% ALU utilization, because we have more instructions to schedule from…
- but it’s hard to achieve this due to control dependencies (branches)
- (aside) some people think SMT requires out-of-order, but some people think you can have SMT without out-of-order…
- what’s shared/partitioned?
- everything the same as out-of-order superscalar except:
- sequencer is now partitioned among different threads
- architectural registers are now also partitioned
- everything the same as out-of-order superscalar except:
- better scaling of sequencer
- if we want more threads, just add more sequencers
- but at some point, other shared resources will become the bottleneck
- if we want more threads, just add more sequencers
- difference between SMT and TLS
- SMT
- Software explicitly specifies threads
- TLS
- Hardware speculates and makes its own threads
- SMT
VLIW
- each instruction contains control information for more than 1 ALU
- typically in-order
- 1 set of registers, 1 set of memory, 1 set of bypass network
- no scheduler, everything in order, synchronized by clock and compiler
- compiler does scheduling
- finding things to run in parallel is the software/compiler’s responsibility!
- if compiler can’t find instructions to execute in all the lanes, then we need to insert NOPs (causes code bloat, but EPIC fixed this by removing explicit NOPs)
- ALUs are partitioned
- each instruction assigned an ALU at compile time!
Explicit dataflow
- 1 sequencer which decides which ALU each instruction goes to
- sequencer guided by heuristics, based on information from:
- compiler
- the instruction
- or dynamic runtime information
- sequencer guided by heuristics, based on information from:
- separate scheduler for each ALU (ALU + scheduler = processing element)
- each scheduler does less work; don’t have 1 giant scheduler that does everything
- scheduler decides which instruction next executes on the ALU
- each processing element broadcasts results to a common bus
- register files also partitioned
- most communications to the local register file; less global communications (if done right)
- no bypass network
DLP Architectures
- multiple “lanes”
- 1 sequencer, multiple ALUs, each ALU has its own register and possibly local memory, then a shared memory hierarchy
- for each instruction, each lane does the same operation at the same time, touches the same register, then goes to the local memory or global memory
- compiler and clock synchronizes
- only explicit communication (i.e. have to explicitly shuffle to move things around from one lane to another)
- sequencer, instruction shared; global memory may be shared (depends; in some architectures you can’t read memory aligned with another lane)
- lots of things get partitioned - good scaling, but bad utilization sometimes
Vector
- specifies the first word, then subsequent words are distributed to ALUs
SIMD
- can specify which word goes in which ALU
