Heterogeneous Computing
Heterogeneous Computing
Editor in Chief
M. Tamer Özsu, University of Waterloo
ACM Books is a series of high-quality books for the computer science community, published
by ACM and many in collaboration with Morgan & Claypool Publishers. ACM Books
publications are widely distributed in both print and digital formats through booksellers
and to libraries (and library consortia) and individual ACM members via the ACM Digital
Library platform.
Mohamed Zahran
New York University
All rights reserved. No part of this publication may be reproduced, stored in a retrieval
system, or transmitted in any form or by any means—electronic, mechanical, photocopy,
recording, or any other except for brief quotations in printed reviews—without the prior
permission of the publisher.
Designations used by companies to distinguish their products are often claimed as trade-
marks or registered trademarks. In all instances in which the Association for Computing
Machinery is aware of a claim, the product names appear in initial capital or all capital
letters. Readers, however, should contact the appropriate companies for more complete
information regarding trademarks and registration.
This book was typeset in Arnhem Pro 10/14 and Flama using ZzTEX.
First Edition
10 9 8 7 6 5 4 3 2 1
To my family, without whom I wouldn’t have existed in the first
place and wouldn’t have managed through this life.
Contents
Preface xiii
Chapter 4 Programmability 67
4.1 Wish List of a Programmer 67
4.2 Psychology of Programming 69
4.3 What Do We Have? The Current Status Quo 71
4.4 In Conclusion 88
References 97
Index 111
Author’s Biography 113
Preface
The term heterogeneous computing has become famous lately (lately, meaning in
the last five years!). It started infiltrating many articles. Research papers have been,
and are still being, written about heterogeneous computing and its implications
on both software and hardware. The definition of this term is quite straightfor-
ward: executing programs on a computing platform with computing nodes of different
characteristics. What is tricky is whether this is a good thing or a bad thing.
From a hardware perspective, as we will see later in this book, it is a good thing.
Each computing node is efficient in specific types of applications. Efficiency here
means it gets the best performance (e.g., speed) with lowest cost (e.g., power). This
excellence in price-performance is very much needed in our current era of big data,
severe power consumption, and the road to exascale computing. So if we can assign
to each node the part of the program that it excels at, then we get the results of
price-performance, and this is the main challenge facing the software community.
From a software perspective, heterogeneous computing seems like bad news
because it makes programming much more challenging. As a developer, you have
way more tasks than with traditional homogeneous computing. You need to know
about different execution units, or at least learn about the computing nodes in
the system you are writing code for. Then you need to pick algorithms to make
your program, or different parts of your program, suitable for these nodes. Finally,
you need to tweak your code to get the needed performance by overcoming many
bottlenecks that certainly exist in heterogeneous computing, like communication
overhead between the different units, overhead of creating threads or processes,
management of memory access of those different units, and so on.
We cannot then say that heterogeneous computing is good or bad news. But we
can say that heterogeneous computing is now the norm and not the exception. It
is here, it will continue to be here, and we need to deal with it. But how do we deal
with it? This is the topic of this book.
xiv Preface
This book discusses the topic of heterogeneous computing from different an-
gles: hardware challenges, current hardware state-of-the-art, software issues, how
to make the best use of the current heterogeneous systems, and what lies ahead.
All the systems we use, from portable devices to supercomputers, embody some
type of heterogeneity. The main reason for that is to have good performance with
power efficiency. However, this opens the door to many challenges that we need to
deal with at all levels of the computing stack: from algorithms all the way to process
technology. The aim of this book is to introduce heterogeneous computing in the
big picture. Whether you are a hardware designer or a software developer, you need
to know how the pieces of the puzzle fit together.
This book will discuss several architecture designs of heterogeneous systems,
the role of operating systems, and the need for more efficient programming models.
The main goal is to bring researchers and engineers to the forefront of the research
frontier in the new era that started a few years ago and is expected to continue for
decades.
Acknowledgments
First and foremost, I would like to thank all my family for their support, encourage-
ment, and unconditional love. I would like to thank Steve Welch, who is actually
the one who gave me the idea of writing this book. A big thank you goes also to
Tamer Özsu, the Editor in Chief of ACM Books, for his encouragement, flexibility,
and willingness to answer many questions. Without him, this book wouldn’t have
seen the light of day.
Anything I have learned in my scientific endeavors is due to my professors, my
students, and my colleagues. I cannot thank them enough. Dear students, we learn
from you as much as you learn from us.
I would like also to thank Paul C. Anagnostopoulos and the entire copyediting
team, who got the book into much better shape than my initial version. A thank
you is also due to Achi Dosanjh, Barbara Ryan, and Bernadette Shade from ACM
for their great help in the last stages of this book.
Mohamed Zahran
December 2018
1
Why Are We Forced to
Deal with Heterogeneous
Computing?
When computers were first built, about seven decades ago, there was one item on
the wish list: correctness. Then soon a second wish appeared: speed. The notion
of speed differs of course from those old days and applications to today’s require-
ments. But in general we can say that we want fast execution. After a few more
decades and the proliferation of desktop PCs and then laptops, power became the
third wish, whether in the context of battery life or electricity bills. As computers
infiltrated many fields and were used in many applications, like military and health
care, we were forced to add a fourth wish: reliability. We do not want a computer
to fail during a medical procedure, for example; or it would have been a big loss
(financially and scientifically) if the rover Curiosity, which NASA landed on Mars
in 2012, failed. (And yes, Curiosity is a computer.) With the interconnected world
we are in today, security became a must. And this is the fifth wish. Correctness,
speed, power, reliability, and security are the five main wishes we want from any
computer system. The order of the items differs based on the application, societal
needs, and the market segment. This wish list is what directs the advances in hard-
ware and software. But the enabling technologies for fulfilling this wish list lie in
hardware advances and software evolution. So there is a vicious cycle between the
wish list and hardware and software advances, and this cycle is affected by societal
needs. This chapter explains the changes we have been through from the dawn of
computer systems till today that made heterogeneous computing a must.
In this chapter we see how computing systems evolved till the current status
quo. We learn about the concept of heterogeneity and how to make use of it. At
the end of this chapter, ask yourself: Have we reached heterogeneous computing
willingly? Or against our will? I hope by then you will have an answer.
2 Chapter 1 Why Are We Forced to Deal with Heterogeneous Computing?
Transistors
107 (thousands)
106
Single-thread
105 performance
(SpecINT × 103)
104
Frequency
3
(MHz)
10
Typical power
102 (Watts)
Number of
101
logical cores
100
Figure 1.1 Trend of different aspects of microprocessors. (Karl Rupp. 2018. 42 years of microprocessor
trend data. Courtesy of Karl Rupp [Link]
last accessed March 2018)
the years. As the figure shows, from around 2004 the number of logical cores
started to increase beyond single core. The word logical includes physical cores
with simultaneous multithreading (SMT) capability, also known as hyperthreading
technology [Tullsen et al. 1995, Lo et al. 1997]. So a single core with four-way
hyperthreading is counted as four logical cores. With SMT and the increase in the
number of physical cores, we can see a sharp increase in the number of logical
cores (note the logarithmic scale). If we look at the power metric, the 1990s was not
a very friendly decade in terms of power. We see a steady increase. After we moved
to multicore, things slowed down a bit due to the multicore era as well as the rise
of dark-silicon techniques [Allred et al. 2012, Bose 2013, Esmaeilzadeh et al. 2011]
and some VLSI tricks. “Dark silicon” refers to the parts of the processor that must
not be turned off (hence dark) in order for the heat generated not to exceed the
maximum capability that the cooling system can dissipate (called thermal design
point, or TDP). How to manage dark silicon while trying to increase performance?
This is the question that has resulted in many research papers in the second
decade of the twenty-first century. We can think of the dark-silicon strategy as a
way to continue increasing the number of cores per chip while keeping the power
and temperature at a manageable level. The figure also shows that we stopped,
or substantially slowed, increasing clock frequency. With this bag of tricks, we
sustained, so far, a steady increase of transistors, as the figure shows at its top
4 Chapter 1 Why Are We Forced to Deal with Heterogeneous Computing?
curve. There is one interesting curve remaining in the figure: the single thread
(i.e., sequential programs) performance. There is a steady increase in single thread
performance almost till 2010. The major reason is Moore’s law, which allowed
computer architects to make use of these transistors to add more features (from
pipelining to superscalar to speculative execution, etc.). Another reason is the
increase in clock frequency that was maintained till around 2004. There are some
minor factors that make single thread performance a bit better with multicore.
One of them is that the single thread program has a higher chance of executing
on a core by itself without sharing resources with another program. The other is
that of thread migration. If a single thread program is running on a core and that
core becomes warm, the frequency and voltage will be scaled down, slowing down
the program. If the program is running on a multicore, and thread migration is
supported, the program may migrate to another core, losing some performance in
the migration process but continuing at full speed afterwards.
The story outlined above relates the hardware tricks developed to manage the
power and temperature near the end of Moore’s law and the end of Dennard scaling.
Those tricks gave some relief to the hardware community but started a very difficult
problem for software folks.
Now that we have multicore processors all over the place, single thread programs
are no longer an option.
The free lunch is over [Sutter 2005]! In the good old days, you could write a se-
quential program and expect that your program would become faster with every
new generation of processors. Now, unless you write parallel code, don’t expect to
get that much of a performance boost anymore. Take another look at the single
thread performance in Figure 1.1. We moved from single core to multicore not be-
cause the software community was ready for concurrency but because the hardware
community could not afford to neglect the power issue. The problem is getting even
harder because this multicore or parallel machine is no longer homogeneous. You
are not writing code for a machine that consists of similar computing nodes but
different ones. So now we need heterogeneous parallel programming.
We saw how we moved from single core to multiple homogeneous cores. How
did heterogeneity arise? It is again a question of power, as we will see. But before
we go deeper into heterogeneity, it is useful to categorize it into two types from a
programmer’s perspective.
A machine is as useful as the programs written for it. So let’s look at heterogene-
ity from a programmer’s perspective. There is this heterogeneity that is beyond a
programmer’s control. Surprisingly, this type has been around for several years
now; and many programmers don’t know it exists! There is also heterogeneity
1.2 Heterogeneity beyond Our Control 5
within a programmer’s control. What is the difference? And how come we have
been dealing with heterogeneity without knowing it?
tance, Vcc is the supply voltage, F is the frequency, and N is the number of bits
6 Chapter 1 Why Are We Forced to Deal with Heterogeneous Computing?
switching. As we can see, there is a cubic relationship between the dynamic power
and supply voltage and frequency. Reducing the frequency and reducing the supply
voltage (up to a limit to avoid switching error) greatly reduces the dynamic power,
at the expense of performance.
There are many layers in the computing stack that do dynamic voltage and fre-
quency scaling (DVFS). It is done at the hardware level and per core. This means
that even if we think we are writing an application for a homogeneous multicore,
it is actually heterogeneous because it may have different performance measure-
ments based on the processes running on each core. It is also done at the operating
system (OS) level. With Intel processors, for example, the OS requests a particular
level of performance, known as performance-level (P-level), from the processor. The
processor then uses DVFS to try to meet the requested P-state. Up to that point, the
programmer has no control and all this is happening under the hood. There are
some techniques that involve application-directed DVFS. The programmer knows
best when high performance is needed and when the program can tolerate lower
performance for better power saving. However, this direction from the application
can be overridden by the OS or the hardware.
Heterogeneity
in
memory
Technology Nonuniform
Cache misses access
• NUMA
Volatile • NUCA
• DRAM
• SRAM • Coherence
Nonvolatile • Thrashing
• STT-RAM
• ReRAM
• MRAM
• PCM
…
environment variables. If you use PThreads, there are APIs that help you assign
thread to cores, such as pthread_setaffinity_np().
Not all the languages allow you this control, though. If you are writing in CUDA,
for example, you cannot guarantee which streaming multiprocessor (SM)—which is
a group of execution units in NVIDIA parlance—your block of threads will execute
on. But remember, you have the choice to pick the programming language you
want. So, if you want this control, you can pick a language that allows you to have
it. You have to keep in mind, though, that sometimes your thread assignments
may be overridden by the OS or the hardware for different reasons, such as thread
migration due to temperature, high overhead on the machine from other programs
running concurrently with yours, etc.
1. [Link]
12 Chapter 1 Why Are We Forced to Deal with Heterogeneous Computing?
Figure 1.3 Part of the TOP500 list of fastest supercomputers (as of November 2018). (Top 500 List.
2018. Top 500 List Super Computers (November 2018) Courtesy Jack Dongarra; Retrieved
November 2018; [Link]
2
Different Players:
Heterogeneity in
Computing
In this chapter we take a closer look at the different computing nodes that can exist
in a heterogeneous system. Computing nodes are the parts that do the computa-
tions, and computations are the main tasks of any program. Computing nodes are
like programming languages. Each one can do any computation, but some are way
more efficient in some type of computations than others, as we will see.
In 1966 Michael Flynn classified computations into four categories based on
how instructions interact with data. The traditional sequential central processing
unit (CPU) executes an instruction with its data, then another instruction with
its data, and so on. In Flynn’s classification, this computation is called single
instruction–single data (SISD). You can execute the same instruction on different
data. Think of multiplying each element of a matrix by a factor, for example. This is
called single instruction–multiple data (SIMD). The other way around, we refer to
the same data that go through different instructions as multiple instruction–single
data (MISD). There are not many examples of MISD around. With some stretch we
can call pipelining a special case of MISD. Redundant execution of instructions, for
reliability reasons, can also be considered MISD. Finally, the most generic category
is multiple instruction–multiple data (MIMD). There are some generalizations.
For instance, if we execute the same set of instructions on different data, we can
generalize SIMD to single thread (or single program)–multiple data (SPMD). One
of the advantages of such classifications is to build hardware suitable for each
category, or for categories that are used more often, as we will see in this chapter.
14 Chapter 2 Different Players: Heterogeneity in Computing
Multicore
2.1 The first player in a heterogeneity team is the multicore processor itself. Figure 2.1
shows a generic multicore processor. The de facto definition of a core now is a CPU
and its two level-1 caches (one for instructions and the other for data). Below the L1
caches are different designs. One design has a shared L2 and L3 cache, where L3
is usually the last-level cache (LLC) before going off-chip. An L2 cache is physically
distributed and logically shared to increase scalability with the number of cores.
This makes the shared L2 cache a nonuniform cache access (NUCA), as we saw in
the previous chapter. An LLC cache is also NUCA. This LLC is designed either in
SRAM or embedded DRAM (eDRAM). POWER processors, from IBM, have eDRAM
as an LLC. Another design has private L2 caches per core followed by a shared LLC.
In some recent processors, but not in many, there is also an L4 shared cache; for
example, Intel’s Broadwell i7 processor has a 128 MB L4 cache implemented in
eDRAM technology. After the cache hierarchy, we go off-chip to access the system
memory. Currently, the vast majority of system memory is in DRAM, but as we saw
earlier, nonvolatile memory technology (such as PCM, STTRAM, MRAM, ReRAM,
etc.) will soon appear and will be used with and/or in place of DRAM and also in
some cache levels.
When we consider programming a multicore processor, we need to take into
account several factors. The first is the process technology used for fabrication. It
determines the cost, the power density, and the speed of transistors. The second
factor is the number of cores and whether they support simultaneous multithread-
ing (SMT) [Tullsen et al. 1995], called hyperthreading technology in Intel lingo and
symmetrical multithreading in AMD parlance. This is where a single core can serve
more than one thread at the same time, sharing resources. So if the processor has
four cores and each one has two-way SMT capability, then the OS will see your
Cache hierarchy
Shared memory
Figure 2.2 IBM POWER9 processor. (Courtesy of International Business Machines Corporation,
© International Business Machines Corporation)
processor as one with eight cores. That number of cores (physical and logical)
determines the amount of parallelism that you can get and hence the potential per-
formance gain. The third factor is the architecture of the core itself as it affects the
performance of a single thread. The fourth factor is the cache hierarchy: the number
of cache levels, the specifics of each cache, the coherence protocol, the consistency
model, etc. This factor is of crucial importance because going off-chip to access
the memory is a very expensive operation. The cache hierarchy helps reduce those
expensive trips, of course with help from the programmer, the compiler, and the
OS. Finally, the last factor is scaling out. How efficient is a multisocket design? Can
we scale even further to thousands of processors?
Let’s see an example of a multicore. Figure 2.2 shows the POWER9 processor
from IBM [Sadasivam et al. 2017]. The POWER9 is fabricated with 14 nm FinFET
technology, with about eight billion transistors, which is a pretty advanced one, as
of 2018, even though we see lower process technologies (e.g., 10 nm) but still very
expensive and not yet in mass production. The figure shows 24 CPU cores. Each
16 Chapter 2 Different Players: Heterogeneity in Computing
core can support up to four hardware threads (SMT). This means we can have up
to 96 threads executed in parallel. There is another variation of the POWER9 (not
shown in the figure) that has 12 cores, each of which supports up to 8 hardware
threads, bringing the total again to 96 threads. The first variation, the one in the
figure, has more physical cores so is better in terms of potential performance,
depending on the application at hand, of course. Before we proceed, let’s think
from a programmer’s perspective. Suppose you are writing a parallel program for
this processor and the language you are using gives you the ability to assign threads
(or processes) to cores. How will you decide which thread goes to which core? It
is obvious that the first rule of thumb is to assign different threads to different
physical cores. But there is a big chance that you have more threads than physical
cores. In this case try to assign threads of different personalities to the same physical
core; that is, a thread that is memory bound and a thread that is compute bound,
or a thread with predominant floating point operations and one with predominant
integer operations, and so on. Of course there is no magic recipe, but these are rules
of thumb. Note that your assignment may be overridden by the language runtime,
the OS, or the hardware. Now back to the Power9.
Each core includes its own L1 caches (instructions and data). The processor
has a three-level cache hierarchy. L2 is a private 512 KB 8-way set-associative cache.
Depending on the market segment, power9 has two types of cores: SMT4 and SMT8,
where the latter has twice the fetch/decode capacity of the former. The L2 cache is
private to SMT8, but if we use SMT4 cores, it is shared among two cores. Level 3
is shared, banked, and built out of eDRAM. But DRAM has high density, as we
said earlier, and L3 is a massive 120 MB and has nonuniform cache access (NUCA).
This cache is divided into 12 regions with 20-way set associativity per region. This
means a region is local per SMT8 core, or two SMT4 cores, but can be accessed
by the other cores with higher latency (hence NUCA). The on-chip bandwidth is
7 TB/s (tera bytes per second). If we leave the chip to access the main memory,
POWER9 has a bandwidth of up to 120 GB/s to a DDR4 memory. These numbers
are important because it gives you an indication of how slow/fast getting your data
from the memory is, and how crucial it is to have a cache-friendly memory access
pattern.
For big problem sizes, you will use a machine with several multicore processors
and accelerators (like a GPU, for example). Therefore, it is important to know the
bandwidth available to you from the processor to the accelerator because it affects
your decision to outsource the problem to the accelerator or do it in-house in the
multicore itself. POWER9 is equipped with PCIe (PCI Express) generation 4 with 48
lanes (a single lane gives about 1.9 GB/s), a 16 GB/s interface for connecting neigh-
2.2 GPUs 17
boring sockets, and a 25 GB/s interface that can be used by externally connected
accelerators or I/O devices.
Multicore processors represent one of the pieces of the puzzle of heterogeneous
computing. But there are some other chips that are much better than multicore
processors for certain types of applications. The term much better here means they
have a better performance per watt. One of these well-known chips that is playing
a big role in our current era of artificial intelligence and big data is the graphics
processing unit (GPU).
GPUs
2.2 Multicore processors are MIMD in Flynn’s classification. MIMD is very generic
and can implement all other types. But if we have an application that is single
instruction (or program or thread)–multiple data, then a multicore processor may
not be the best choice [Kang et al. 2011]. Why is that? Let’s explain the reason with
an example. Suppose we have the matrix-vector multiplication operation that we
saw in the previous chapter (repeated here in Algorithm 2.1 for convenience). If
we write this program in a multithreaded way and we execute it on a multicore
processor, where each thread is responsible for calculating a subset of the vector Y,
then each core must fetch/decode/issue instructions for threads, even though they
are the same instructions for all the threads. This does not affect the correctness
of the execution but is a waste of time and energy.
If we now try to execute the same program on a GPU, the situation will be
different. SIMD architectures have several execution units (named differently by
different companies) that share the same front end for fetching/decoding/issuing
instructions, thus, amortizing the overhead of that part. This also will save a lot of
the chip real estate for more execution units, resulting in much better performance.
Figure 2.3 shows a generic GPU. Each block that is labeled lower level scheduling
can be seen as a front end and many execution units. Each execution unit is
High-level scheduling
Memory hierarchy
responsible for calculating one or more elements for vector Y in the example of
Algorithm 2.1. Why do we have several blocks then? There are several reasons.
First, threads assigned to the different execution units within the same block can
exchange data and synchronize among each other. It would be extremely expensive
to do that among the execution units of all the chips as there are hundreds in
small GPUs and thousands in high-end GPUs. So this distributed design makes the
cost manageable. Second, it gives some flexibility. You can execute different SIMD-
friendly applications on different blocks. This is why we have high-level scheduling
shown in the figure. Execution units of different blocks can communicate, albeit in
a slow manner, through the memory shared among all the blocks, labeled “memory
hierarchy” in the figure, because in some designs there are some cache levels above
the global memory as well as specialized memories like texture memory.
The confusing thing about GPUs is that each brand has its own naming conven-
tion. In NVIDIA parlance, those blocks are called streaming multiprocessors (SM or
SMX in later version) and the execution units are called streaming processors (SPs)
or CUDA cores. In AMD parlance, those blocks are called shader engines and the
execution units are called compute units. In Intel parlance, the blocks are called
slices (or subslices) and the execution units are just called: execution units. There
are some very slight differences between each design, but the main idea is almost
the same.
GPUs can be discrete, that is, stand-alone chips connected to the other proces-
sors using connections like PCIe or NVLink, or they can be embedded with the
2.2 GPUs 19
multicore processor in the same chip. On the one hand, the discrete ones are of
course more powerful because they have more real estate. But they suffer from the
communication overhead of sending the data back and forth between the GPU’s
memory and the system’s memory [Jablin et al. 2011], even if the programmer sees
a single virtual address space. On the other hand, the embedded GPUs, like Intel
GPUs and many AMD APUs, are tightly coupled with the multicore and do not suf-
fer from communication overhead. However, embedded GPUs have limited area
because they share the chip with the processor and hence are weaker in terms of
performance.
If you have a discrete GPU in your system, there is a high chance you also
have an embedded GPU in your multicore chip, which means you can make use
of a multicore processor, an embedded GPU, and a discrete GPU, which is a nice
exercise of heterogeneous programming!
Let’s see an example of a recent GPU: the Volta architecture V100 from NVIDIA
[2017]. Figure 2.4 shows the block diagram of the V100. The giga thread engine
at the top of the figure is what we called high-level scheduling in our generic
GPU of Figure 2.3. Its main purpose is to schedule blocks to SMs. A block, in
NVIDIA parlance, is a group of threads, doing the same operations on different data,
assigned to the same SM, so that they can share data more easily and synchronize.
Interface to host
High-level scheduling
GTC
SM SM
TPC TPC GTC
Memory controllers
Memory controllers
SM SM
GTC GTC
NYLINK interconnect
Figure 2.4 NVIDIA V100 GPU block diagram. (Based on NVIDIA, 2017. NVIDIA Tesla v100 GPU
architecture)
20 Chapter 2 Different Players: Heterogeneity in Computing
SM
Instruction cache
L1 instruction code
Warp scheduler
Dispatch unit
Tensor cores
FP64
FP32
Warp execution Warp execution
INT
L1 data cache/shared memory LD/ST units SFU
Texture
Figure 2.5 NVIDIA V100 GPU streaming multiprocessor. (Based on NVIDIA, 2017. NVIDIA Tesla v100
GPU architecture)
There is an L2 cache shared by all, and it is the last-level cache (LLC) before going
off-chip to the GPU global memory, not shown in the figure. NVIDIA packs several
SMs together in what are called GPU processing clusters (GPCs). In Volta there are
six GPCs; each one has 14 SMs. You can think of a GPC as a small full-fledged
GPU, with its SMs, raster engines, etc. The main players, who actually do the
computations, are the SMs.
Figure 2.5 shows the internal configuration of a single SM. Each SM is equipped
with an L1 data cache and a shared memory. The main difference is that the cache
is totally transparent to the programmer. The shared memory is controllable by the
programmer and can be used to share data among the block of threads assigned
to that SM. This is why a block of threads assigned to the same SM can share data
faster. There is also an L1 instruction cache and an L0 instruction cache for each
warp scheduler. Warp? What is a warp?
Warp is a hardware concept transparent from the programmer, in theory! But
in reality, when you write a program for such GPUs, you need to know about this
concept to write a more efficient code. This is very similar to cache memory. People
2.2 GPUs 21
can write programs all their lives without knowing about the existence of caches.
But if they know about them, they can write more efficient, cache-friendly code
and therefore get better performance. You cannot always rely on the compiler
to do the job for you because there is only so much that the compiler can do.
Compilers will tend to be conservative. Moreover, the compiler will not pick the
algorithm for you or change it. When it comes to parallel programming, compilers
are not yet very mature, a problem that is even more noticeable with parallel
programming for heterogeneous systems. Now back to the concept of warps: What
is it? We said earlier that the different execution units in a GPU share the same
front end, since it is single-thread–multiple data. This means that instructions are
fetched/decoded, then all the execution units execute them, then more instructions
are fetched/decoded, then the execution units execute them, etc. As you can see,
the execution units are executing the instructions in lock-step fashion. No execution
unit can proceed to the next instruction before all the execution units, connected
to the same front end, finish this instruction. This is a price that has to be paid to
amortize the cost of the front end over a large number of execution units. A warp
is the set of threads that is executing the instructions in lock-step fashion. How
many threads in a warp? Remember, it is a hardware concept, so if you say that
the number of threads equals the number of threads in the block assigned to the
SM, you would not be correct. The number of threads in a block is usually larger
than the number of the execution units in an SM. Shall we make the number of
threads in a warp equal the number of the execution units in an SM then? This
is also not a very good decision, but the number, and type, of execution units
changes from generation to generation, and we need to keep the warp size fixed
to make compilation, scheduling, and execution easier. NVIDIA fixed the warp
size to 32 threads. That number has been fixed across generations of GPUs. So a
block of threads assigned to SMs is divided into a group of warps, and each warp
is scheduled for execution. We can see from Figure 2.5 that each SM has four warp
schedulers, which allows four warps to execute simultaneously. We said earlier that
a programmer will benefit a lot from knowing about the concept of warps. Why?
First, you can decide on the size of your blocks to be divisible by 32, hence making
the best use of the available hardware. Second, if you know about warps, you will be
very careful when writing a code with if-else conditions. As we said, threads in the
same warp execute in lockstep fashion. What if there is an if-else and some threads
have a true condition and others have false? In that case some threads execute
the if part while the others wait. Then the other threads of the warp execute the
else part while the first group waits. This means there is some kind of serialization
22 Chapter 2 Different Players: Heterogeneity in Computing
here, reducing the parallelism and negatively affecting the performance. This issue
is called thread or branch diversion [Vaidya et al. 2013], and people who write in
CUDA, for instance, and are aware of the concept of warps are quite familiar with
it. There is no magical solution for branch diversion. But there is a lot of work in the
literature, both hardware solutions and software solutions, to reduce the negative
effect of it [Han and Abdelrahman 2011]. A programming tip is to try to make all
the threads in a warp have the same condition result (true or false).
After the warps have been scheduled for execution, the job of the execution units
starts. As we see from Figure 2.5, there are several types of execution units in Volta:
integer units, single-precision floating point units, double-precision floating point
units, and tensor units. The tensor units are included in recent GPUs to accelerate
operations used in aritifical intelligence applications, especially deep learning.
There are also some special function units (SFUs) for specialized functions like
sin(), cos(), etc.
All these SMs, with all their execution units, need to be fed with data on a contin-
uous basis, and this is the job of the GPU global memory. This global memory has
been fabricated, till now, using DRAM technology and optimized for bandwidth,
unlike the main system memory that is optimized for latency. The global memory of
the discrete GPUs in general suffers from two problems. The first is its low capacity
compared to the system memory. If we look at GPU global memories, we see single-
digit GB capacity until very recently. This small size increases the communication
between the GPU memory and the system memory, which is the second problem of
GPU global memory: communication overhead. Volta GPU uses a 3D stacked mem-
ory called high-bandwidth memory. Its size is 16 GB and it delivers 900 MB/s peak
bandwidth. These two numbers from a state-of-the-art GPU illustrate the issues of
capacity and communication.
From the above description, whenever you decide to outsource part(s) of your
application to the GPU, you need to consider several things:
If the answer is yes for all the above, then you will benefit from using GPUs if you
can optimize your code to reduce the effect of branch divergence, make the best
use of the available hardware, etc.
2.3 FPGA 23
FPGA
2.3 Hardware and software are logically equivalent. What you can do with hardware you
can do with software. The hardware implementation is much faster but not flexible
and can be expensive in terms of price. Software implementation is very flexible but
is slower because you have to execute it on general-purpose hardware that fetches,
decodes, executes, etc. Can we have the flexibility of the software solutions with
the performance of the hardware solutions but with less cost than an application-
specific integrated circuit (ASIC)? FPGA comes very close to that [Hemsoth and Mor-
gan 2017]. In very simple terms, FPGA is a hardware circuit that can be reconfigured
by software. It can be updated even after it is deployed. That is, it can be configured
while it is in the field, hence the first part of its name, field-programmable gate
array. You can think of an FPGA as a group of logic gates (gate arrays) whose in-
terconnection can be programmed, resulting in implementing different functions
that can be as complicated as a soft processor. FPGAs are programmed using a
hardware description language (HDL) such as Verilog or VHDL. Besides reconfig-
urability, FPGAs can scale by connecting several FPGA boards together, and they
can efficiently handle a vast amount of streaming data.
So if your program has some parts that do not have data parallelism but are used
frequently, then FPGAs can be a good option. One example of this is the Microsoft
Catapult project [Caulfield et al. 2016] in which all Microsoft datacenters are FPGA
equipped.
If you know the functions that you will use very frequently during execution,
you can configure the FPGA before starting the execution. If you don’t know these
functions, of if these functions change during the lifetime of your application, then
a profiling is needed, followed by reconfiguration, as shown in Figure 2.6. There is
an overhead, of course, for profiling and for dynamically reconfiguring the FPGA
during execution. But the performance boost obtained will overcome this overhead.
Profile
Execute
Configure
There are many big players in the industry for designing FPGAs. Xilinx is one of
them.1 Intel, after it acquired Altera, is another one.2
Automata Processors
2.4 We saw that GPUs are good for data-parallel applications. FPGAs are good for
streaming data and have the strength of flexibility in regard to hardware-
implemented functions. There is another range of applications that have broad
applicability: applications that depend on pattern-based algorithms. We can see
these types of applications (pattern matching, pattern recognition, and so on) in
many domains like data mining, bioinformatics, etc. [Wang et al. 2016]. Many of
these domains depend on what we call inexact matching, which is computationally
intensive and in which accuracy is often sacrificed for the sake of tractability. A few
years ago, around 2012–2013, Micron Technology proposed a hardware chip that
could natively implement nondeterministic finite automata (NFA), which is a math-
ematical tool for pattern matching and has less states than a deterministic finite
automata (DFA) and hence can be efficiently implemented in hardware. Micron
now has stopped development work for automata processing, but there are other
researchers in academia (Center for Automata Processing at University of Virginia)
and industry (Natural Intelligence Semiconductor Inc.) working on it.
NFA can have several active states at the same time. In real-life applications,
active states can number in the hundreds, or even thousands. This means using a
traditional multicore processor, with at most tens of cores, is not the best route to
take. Using GPUs may be better but will suffer from a severe memory bandwidth
bottleneck because each active state needs to be processed in a different memory
location; states are usually presented as state-tables with every state containing in-
formation about the next state, resulting in a very cache-unfriendly access pattern.
To have only one active state, we need to use DFA, with a booming number of states
in comparison to NFA. This is where an automata processor (AP) comes in handy.
Simply speaking, AP is a native implementation of NFA.
AP initial design depends on the inherent parallelism in memory. Memory can
be seen as a matrix of rows and columns accessed with row addresses and column
addresses. AP uses an 8-bit symbol as input to a row decoder. The decoder enables
one out of 256 rows. Each column is represented by a state transition element
(STE). The initial design has 49,152 columns (i.e, states). A subset of these states is
1. [Link]
2. [Link]
2.5 Neuromorphic Chips 25
8-bit
symbol 8×256
decoder …
Routing matrix
active at any point of time. When a row is selected, the active states pass through
a logic that can then activate another set of states using a reconfigurable routing
matrix, as shown in Figure 2.7. To add versatility, AP is also equipped with logic
elements that can implement AND, OR, NOT, NAND, or NOR gates. This helps
with implementing combinational circuits of arbitrary complexity. Also, there are
counter elements that can be configured to send an activation signal after the count
reaches a threshold. An AP board has a set of AP chips.
Using the AP is very similar to any accelerator. The CPU configures the AP,
offloads the data and program, then retrieves the result. There is a software de-
velopment kit (SDK) for programming the AP with binding to languages like C
and Python. The programming is done using a markup language called Automata
Network Markup Language (ANML). ANML is used to describe the network. The
binding to other languages, through the SDK, helps with describing automata net-
works, creating input streams, parsing output, and managing computational tasks.
Neuromorphic Chips
2.5 Everybody agrees that the brain is the most sophisticated, efficient, and powerful
information processor in the universe, at least the part of the universe known to
us. This pushes a lot of researchers to learn from Mother Nature, and we start
to hear statements like brain-inspired machines, cognitive computing [Modha et al.
2011], etc. There are three types of relationships between the brain and computers.
26 Chapter 2 Different Players: Heterogeneity in Computing
Other Accelerators
2.6 Besides the chips that we have discussed in the previous sections, there are some
special chips that are used, or can be used, in heterogeneous systems. Simply
speaking, you can use any application-specific computing unit in a heterogeneous
system setup; for example:
Digital signal processors (DSPs). Processing analog signals, such as audio and
video, is a must in today’s environment. The main characteristic of such
workloads is that they apply several mathematical equations on a stream of
signals that are sampled. As with any specialized circuits, DSPs are more
power efficient than general-purpose processors for these types of applica-
tions. This is why we see them a lot in many portable devices (smartphones,
tablets, etc.). However, they can also be used in high-performance computing
for some types of scientific applications when needed.
Quantum computing (QC). QC has made very big steps forward, leading to ad-
vances that have gotten them close to production. Quantum computers do
2.6 Other Accelerators 27
not depend on the traditional binary bits we use in our digital transistor-
based computers. QC depends on quantum bits (qubits). Each qubit can be
either zero, one, or something in between. Qubits can also exist in superpo-
sition and have two values at the same time. Quantum mechanics is mind
boggling in itself when we consider concepts like entanglement and super-
position! When we use these concepts to build a computing platform, things
will be . . . well . . . mind boggling! A few years ago, quantum computing
was considered more or less science fiction or a research endeavor beyond
our current technology and too risky to pursue. Nowadays we can see many
implementations of quantum computers. There are many big players invest-
ing in QC, including but not limited to Microsoft,3 IBM,4 Google,5 and Intel.6
The reason for this enthusiasm is that QC can be much faster than traditional
computers in some types of applications (e.g., factorization), once they reach
supremacy level (believed to be about 50 qubits).
Tensor processing units (TPUs). The TPU is Google’s chip designed specifically
to support AI applications, more specifically to accelerate neural networks
used in machine learning [Jouppi et al. 2017]. The chip was announced by
Google in 2016. The main idea of a TPU is to be efficient in the computation
operation frequently used in neural networks (NNs). If we look closely at how
a traditional NN works, it depends on many neurons, each one summing up
the product of a signal from other neurons with the weight of the connec-
tion between that neighboring neuron and the current one. For example, if
a neuron is connected to 10 other neurons, it has to do 10 multiplications,
multiplying the signal from the neuron by the weight connecting that neuron
to the one in question, then adding the results. This series of multiplications
and additions has a nickname: matrix multiplication. Simply speaking, we
need a piece of hardware that can do matrix multiplication efficiently. You
may say that GPUs are already doing this efficiently. This is true, but TPUs are
doing an extra step of simplifications. They are using quantization to trans-
form floating points (32 bits for single-precision) to 8-bit integers. The main
philosophy here is that you do not really need the high precision of floating
points to implement activation of neural networks. TPUs are implemented,
3. [Link]
4. [Link]
5. [Link]
6. [Link]
28 Chapter 2 Different Players: Heterogeneity in Computing
TPU and QC may not be available to customers to buy, at least for now. However,
customers and other companies can have access to them through the cloud. What
we can see here is that the computing devices in a heterogeneous system may
not use the same instruction set. This requires a radical change in how we design
our development environment and ecosystem of libraries, compilers, assemblers,
linkers, etc.
When building a heterogeneous system, there are several decisions that have to
be made. First, what are the types of processing elements needed in the system?
Second, how many of each type is needed? Third, how is the memory hierarchy
managed? There is a compilation of several accelerator chips that may exist in some
heterogeneous systems besides the famous one.7 The problem is more complicated
when we talk about big machines (supercomputers, datacenters, etc.). In the next
section we look at these types of decisions but for single chips.
Mix-and-Match
2.7 There are many chips in the market that include heterogeneous computing ele-
ments, besides of course the heterogeneity in memory access time, etc., that we
have discussed earlier in this book. In this section we look at some examples of
these chips.
7. [Link]
2.7 Mix-and-Match 29
Figure 2.8 Intel architecture for the i7-6700K. (© Intel Corporation, Used with Permission)
The Intel GT2 is the generation 9.5 of Intel integrated graphics. The GT2 has
one slice and 24 execution units. Let’s take a quick look at the integrated graphics
to see the difference between it and a discrete GPU similar to what we saw ear-
lier in this book. One part of this embedded graphics unit is what is called the
unslice. The unslice is the piece responsible for fixed-function media and geome-
try functions. It has its own power management and clock domain independent
from other cores. This gives it the ability to turn off the slice or run it with higher
performance on demand, saving power and optimizing performance. If we leave
the fixed-function pipelines and move to a more general-purpose execution (yet GPU
friendly), we find the slice. The slice is a cluster of subslices. Each sublice is a group
of execution units. A global thread dispatch unit ensures load balancing among
subslices. There is a difference between the execution units here and the CUDA
cores, or SP, in NVIDIA discrete GPUs, for example. In the case of NVIDIA GPUs,
each SP is an execution unit without a front end. So a group of threads, called a warp
in NVIDIA parlance, executes in lockstep, hence SIMD (or STMD). In our case here,
each execution unit is an independent unit. And each execution unit is sophisti-
cated enough internally to be multithreaded, support seven threads, and execute
instructions in SIMD fashion thanks to its support of 128 SIMD 32-bit registers
per thread. By varying the number of execution units per subslice, the number of
subslices per slice, and the number of slices, a different type of scalability serving
different market segments can be built. Figure 2.9 shows the architecture of the
embedded graphics. The embedded GPU hosts a slice of the L3 cache. Hence the
GPU and the different cores in the chip share the L3 cache.
When it comes to memory hierarchy, each core has an IL1 (i.e., level 1 of
instruction cache), a cache size of 32 KB, is 8-way set-associative, and it has 64 sets.
30 Chapter 2 Different Players: Heterogeneity in Computing
Figure 2.9 Embedded Intel graphics with one slice. (© Intel Corporation, Used with Permission)
The block has a size of 64 bytes across the whole cache hierarchy. This cache is
shared by the two threads of the core. A DL1 cache (i.e., level 1 data cache) per
core is also 32 KB and 8-way set-associative, with 64 sets. It uses a writeback policy
to lower-level caches to reduce bandwidth requirements. The L2 cache is unified
and noninclusive. It is 256 KB and 4-way set-associative, and also uses a writeback
policy. L3 is the last-level cache. It is sliced, and each slice is up to 2 MB per core,
but this cache is shared across all cores. The cache uses a writeback policy too.
The performance cores have 256 KB private L2 caches while efficiency cores use
128 KB private L2 caches. The L3 cache is a shared 2 MB. In the memory controller,
there is a 3 MB system cache that acts as an L4 cache shared by the whole SoC chip.
But not all execution units share the memory hierarchy. There is an isolated island
in that chip: the secure processing unit (SPU). The SPU has its own execution unit
with its own random number generator and memory. It is important for the SPU to
be isolated from the other pieces in order to defend against attacks.
This SoC is a very good example of the heterogeneity in hardware. Several dif-
ferent applications use the different execution units here. The development of a
single application that leverages all the different heterogeneous execution units is
still in progress, except in some areas of high-performance computing.
GIC-400
Interupts Interupts
Figure 2.10 Arm [Link]. (Reproduced with permission from Arm Limited. Copyright © Arm Limited
(or its affiliates))
first one is called CPU migration. In this model, each big processor is paired with
another little processor. This requires that the number of cores of each type must
be equal. If we have four big cores and four little cores, the OS will see four cores.
The task is assigned to one of the cores and moved between the big and the little
based on its requirement using a variation of DVFS. The other model is called global
task scheduling, which is the usual scheduling we expect. The OS sees all the cores
and is aware of their different capabilities. Using heuristics, tasks are assigned to
the most suitable cores. This second model does not require the processor to have
the same number of cores of each type.
In Conclusion
2.8 In this chapter we looked at heterogeneity in computing. We had a tour of the dif-
ferent computing nodes and we saw some real-life examples. We saw heterogeneity
across chips (discrete GPU, traditional multicore, FPGA, etc.). We also saw hetero-
geneity within the same chip. However, there is more to heterogeneity in hardware
design. We need to look at the architecture from a different angle, which is the topic
of the next chapter.
3
3.1
Architecture:
Heterogeneity in Design
In the previous chapter we looked at the main pieces of the puzzle in a heteroge-
neous system: the computing nodes. In this chapter we put those pieces together
and see the result of their interaction. The computing nodes need to access the
memory, and storage, and need to be interconnected. So we look at the memory
system as well as the interconnect. We also take a detailed look at some examples
of supercomputers. Once we have a fully working system, we need to start worry-
ing about two other factors that affect performance: security and bandwidth, which
constitute the rest of this chapter.
Memory System
The memory system is actually a hierarchy, or many hierarchies for supercomput-
ers, datacenters, hyperscalers, etc. The lowest level of this hierarchy, farthest from
the processors, is the system memory. The higher levels are caches.
In the 1990s and the first decade of the twenty-first century, most of the research
related to cache memory [Smith 1982] proceeded in two parallel paths. The first
deals with caches for uniprocessor systems in an attempt to enhance one or more
of the cache aspects, such as access time, accuracy, and power consumption. The
second path deals with parallel architectures.
For uniprocessor systems, some work tries to capture the program behavior
and adapt the cache based on this program’s requirements [Calder et al. 1998,
Dhodapkar and Smith 2002, Lee and Kim 2002]. Also, several proposals have been
made in academia to enhance the memory system by dynamically varying one of
its parameters [Peir et al. 1998, Kin et al. 1997, Veidenbaum et al. 1999, Albonesi
2002, Calder et al. 1996, Abella and Gonzalez 2006]. Improving the hit rate has been
the main goal for a long time [Karkhanis and Smith 2002]; for instance, column-
associative caches [Agarwal and Pudar 1993], filter caches [Kin et al. 1997], and
34 Chapter 3 Architecture: Heterogeneity in Design
predictive sequential-associative caches [Calder et al. 1996] are all geared toward
increasing the hit rate by modifying the way an address is decoded and the sequence
followed to find a block, at the expense of simplicity. Peir et al. [1996] proposed a
path-balancing technique to help match the delays of caches and data paths. This
has the effect of decreasing the access time. Another important aspect of a cache
is its power consumption. Cache being one of the main power-hungry structures
on-chip, it triggered a lot of research both in academia and industry [Flautner et al.
2002, Flautner et al. 2002, Ghose and Kamble 1999, Inoue et al. 2002, Kamble and
Ghose 1997, Kaxiras et al. 2001, Kim et al. 2001, Kim et al. 2002, Kim et al. 2004a,
Albonesi 2002]. For example, in Veidenbaum et al. [1999], the authors proposed a
cache memory where cache line size is continuously adjusted by hardware based
on observed application access.
The second path taken in memory system research is the cache hierarchy for
multiprocessor systems. The research in that area consists mainly of coherence
protocol schemes [Archibald and Baer 1986, Agarwal et al. 1988, Eggers and Katz
1989, Tomasevic and Milutinovic 1993, Moshovos 2005]. That path is now targeting
multicore architectures [Hammond et al. 1997, Krishnan and Torrellas 1999, Cheng
et al. 2006, Ekman et al. 2002, Nayfeh 1998].
These two paths in memory system research proceed in parallel. But with the in-
troduction of multicore architectures (aka chip multiprocessors or CMPs), the two
paths started to converge [Beckmann and Wood 2004], and the main theme was
the high bandwidth available on-chip, as opposed to the limited, and more expen-
sive, off-chip bandwidth. However, wire delay represents a big challenge because it
does not scale with the transistor [Agarwal et al. 2004, Beckmann and Wood 2004].
This leads to the introduction of networks on-chip [Dally and Towles 2001, Benini
and DeMicheli 2002, T. T. Ye 2003], which uses the experience gained at design-
ing packet-switched networks to design on-chip networks that connect cores and
caches. With the increase in the number of on-chip cores and the fact that most of
the current designs still make use of the traditional design of a private L1 cache for
each core and a shared L2 cache among cores, the problem of sharing the L2 caches
among cores becomes important both in academia [Kim et al. 2004b, Qureshi and
Patt 2006] and in industry. The problem of cache sharing for multicore architectures
uses experience gained from cache partitioning in clustered architectures [Farkas
et al. 1997]. Another trend is to use small caches to act as an aggregate large cache
[Varadarajan et al. 2006, Chang and Sohi 2006]. These methods have the advan-
tage of saving power but do not solve the problem of application interference in
caches, where two applications have conflicting cache requirements, which hap-
pens very frequently in simultaneous multithreading architectures [Tullsen et al.
3.2 Interconnect 35
1995, Lo et al. 1997], multicore architectures [Sinharoy et al. 2005, Borkar et al.
2006, Ramanathan 2006, Broadcom Corporation 2006], and systems built as a hy-
brid of the two, like the Niagara (UltraSPARC T1) [Kongetira et al. 2005].
Another line of research that has been targeting single core and has migrated,
without many enhancements or changes at first, to multicore is cache replacement
policy [Jeong and Dubois 2003, Reineke et al. 2006, Guo and Solihin 2006, Megiddo
and Modha 2004, Qureshi et al. 2006, Al-Zoubi et al. 2004, Wong and Baer 2000].
Cache replacement policy decides which block to evict to make room for a new
incoming block.
Cache replacement starts to evolve with the widespread usage of multicore and
manycore processors [Pekhimenko et al. 2015, Ros et al. 2015, Guo et al. 2013].
However, cache hierarchy nowadays needs to deal with different types of scenarios:
heterogeneous multicore, like multicore with embedded GPUs, and many-core
chips like GPUs [Xie et al. 2015].
Another aspect of the memory hierarchy is the inclusion property. It goes with-
out saying that most hierarchies now do not support inclusion, except at the last-
level cache. The main reason for that is to make the best use of the chip area. With
inclusion, data is replicated in all the higher cache levels (“higher” means closer
to the processor). As the number of cores increases, the data replication increases,
especially when there is a lot of data sharing among cores. But if we do not have
an inclusion property at the last-level cache, which is almost one big shared cache
before going off-chip, then coherence will happen off-chip, which will result in very
high performance degradation.
Interconnect
3.2 We are in the big-data era. Machines are number-crunching huge amounts of data.
But in order to solve big and important problems, data have to be moved both in
the memory hierarchy and among cores/processors. This makes the interconnect
a vital part of overall performance in any system.
There are several aspects of interconnect in any system:
. The topology. What is the shape of interconnection among the different
pieces? A piece can be a processor, a computing node, a switch, etc.
. The granularity. What are we connecting (cores, chips, full cabinets)?
. The technology. What is the material we are using (fiber optics, copper)? A
single system can use different technologies at different granularity levels;
that is, using copper to connect cores, fiber optics to connect processors, and
so on.
36 Chapter 3 Architecture: Heterogeneity in Design
The technology, protocol, and switching affect the bandwidth sustained by the
interconnect and hence the overall performance of data movement. The granularity
is just the playground of the interconnect. For example, connecting cores inside
a chip [Kumar et al. 2005, Jerger et al. 2017] has different characteristics than
connecting several cabinets. Inside the chip the distances are much shorter and
the bandwidth is much higher, but space and power are more restrictive than off-
chip and among cabinets. In this section we take a look at the different aspects of
interconnect.
3.2.1 Topology
The topology is the shape of interconnection. In its general form, it is an undi-
rected graph of connected vertices. These vertices can be cores, processors, routers,
blades, cabinets, or anything we want to connect. Of course connecting all ver-
tices to each other (i.e., a fully connected graph) is very expensive in terms of area
and power consumption. Each connection consumes, and dissipates, power. At
the same time, we want each node, a vertex in the graph, to be able to communi-
cate with any other node. This is where topology comes in. It answers the question,
What are the best and most economical interconnections among the nodes? Nodes
exchange messages.
An interconnection network can be direct or indirect. Suppose we have N nodes
to be interconnected. For direct networks, the nodes that need to be interconnected
reside inside the network. Indirect networks have the nodes sitting outside the
network. Both cases are shown in Figure 3.1. In the case of an indirect interconnect
(Figure 3.1(a)), the interconnect itself is made of routers and switches, while in
direct interconnect (Figure 3.1(b)), the node itself contains the router or switch.
The topology can have a regular shape (mesh, ring, torus, etc.) or an irregular
one. How do we compare two different topologies? One of the important measure-
ments is the diameter. For a message to go from node A to node B, if the two nodes
are not directly connected, the message has to go through several hops (called the
routing distance). The diameter is the maximum number of hops between any two
nodes. The lower the diameter the better because then the message will travel less
distance. Figure 3.2(a) shows a bus interconnect. The longest routing distance is
two, which the distance needed to send a message from node A to node C. So the
diameter of this topology is two. If we move to the ring interconnect in Figure 3.2(b),
3.2 Interconnect 37
Inter-
connect
…
Nodes
(a) (b)
A B C A B C
(a) (b)
we can easily see that the diameter is now one, which is better. As you may have real-
ized, the diameter grows with the number of nodes. Only a fully connected topology
has a fixed diameter of one. A ring of n nodes has a diameter of floor (n/2).
Besides the ring and the bus, there are many different interconnect topologies.
Figure 3.3 shows some examples. As you can see from the figure, some topologies
are indirect interconnect (crossbar, tree, and omega) and some are direct intercon-
nect (hypercube, mesh, and torus). Of course there are systems with irregular topol-
ogy. The interconnect can be sophisticated to include contention control through
routing, in which case messages sent from source to destination may take different
routes depending on which links are heavily used.
That covers the topology. How about the technology used for the links?
3.2.2 Technology
The interconnect was designed mainly using copper from the old days until today.
Let’s take on-chip as an example. A link between two entities (core, cache, etc.) is
a group of parallel copper wires. The more parallel wires we put, the higher the
bandwidth we get because we can move more bits in parallel. But more parallel
38 Chapter 3 Architecture: Heterogeneity in Design
Switch
Switch
wires means more chip area. If we don’t have this needed area, then we can make
each copper wire thinner. Thinner wire increases its resistance and hence its tem-
perature and the power it dissipates. So we have a trade-off of bandwidth versus
power. This trade-off is not the only problem we face. Wire delay is a performance
bottleneck.
3.2 Interconnect 39
Si Si
Laser modulator Waveguide detector
Driver Amplifier
Logic Logic
Moving data around from core to shared cache or among different banks of
a cache affects performance in a negative way. The communication also affects
programming. Programmers now need to manage data locality (to reduce data
movement) and data communication (to move data in an efficient way). If there
is a broadcast channel and it has low latency, then the programmer’s task will be
easier. This is where optical interconnection comes into play.
Using photonics, we have several advantages. The power consumption is less
than copper. Contention is not a big problem because you can use different wave-
lengths. You can have arbitrary interconnection because two overlapping laser
beams will not interfere. In Kurian et al. [2010], for example, the authors use
nanophotonic technology to implement a fast, efficient global broadcast network
implemented as a ring connecting all the cores on-chip. This network can be used
together with a traditional point-to-point copper network. Optical interconnection
is getting more and more momentum and is already in use on-board and across
boards [Kobrinsky et. al. 2004, Kodi and Louri 2007, Xue et al. 2010, Nitta et al.
2013]. Figure 3.4 shows a typical optical interconnect that can be used on-chip. The
main processor is of course electrical (the logic part). There must be a modulator to
transform electrical pulses into a laser beam by modulating the laser beam using a
driver. The modulated laser beam is directed, using a waveguide, to its destination
where a detector returns it to its electrical form to be consumed by the logic. The
main drawback for optical interconnection is that it is still more expensive than
traditional copper interconnection.
As we can see, there is heterogeneity here too, because we can have optical
and copper interconnect in the same system. Each network can have different
topology too.
Now that we can have different topologies and different technologies, how are
data sent across the interconnect links? There must be some kind of protocols.
40 Chapter 3 Architecture: Heterogeneity in Design
1.0 12.8
2.0 22.4
3.0 41.6
3.1 51.2
3.2.3 Protocol
In computer systems, data move from one entity to another. These data take the
form of streams of bits, messages, packets, etc. (depending on how you look at it).
How do these data move? There must be a protocol between sending and receiving
partners, whether these partners are CPUs, cache banks, or even full cabinets.
There have been many protocols in the last several decades. In this section we look
at three of the most widely used protocols: HyperTransport, PCI Express (PCIe),
and NVLink.
1. [Link]
3.2 Interconnect 41
1.x 0.25
2.x 0.5
3.x 1
4.0 2
5.0 4
3.2.4 Examples
We have seen examples of interconnect inside the chip and on-board. What about
higher granularity? In this section we take a quick look at some interconnect at
high granularity in supercomputers. The trend now is to use switches with more
skinny ports (i.e., ports with fewer parallel bits) than fewer fatter ports. The main
reason for that is that signaling rates—the electrical signals used to send the data—
have gone up relative to the sizes of the network packets. This means the penalty
for sending large packets through skinny ports (i.e., serialization) has gone down.
Another trend is to have switches with more ports. These are called high-radix
switches. The implication of this is that we can have network topology with lower
diameter. Now, let’s see some examples.
Intergroup interconnection
Intragroup connection
Examples of Supercomputers
3.3 In the previous chapter we looked at heterogeneity inside the chip. We saw exam-
ples of chips with heterogeneous computing nodes. This chapter goes a step further
and we look at the whole system when we connect many computing nodes. As we
did in the previous chapter, we look at examples of heterogeneous machines at the
system level. Because we are discussing high-performance computers, the mem-
ory, at that high level, is distributed. The design philosophy for most of them is the
same: few processors in a node (or card), several nodes in a blade, several blades
in a cabinet, and then multiple cabinets. At each level there are interconnections.
Processors in a node share memory. Once we are at the blade level, we have dis-
tributed memory. The naming (e.g., rack instead of blade) and number of levels
may differ among companies, but this philosophy of design allows scalability and
at different granularities when needed (adding more cards, blades, etc.). Figure 3.7
summarizes these concepts.
Processor
Computing
mode Increase in parallelism granularity
Blade
Toward distributed memory
Cabinet
form a chassis. A cabinet can have up to three of these chassis and a claimed
peak performance of 1 PFLOPS (peta floating point operations per second). By
introducing Arm and Intel processors, CRAY made their machine very flexible to
accommodate different host processors.
Each computing node has a one-to-one matching of a multicore (whether from
Intel or Arm) and an accelerator. This makes for a balanced compute to accelera-
tor ratio. This balance, coupled with a choice of CPU (Intel vs. Arm), allows Cray to
cater to different heterogeneous workloads. Even though the memory is distributed
across cards, the address space of general-purpose processors (i.e., the nonacceler-
ators) is extended to access all the physical memory in the system. This makes the
machine programmable with languages like MPI and also with schemes of parti-
tioned global address spaces (PGAS) like Unified Parallel C (UPC) and Fortran with
Coarrays. This capability of allowing processors to access the physical address of
the system is enabled through the Aries interconnect.
The Aries interconnect by Cray is a chip (router), a topology, an interconnect
technology, and a protocol. There is an Aries chip in each computing node. In
that protocol is a network address that consists of three parts: (1) an 18-bit node
identifier, (2) a 12-bit memory domain handle associated with a memory segment
registered at the remote node, and (3) a 40-bit offset into this segment. This makes
a 70-bit address that allows a processor in a node to access all physical addresses in
the system. At the technology front, the Aries interconnect uses optical connection,
and for a good reason. The downside with small-diameter topologies is that the
links (i.e., cables) are a bit long, for example, 15 meters. With that length, using
copper interconnect becomes very slow. This is why Cray uses optical interconnect
here. However, the optical interconnect is not used everywhere in that system but
is used in tandem with copper wire, as we will see shortly. The topology used
is called dragonfly. This topology has a very small diameter. There is an all-to-
all link inside the chassis. This is done with copper wires because the length is
short. Then there is another all-to-all link among six chassis. We saw earlier that
every three chassis form a cabinet. So we have an all-to-all among the chassis
forming two cabinets. At that level of chassis, the connections are done in copper
too, for two reasons. The first is to reduce the cost of the system because optical
interconnect is very expensive. The second is that the wires are short so copper
wires will not hurt performance. Among groups of two cabinets we have optical
cables and connections in an all-to-all topology because here the cable’s length is
high. That is, the connection between any two points does not need to go through
many hops—actually, only two hops if the two nodes are in the same group of two
3.3 Examples of Supercomputers 45
cabinets, based on the description above—and it can get between any two nodes
in the system with at most five hops.
Node
PCIe
Gemini
interconnect
Node
form a blade. A cabinet has 24 blades. The system consists of 200 cabinets, making
the total number of nodes 18,688. Every two nodes are connected to Cray’s Gemini
interconnect. This interconnect forms a 3D torus, as shown in Figure 3.8.
2. [Link] cybersec/[Link].
3.4 Security Challenges Facing Heterogeneous Computing 47
trial process control systems.3 This previously classified incident was the most
significant breach of US military computers ever and marked a turning point in
US cyber defense strategy. It was also a turning point in computer system research
and industry to make security as important as performance and cost, not just an
afterthought. The sensitive information and critical applications handled by com-
puters makes building a trusted computer system a necessity and not a luxury. This
is easier said than done due to the proliferation of methods by which a computer
system can be attacked. Trusted computing is a paradigm that has emerged to ad-
dress the security concerns in general-purpose computing systems [TCG 2008].
There are software-oriented and hardware-oriented ways to attack a system, and
also software-oriented and hardware-oriented ways to add security to computer sys-
tems and build trusted platforms. To design a trusted computing system, security
has to be systematically incorporated into the various stages during the design of
such systems: including system architecture, hardware implementation, and soft-
ware implementation [McGinn-Combs 2007].
Securing heterogeneous systems is even more challenging due to the different
ways programs are executing in different accelerators and the need to move a lot of
data around. Prior to execution, a program resides on the disk. When this program
starts execution, it is copied to the system memory to be fetched and executed by
the processor. This program can be tampered with while it is on the disk [Nijim
et al. 2006, Hughes and Murray 2005, Ruan et al. 2009, Abualsamid 1998], on the
bus from disk to memory or from memory to the processor [Su et al. 2009, Elbaz
et al. 2005, Coburn et al. 2005], while in the memory [Di Crescenzo 2005, Yan et al.
2006, Vaslin et al. 2009], or when moving from node to node (or blade to blade or
cabinet to cabinet in large systems). It can also be modified by malicious software
[Lin 2008]. Simply using encryption and decryption schemes is not enough to
ensure that programs will be secure, because side channel attacks can be extremely
dangerous [Tiri 2007, Standaert et al. 2009, Wang et al. 2009].
Computer systems can be compromised using software attacks and/or hardware
attacks [Waksman and Sethumadhavan 2010, Karri et al. 2010]. The most obvious
threat model is time of check/time of use (TOCTOU) [Bratus et al. 2008], where there
is an interval between the time the software has been checked for integrity and
the time the software is used. During that interval, the software can be tampered
with and illegitimately modified. Other types of attacks involve taking advantage of
program vulnerabilities. Most programs usually contain vulnerabilities. Over the
3. [Link]
48 Chapter 3 Architecture: Heterogeneity in Design
years attackers have devised various ways to exploit vulnerabilities in programs and
transfer control to their attack code. Some of the most common types of attacks are
as follows:
Buffer overflow attacks [Cowan et al. 1998] arise due to lack of bounds checking
on the size of the input array being stored in the buffer. Attackers make
use of an unchecked buffer in a program and overwrite it with their own
data, thus allowing unintended control transfer to their own attack code.
This is also known as the “stack smashing attack” and is one of the highest
reported vulnerabilities to CERT.4 Several hardware-based solutions, like a
secure return address stack [Lee et al. 2003], using a nonexecutable stack
[Cowan et al. 1998], etc. have been proposed to thwart these attacks. Secure
languages such as Cyclone, which perform array bounds checking, have
also been proposed. As more and more applications are developed, buffer
overflow attacks still form a major share of the vulnerabilities reported.
Return-into-libc [Nergal 2001] exploits buffer overflow to overwrite the return
address with the address of a C library function such as system(). Instead
of returning into code located in the stack, control is transferred to a memory
area occupied by a dynamic library. Since it uses existing code rather than the
attacker’s shell code, a return-into-libc attack is very difficult to detect.
Code injection attacks [Kc et al. 2003] inject malicious code into a running
application and then cause the injected code to be executed. The execution
of the injected code allows the attacker to gain the privileges of the executing
program. Instruction set randomization has been proposed to counter code
injection attacks.
Replay attacks [Wheeler 2008] involve fraudulent repetition of valid data trans-
mission by an attacker. If the destination does not detect the duplicate copies
of the data being sent by the attacker, the attack is successful.
As we can see, for each type of attack there are many attempts to defend against
it. This means if we want to protect our program from, say, 10 different attacks,
we need to implement 10 different defenses! A unified security system is needed to
ensure program integrity against most, if not all, different threats.
On the hardware side, attacks can result from a malicious piece of hardware
(called a hardware Trojan) inserted into the computer system [Waksman and Sethu-
madhavan 2010, Karri et al. 2010], or through an external attacker who takes phys-
ical possession of the system (like a laptop or desktop) [Tereshkin 2010]. Collec-
tively, hardware threats can be grouped into three categories. The first category is
the side channel attack [Karri et al. 2001, Standaert et al. 2009, Wang et al. 2009,
Tiri 2007]. This type of attack compromises the system by capturing information
about program execution, such as electromagnetic signals emitted due to compu-
tations [Fiori and Musolino 2001, Gandolfi et al. 2001], or by exploiting weaknesses
in caches and branch predictors [Aciiçmez 2007, Kong et al. 2008, Aciiçmez et al.
2010]. The second category requires physical access to the system. By gaining ac-
cess to the system, the attacker can launch attacks against encrypted disks, or make
the system boot from a compromised OS, or compromise an external device such
as the Ethernet card.5 Finally, the third category of hardware threats consists of
hardware Trojans. A hardware Trojan is a malicious circuitry, or malicious modifi-
cation of circuitry, inserted into the computer system [Jin et al. 2009, Tehranipoor
and Koushanfar 2010, Clark et al. 2009, Wang et al. 2008, Potkonjak et al. 2009].
This circuit can be inserted during any phase of the chip design and fabrication:
specification phase, design phase, validation phase, physical design phase, fabri-
cation phase, or deployment. The Trojan is typically dormant and is triggered only
after some event, such as the execution of a specific instruction sequence, or ex-
ternal signals, etc. When triggered, the Trojan can disable some hardware fences,
cause denial of service, leak information, or cause the system to malfunction. How
do we defend against all that?
Integrity checking to detect control flow anomalies at runtime involves comput-
ing a hash value of an instruction or a basic block at compile time and comparing
it with the hash value calculated at runtime [Schuette and Shen 1987, Gelbart et al.
2005, Gassend et al. 2003]. At a microarchitecture level, the instruction can be
modified to secure the system against malicious code injection [Fiskiran and Lee
2004]. This technique of course has some performance effect because it controls
the processor pipeline and does not commit the instructions in the basic block
until integrity has been checked.
For multicore processors, Orthrus [Huang et al. 2010] employs replication of
the program on multiple cores to enhance security. At runtime, these replicas ex-
ecute on different cores with the same input, and their outputs are checked for
Bandwidth
3.5 We are facing a bandwidth wall both off-chip and at higher levels (among blades,
cabinets, etc.). With a large number of cores per chip and a huge number of
processors and computing nodes in high-performance computing machines, we
expect potential performance gains but also severe performance bottlenecks: the
expected increase of bandwidth requirements.
Software applications are becoming much more sophisticated and are charac-
terized by large memory footprints. This means an increase in the number of cache
memory misses and more accesses to off-chip memory, which, in turn, puts a lot
of pressure on memory ports, memory buses, and socket pins, and severely affects
overall performance. As we go from on-chip to off-chip to on-board to blades and
all the way to cabinets, the bandwidth required is higher but the bandwidth given
by technology is lower. For instance, on-chip bandwidth is much higher than off-
chip. Solving the bandwidth problem needs different solutions at different levels.
Dealing with on-chip is different than off-chip, which is different than among com-
puting nodes, and so on, because the bandwidth requirement is different and the
technology used is different. Let’s see how we can approach the bandwidth wall at
one level: managing off-chip bandwidth. We did some experiments and we present
here some insights.
6. We have chosen the benchmarks with the largest memory footprint from the SPLASH-2 suite.
The input set of the benchmarks does not vary with the number of cores.
52 Chapter 3 Architecture: Heterogeneity in Design
0.8
0.6
0.4
0.2
0.0
Barnes Cholesky Fft Fmm Radiosity Radix Raytrace
Figure 3.9 Ratio of dirty LRU blocks to the total number of Accesses in the LLC.
non-LRU blocks are important blocks and that getting rid of them hurts perfor-
mance. This is true for an L1 cache. But is it true for an LLC? Table 3.3 provides
evidence that violating the LRU strategy, of victimizing the LRU block, at the LLC
does not always lead to severe performance loss. The table shows the total execution
time, in terms of number of cycles, of the whole execution of SPLASH-2 benchmarks
for several non-LRU schemes normalized to the LRU scheme. LRU-M means we al-
ways victimize the Mth block from the LRU stack. For an 8-way set-associative cache,
which is the one we use in this experiment, LRU-7 is the most-recently-used (MRU)
block. As we see from the table, even when we always victimize the MRU block,
the performance loss can be as little as 6%. Of course this is application depen-
dent and hardware dependent. But it is an insight that may help to direct building
future systems. This gives us an idea about what to expect if we deviate from tradi-
tional LRU. This means that the LRU stack in the LLC is no longer an LRU stack, and
does not represent recency behavior for the running multithreaded application. There
are two reasons for such a surprising behavior. First, the LLC contains blocks that
have exhausted their temporal locality at L1 (or upper-level caches in case of more
than two levels). Therefore, blocks at the LLC have less temporal locality than L1,
which makes the concept of recency at the LRU stack of the LLC less meaningful.
The second reason is that the LRU stack at the LLC is the result of interference of
LRU stacks of L1 caches (each core has its own L1 caches), so if the interference
is destructive, it no longer represents recency information for any single thread,
nor for the whole program. Different threads have their own important blocks, and
hence their own LRU stack. When these threads share a cache, the LRU stack of
that cache is not really very useful. Thus the LRU stack is a “mix” of LRU stacks of
L1 caches, which may not yield a recency order of any running thread. Sometimes
3.5 Bandwidth 53
Table 3.3 Normalized values of the number of cycles for victimizing a non-LRU block
LRU 1 1 1 1 1 1 1
LRU-1 1 1.03 1.01 1 1.01 1 1.02
LRU-2 1 1.06 1 1 1.01 1.04 1.06
LRU-3 1 1.15 1.01 1 1.03 1.07 1.12
LRU-4 1.02 1.23 1.01 1.03 1.03 1.09 1.21
LRU-5 1.04 1.37 1.02 1.06 1.03 1.12 1.36
LRU-6 1.13 1.55 1.04 1.12 1.05 1.18 1.63
LRU-7 1.46 1.69 1.06 1.21 1.09 1.25 2.05
80 Barnes 70 Cholesky
Percentage of block hits for LRU stack
1 core 60 1 core
4 cores 4 cores
60 8 cores 8 cores
50
40
40
30
20
20
10
0 0
MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU
50 FFT FMM
Percentage of block hits for LRU stack
60
1 core 1 core
40 4 cores 4 cores
8 cores 50 8 cores
30 40
30
20
20
10
10
0 0
MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU
Radiosity 70 Raytrace
Percentage of block hits for LRU stack
80 1 core 1 core
60
4 cores 4 cores
8 cores 8 cores
50
60
40
40 30
20
20
10
0 0
MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU MRU LRU-6 LRU-5 LRU-4 LRU-3 LRU-2 LRU-1 LRU
Cache blocks for 8-way set associativity Cache blocks for 8-way set associativity
Figure 3.10 LRU stack hits for one, four, and eight cores for 8-way set associativity.
3.5 Bandwidth 55
the LRU blocks and n − 1 other blocks on top of it in the LRU stack. So for an 8-way
set-associative cache, the maximum M is 8. M is determined at design time and is
not changed afterward.
The main advantage of that static scheme is its simplicity. Once we decide on
the design parameter, M in this case, the implementation is fixed. However, deter-
mining this design parameter is usually not easy because it depends on runtime
information that is not available at design time, so it relies on designer experi-
ence and a lot of simulations. Determining M is very challenging and is application
dependent. We need a scheme where M changes dynamically, depending on the
application behavior. We can try some schemes for dynamically changing M, such
as writeback-sensitive and LRU-sensitive. Each of these schemes can either be global
(same M for the whole cache) or local (M per set). In the writeback-sensitive scheme,
when M is high, we have a higher chance of reducing traffic but also a higher chance
of affecting performance. This means that M is governed by how much traffic (write-
backs) is generated, and this traffic is generated only in the case of a cache miss at
the LLC. So in the writeback-sensitive scheme, M is incremented when there is a
writeback, to reduce off-chip traffic, and is decremented when there is a cache miss
with no writeback, to reduce performance loss. The new value of M will be used the
next time a victim is to be chosen. In the LRU-sensitive scheme, we check the LRU
block every time the cache is accessed to see whether it is dirty. If it is, then we have
potential traffic if we victimize the LRU block, so we increment M to reduce poten-
tial traffic. We decrement M whenever there is a cache miss, at the LLC, to reduce
potential performance loss. The technique works on the premise that if the LRU is
made dirty, then there are more chances of a writeback, so we increase the value of
M to check more blocks for a clean one. If, however, the application is LRU friendly,
it will result in misses and each miss would reduce the value of M, bringing it back
to 1 (LRU). Thus this scheme tries to reduce potential bandwidth. It assumes im-
plicitly that a dirty LRU will generate bandwidth. This assumption is not correct all
the time. This is why the former proposed scheme (writeback-sensitive) turns out
to be better. We will show results of these experiments shortly. But for now let’s
continue our brainstorming for reducing off-chip bandwidth by manipulating LLC
replacement policy.
In order to reduce the complexity of the hardware, compiler support can be
used. Compilers are essentially free in terms of hardware requirements. We will
use compiler-based profiling. The compiler will use one core to do only profiling.
During profiling the compiler builds a write frequency vector (WFV). WFV is just
a histogram representation of the number of writebacks at several time intervals.
56 Chapter 3 Architecture: Heterogeneity in Design
During profiling the compiler keeps a count of the number of writebacks at each in-
terval of length X. Then, based on that number, an entry of the WFV is incremented
by one. In our experiments we made the compiler check the number of writebacks
every 10,000 cycles. We have a WFV of 32 elements, where each element represents
an interval of 10. For example, if the number of writebacks in an interval is 75, it will
increment the element number 7 in the WFV. The above numbers are empirically
based on the SPLASH-2 suite but can be easily changed. After the profiling phase,
the compiler has a WFV representing a writeback distribution for the application
at hand. The following step removes noise from the WFV. We define noise as any en-
tries that are smaller than 5% of the largest value in the WFV, and we remove them.
This noise-removal step is necessary because we want our algorithms to be based
on frequent behavior, not some individual rare events. With the noise-free WFV,
we propose two algorithms: a frequency-based algorithm and a weighted-average
algorithm.
For a frequency-based algorithm, given a noise-free WFV:
. Pick the two indices for the highest and lowest numbers above the noise level
. Min = highest number of the interval represented by the lowest index
. Max = lowest number of the interval represented by the highest index
For example, if the two indices were 7 for the lowest and 20 for the highest, Min
would be 69 and Max would be 199. Min and Max represent two thresholds that
will be used at runtime. These two thresholds are given to the hardware. Every X
cycles, the hardware keeps a count of the number of writebacks from the LLC in
that interval. This count will either fall below Min, between Min and Max, or above
Max. If it is below Min, the LLC uses traditional LRU for victim selection because
it means that we have low numbers of replacements of dirty blocks and we do not
want to risk losing performance. If the number of writebacks is between Min and
Max, we use MLRU with M = associativity/2, as indicated in the previous section.
Finally, a number above Max means we have a lot of writebacks, so we use MLRU
with M = associativity.
For the weighted-average algorithm, Min and Max are used as they are in
the frequency-based algorithm, but Min is always 0 and Max is calculated as
32 W F Vi ∗midi
i=0 SU M , where W F Vi is the content of element i of the WFV vector, midi
is the midrange of element i (for example, if we are talking about element 6, it
spans range 50 to 59 with mid of 55), and SUM is the sum of all nonnoisy en-
tries of all WFVs. So in this algorithm we have only two regions: below Max (where
we use MLRU with M = associativity/2) and above Max (where we use MLRU with
3.5 Bandwidth 57
Number of writebacks. This is the traffic from the LLC to the memory, and it
is our measure of success.
Number of read misses. This is the traffic from the memory to the LLC. A write
miss is considered a read miss followed by a write hit.
Number of cycles. Of interest here are the total number of cycles taken by the
program till completion. So we are trying to reduce the number of writebacks
(our main measure of success) while not increasing the number of read
misses (negative side effect), and with minimal impact to the total number
of cycles.
Our first set of experiments compares traditional LRU with MLRU for different
values of M. Figure 3.11 shows the number of writebacks normalized to LRU. All
the benchmarks show a decrease in the number of writebacks. The maximum
58 Chapter 3 Architecture: Heterogeneity in Design
1.0
0.8
LRU(M=1)
Normalized writebacks
MLRU(M=2)
MLRU(M=3)
0.6 MLRU(M=4)
MLRU(M=5)
MLRU(M=6)
MLRU(M=7)
0.4 MLRU(M=8)
0.2
0.0
Barnes Cholesky Fft Fmm Radiosity Radix Raytrace
1.6
LRU(M=1) MLRU(M=5)
MLRU(M=2) MLRU(M=6)
MLRU(M=3) MLRU(M=7)
MLRU(M=4) MLRU(M=8)
Normalized read misses 1.2
0.8
0.4
0.0
Barnes Cholesky Fft Fmm Radiosity Radix Raytrace
1.6
LRU(M=1) MLRU(M=5)
MLRU(M=2) MLRU(M=6)
MLRU(M=3) MLRU(M=7)
Reduction in execution time (%)
0.8
0.4
0.0
1.6
6
Normalized writebacks
1.2
4
0.8
2
0.4
0.0 0
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.14 Normalized number of writebacks for four cores (left) and eight cores (right) (compiler
support and static schemes).
Let’s now add compiler support to the static scheme. Figure 3.14 shows the nor-
malized number of writebacks. We compare LRU with eager writeback
[Lee et al. 2000](a compromise between write-through and writeback), DIP [Qureshi
et al. 2007](dynamic insertion policy), our techniques using compiler support
(frequency-based and weighted-average), and MLRU with M = 4. The best three
schemes for four cores are the weighted-average, M = 4, and DIP. However, the
weighted-average becomes better as we increase the number of cores to eight,
which is a sign of good scalability. The frequency-based method does well too, but
the LRU part of it (when number of writebacks is below Min) holds it a step behind
the weighted-average. Similar behavior can be seen in the number of misses shown
in Figure 3.15. As the number of cores increases, the compiler-based weighted-
average method and static MLRU with M = 4 do much better. The proposed tech-
niques do not hurt performance, as indicated by Figure 3.16.
Raytrace is the only benchmark that suffers some performance loss. This is
because the interference at the LLC among the threads is constructive (blocks
shared by more than one core account for 99% of the blocks at the LLC), which
makes the program very sensitive to block replacement, as was indicated earlier in
Table 3.3.
Let’s now see how our dynamic techniques + compiler support behave. We ex-
perimented with the four variations of the dynamic techniques: writeback-sensitive
and LRU-sensitive, with each one using local or global M. We found that the global
scheme is better from a price-performance point of view. The gain we get from the
3.5 Bandwidth 61
1.0 1.0
Normalized read misses
0.8 0.8
0.6 0.6
0.4 0.4
0.2 0.2
0.0 0.0
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.15 Normalized number of read misses for four cores (left) and eight cores (right) (compiler
support and static schemes).
8 8
Reduction in execution time (%)
6 6
4 4
2 2
0 0
–2 –2
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.16 Speedup for four cores (left) and eight cores (right) (compiler support and static schemes).
local scheme does not justify the extra hardware. The writeback-sensitive technique
is better than LRU-sensitive techniques for most of the benchmarks. So we will be
using writeback-sensitive with global M as our dynamic technique (we will call it
dynamic M). Figure 3.17 compares the total number of writebacks normalized to
LRU. The weighted-average and dynamic M schemes are the best on average. This
62 Chapter 3 Architecture: Heterogeneity in Design
1.5
Normalized writebacks
1.00
1.0
0.10
0.5
0.01
0.0
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.17 Normalized number of writebacks for four cores (left) and eight cores (right) (compiler
support and dynamic schemes).
means that they are the most accurate at capturing block behavior. The LRU compo-
nent of the frequency-based scheme affects its ability to save on off-chip bandwidth.
Figure 3.18 compares the same techniques in terms of read misses. With the
exception of Barnes, dynamic M and weighted-average methods are the best on
average, together with DIP. To understand the behavior of Barnes, it is important
to see the number of dirty blocks it has. Figure 3.19 shows the percentage of dirty
blocks to the total number of blocks at the LLC for Barnes over time. There are
phases where this program has 80% of its blocks dirty. This causes M to be very
high, which increases the likelihood of discarding an important block, because
high values of M affect Barnes performance, as indicated by Table 3.3 earlier. This
also explains the speedup (and slowdown) shown in Figure 3.20.
What is the moral of the story? We can see from all the above that
1.5
1.5
Normalized read misses
1.0 1.0
0.5 0.5
0.0 0.0
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.18 Normalized number of read misses for four cores (left) and eight cores (right) (compiler
support and dynamic schemes).
Barnes
Percentage of dirty blocks to total blocks
80
60
40
20
0
0 1e+08 2e+08 3e+08 4e+08 5e+08 6e+08
Cycleses
All of those scenarios were for off-chip bandwidth. Once we are off-chip, we
need to deal with bandwidth at higher granularities: among chips on-board, among
boards in blades, among blades in cabinets, and among cabinets.
64 Chapter 3 Architecture: Heterogeneity in Design
6 15
Reduction in execution time (%)
4
10
2
0
5
–2
0
–4
–6
–5
–8
Barnes Cholesky Fft Radiosity Raytrace Barnes Cholesky Fft Radiosity Raytrace
Figure 3.20 Speedup for four cores (left) and eight cores (right) (compiler support and dynamic
schemes).
In Conclusion
3.6 In this chapter we went from inside the chip to the system level. We saw how to inte-
grate several chips to make a big machine. The design manifests itself in different
forms: the type of computing nodes, the interconnection, and the memory hierar-
chy. In very large machines, such as supercomputers, the interconnect is usually
implemented at several levels: within a chip, within nodes, within blades, within
cabinets, and among cabinets. These levels of interconnect introduce a severe
level of heterogeneity in data movement latency, not to mention the heterogeneity
in number-crunching performance introduced by the different computing nodes.
How to program those types of heterogeneous machines? This is the topic of the
next chapter.
4
4.1
Programmability
One of the main success factors of a hardware design is its programmability. No
matter how fast or power efficient the designed machine is, if it is not easy to
program, it has high chances of failure. The Cell processor is a good example of
that, even though there are some other factors that contributed to its failure, but
programmability is for sure an important factor.
This chapter discusses the issue of programmability. What do programmers
want? What do we currently have in terms of programmability of heterogeneous
systems? Can we do better?
If we can have a short list for what programmers wish from a perfect program-
ming language (list not exhaustive by any means), we can say the following:
Suppose we, magically, designed a programming language with the above char-
acteristics. Will this help all programmers with their code? The answer depends
on psychology.
4.2 Psychology of Programming 69
Psychology of Programming
4.2 The term psychology of programming is used to designate the field that studies
the psychological aspects of writing programs. We do not want to go deep into
psychology here. But in this section we try to extract some aspects that designate
expert programmers and use them to come up with a list of programming language
characteristics that can help programmers do their job well. Then we use this
information to update/refine the list we made in Section 4.1.
In the book Psychology of Programming edited by Hoc [1990], there is an in-
teresting chapter titled “Expert Programmers and Programming Languages” by
Marian Petre about how expert programmers use programming languages. One of
the things mentioned in that chapter is that experts represent programs in terms
of semantic structure, unlike novices who think syntactically. I can personally con-
firm this too, when I teach undergraduate students versus graduate students (or
strong undergrad students). Average undergrad students, especially in freshman or
sophomore classes, tend to concentrate more on the syntax of the language than the
philosophy of the language or the way of thinking using the underlying program-
ming model. The chapter also mentions that experts spend more time planning.
Let me stop at this planning thing to discuss an important point: picking the right
algorithm, as things have changed a lot lately.
Let’s take the big-O notation in algorithm analysis. Our students learn how to
analyze algorithms and pick or design algorithms based on the asymptotic behavior
of computations of the worst-case scenario, also known as the big-O notation.
Is this the best way to analyze algorithms, with the machines we have now? You
can optimize your algorithm to have lower complexity, that is, a lower amount of
computation. But this may come at the expense of data movement (communication
overhead) and memory access. Communication and memory access are way more
expensive than computations today. This means you can find that an algorithm of
O(n log n) can be slower than another one of O(n2). The moral of this example, and
also pointed out in the aforementioned book, is that expert programmers think at
different levels.
Expert programmers want the ability to think in abstract form, in high-level
constructs, and also the ability to manipulate the hardware. That is, as the book
mentions, they need to choose their grain of focus at different times. This is how
they are productive. A programming language needs to provide that. One of the
main reasons programmers may need to have control over low-level details is to
be able to predict the behavior and control of their programs. If a programmer
thinks only in terms of algorithm and language syntax but knows nothing about
the cache hierarchy, cache organization, the interconnect, whether the processor
70 Chapter 4 Programmability
has superscalar capability and out of order execution, whether each core supports
multiple threads, etc., then the programmer will be in total darkness when it
comes to performance. If power efficiency is added to the list, then the programmer
may want control over the frequency and voltage of each core. If reliability comes
into question, then more hardware details must be revealed. For instance, shall
transient errors be exposed to the programmer? Or dealt with under the hood,
especially in supercomputers and large datacenters?
It should be noted that we are talking about expert programmers, designing and
writing programs for heterogeneous parallel machines to get the best performance
per watt. So applications that don’t have this requirement can just use program-
ming languages that protect the programmer from low-level details, for the sake
of productivity. In that case productivity means finishing your programming task
fast, and not having to worry about performance, power, reliability, etc.
But shall we disregard productivity even for high performance? Definitely not,
because there is an economic angle here that involves time-to-market, customer
satisfaction, etc. The point is to have a programming language that can give the
programmer the ability to think and implement at a high level, as well as the ability
to think and act on the low-level details. As languages mature and more versatile
application pools arise, programmers build more libraries, and levels of abstraction
are built to make things easier from a productivity perspective.
Given all the above, we can now modify the list from Section 4.1 to be as follows.
The wish list of programming language characteristics for expert programmers:
Now that we are done with our wish list, let’s come back to real life. Are we even
close to having such a language? What do we have now?
are generated in real time (i.e., streamed) and we need to process them on the spot,
then something like FPGAs are suitable for the job. Now, let’s dive into our chosen
paradigms.
4.3.1 MPI
MPI1 is a de facto programming model for distributed memory architectures, chal-
lenged only with PGAS (partitioned global address space) models. MPI is just a
library and set of APIs used on top of common languages like C/C++ and Fortran.
1. [Link]
4.3 What Do We Have? The Current Status Quo 73
You don’t want to use Java or Python on a distributed memory system, do you? We
are talking about performance here! With MPI several processes are created, using
the command line by the user. Since we are talking about processes, then it means
each process has its own address space. Nevertheless, these processes need to ex-
change data as they are solving the same problem. Data values are exchanged using
messages, hence the name MPI (message passing interface). Listing 4.2 shows the
MPI version of vector addition. It seems longer, much longer, than the sequential
version! As we will shortly see, it is just for setting the stage.
All the created processes execute the same code. In order to avoid redundant
computations, we must ensure that each process works on different parts of the
problem or does different tasks. To do that, MPI groups processes into something
called a communicator. By default, all created processes are inside one default com-
municator called MPI_COMM_World. You can create different groups from this
communicator and/or split the communicator if you want. Inside each communi-
cator in our example, we just keep the default communicator. Each process has
a unique ID, called a rank. Line 14 in the listing just initializes the MPI runtime
system and passes to it the arguments of the main() function, if any. Lines 15 and
16 let each process get its rank and the size of the default communicator. That is,
after executing lines 15 and 16, each process will know its rank, stored in a variable
with the same name, and the variable size will have the total number of processes
created. Line 48 closes the MPI runtime. You cannot use any API from MPI after
line 48. Your program may continue execution as a single process executing C (or
C++ or Fortran). By using the rank in an if-condition, you can make each process
do different things from each other. Here, the master process, the one with rank 0,
dynamically allocates three arrays: A, B, and C. It also populates arrays A and B with
data. This is done only by process 0 because of the if-condition in line 22. All the
processes, including the master process, dynamically allocate three other arrays,
of smaller sizes, called local_a, local_b, and local_c. This is shown in lines 23, 24,
and 25.
Because sending messages is very expensive and MPI is usually used in big
machines with distributed and large numbers of computing nodes, we use it when
the problem size is really huge. Otherwise, we will lose the performance we gained
from parallelism due to the overhead of communication. We are using small array
sizes in the example of Listing 4.2 just for simplicity. But in real situations, if N
is just one million, as in our example here, then MPI is a bad choice. MPI allows
two processes to communicate through MPI_Send() and MPI_Receive(). Each send
must have a corresponding receive or else you may reach a deadlock because those
APIs are usually blocking. That is, MPI_Receive(), when called by a process, blocks
74 Chapter 4 Programmability
Process 0 0 1 2 3 4 5 6 7 8
Process 0 0 1 2
Process 1 3 4 5
Process 2 6 7 8
this process till it receives the data from the corresponding MPI_Send(). But in our
example here we don’t want this one-to-one communication. We need collective
communication among processes because it is cheaper than sending one-to-one
messages. So the master process takes arrays A and B, cuts them into pieces of
equal sizes, and scatters them to the other arrays including itself. This is what
happens in lines 27–30. Figure 4.1 shows how the scatter is performed. Let’s look at
one scatter operation and explain it. MPI_Scatter(A, N/size, MPI_INT, local_
A, N/size, MPI_INT, 0, MPI_COMM_WORLD) means take the array A from process
0, divide it into chunks of N/size elements, and give those chunks to processes
based on their ranks. That is, process 0 takes the first N/size, process 1 takes the
next chunk, etc. The receiving processes, including the master process, store those
chunks into another array local_A. This API is collective, which means all processes
must call it or else the program will hang with a deadlock. Each process will do the
additions of its chunk (lines 33 and 34). We have parallelism here because all the
chunks are added in parallel by the different processes. The following step is to
gather all these chunks and put them back into the original array C at the address
space of process with rank 0 (line 36). Remember that arrays A, B, and C exist only
in the address space process 0 (look at lines 23–25). Therefore, only process 0 must
free those arrays (lines 39–44). But the local arrays local_a, local_b, and local_c have
been allocated by all processes, and hence must be freed by all of them (line 46).
MPI can be used with shared memory models like OpenMP or with accelerator
languages like CUDA. A process can offload part of its code to a GPU, for example.
MPI also allows you to map processes to cores (affinity). It is an easy-to-grasp
language. You can have a parallel program ready to execute from a sequential one
with just a few extra lines, but you have to manage communication by hand.
76 Chapter 4 Programmability
4.3.2 OpenMP
Whereas MPI deals with distributed memory, OpenMP2(Open Multiprocessing) is
gaining popularity in shared memory architectures. Here we are talking about one
process executed in parallel using threads sharing the whole address space. These
threads share the heap, text, and data, but each thread has its own stack. It is also a
library on top of many common languages and is based on pragmas. A quick look
at Listing 4.3 reveals that it does not require a lot of stage setting like MPI. Actually,
the main difference between the sequential version and the OpenMP version is line
20! This line, simply speaking, tells the OpenMP runtime to generate M threads.
The number of iterations of the for-loops (OpenMP works only with for-loops, not
while nor do-while) are divided among those M threads. There are several remarks
here.
The program in Listing 4.3 starts with one process and one thread of execution.
That thread executes everything till line 19. The pragma at line 20 forks M processes.
Each process will execute a subset of the loop iterations, as we said before. Since
in this code the programmer did not indicate the number of threads, then it is
up to the runtime to determine it. In most OpenMP implementations, the default
number of threads will be the number of cores. The pragma applies only to the
2. [Link]
4.3 What Do We Have? The Current Status Quo 77
following structured piece of code. In our case here, it is the for-loop. Starting from
line 24, we are back to one thread of execution.
Starting with version 4.0, OpenMP can now offload tasks to accelerators like
GPUs and FPGAs [der Pas et al. 2017]. This is done with a modifier, called target,
added to the #pragma. Due to its simplicity and its support for accelerators, hence
heterogeneous architecture, OpenMP has gained wider popularity recently. It can
be used in tandem with MPI if we have multisocket boards (i.e., nodes), or even a
single-socket board with a multicore processor connected together. The processors
78 Chapter 4 Programmability
rank0 F J F J
O O O O
OpenMP
Master R I R I
thread K N K N
{parallel region} {parallel region}
rank1 F J F J
Processes O O O O
OpenMP
MPI Master R I R I
thread K N K N
{parallel region} {parallel region}
rank2 F J F J
O O O O
OpenMP
Master R I R I
thread K N K N
{parallel region} {parallel region}
on the board are sharing memory, thus OpenMP. Across boards is distributed
memory, thus MPI. This hybrid combination is shown in Figure 4.2. In MPI you
explicitly manage communication. In OpenMP communication is implicit, which
is easier from a programming perspective but harder to debug and profile.
OpenMP enjoys a very active community and is gaining more and more popu-
larity. Recently, OpenMP started supporting accelerators too.
4.3.3 OpenCL
Open Computing Language (OpenCL)3 was developed by a consortium of many
companies, initiated by Apple and maintained by the Khronos Group [Kaeli et al.
2015]. OpenCL is designed with several goals in mind.
. Maximize portability by minimizing burden on vendors. Take a look at the
list of companies in the OpenCL consortium!
. Use all computation resources. (So it is designed with heterogeneous com-
puting in mind.)
3. [Link]
4.3 What Do We Have? The Current Status Quo 79
The first requirement makes the host code very tedious, as you can see from
Listing 4.4. In order to minimize the burden on vendors, you have to move that
burden to the programmer and/or the runtime. Setting the stage here is sophis-
ticated. The second requirement is more or less fulfilled and growing. The third
requirement, to the best of my knowledge, is not fulfilled yet, at least not at full
scale. The fourth requirement is materialized at the expense of control to the pro-
grammer. OpenCL is C-based with a rich library of APIs. It is also a data-parallel
language, Single Program Multiple Data (SPMD).
The programming model consists of one host and one or more compute de-
vices. Each compute device consists of one or more compute units. Each compute
unit consists of one or more processing elements, as shown in Figure 4.3. This de-
scription can be mapped to a wide range of accelerators. As you can see, we did not
mention the name of any accelerator (GPU, CPU, etc.), which is the philosophy of
OpenCL. See the fourth requirement above.
The host program has some parts that are executed in parallel by the other com-
putation devices. Those parts are called kernels. All processing elements execute the
same kernel with different data, hence SPMD. Work items are grouped into work
groups. So we can say that the OpenCL kernel is the basic unit of parallel code that
can be executed on a target compute device. You decide how many work groups and
how many work items per work group. Even though all work items in all work groups
are executing the same kernel, work items in the same group can synchronize to-
gether. It is much cheaper to organize it like this because to be able to synchronize
a huge number of work items would be prohibitively expensive in hardware imple-
mentation. This is why we see in GPUs, for example, execution units (streaming
processors (SPs) or CUDA cores), that are grouped together as streaming multi-
processors (SMs). Threads executed by SPs within the same SM can synchronize
together and exchange data faster.
Because OpenCL is designed by a consortium of many companies, there are
many implementations of it. Each implementation is called a platform (line 35 in
Listing 4.4). All platforms follow the standard of OpenCL, of course. But, as with
any programming language standard, there are open issues. We saw in OpenMP,
for example, that not specifying the number of threads in a parallel section leaves
it to the runtime. What the runtime does is implementation dependent. Here also,
there are open issues and they are implementation dependent. That is, they are
platform dependent.
80 Chapter 4 Programmability
PE0
Compute
unit 0
OpenCL runs on host, PE1
which submits work Compute
to compute devices. device 0 PE0
Compute
unit 1
PE1
Work item
PE0
Compute
Host unit 2
PE1
PE3
Once you pick a platform, in case your system has several, you need to see
which devices are attached (line 36). OpenCL recognizes CPUs, GPUs, FPGAs, and
generic accelerators. The next step is to set the stage for executing a program on that
platform. Setting the stage for a program is called setting the context (line 40). Then
you create a command queue from the host to each device (line 43). A command
queue is a channel of communication by which the host can send commands to a
device. This command can be data transfer, kernel execution, etc. Even though it is
called a queue, you can set it up such that commands can be executed out of order,
in order, or something in between by setting up dependencies.
Because OpenCL wants to have higher portability with less burden to vendors,
it has its own generic memory objects: buffers and images. In the example of
Listing 4.4, we use only buffers (lines 46–51); then we include commands in the
command queues to move the arrays A and B into these buffers (lines 54–57).
Then comes the most tedious part, which is the designation of the kernel and
executing it on the computation device. The kernel can be loaded from a separate
4.3 What Do We Have? The Current Status Quo 83
source code file (lines 24–28 load the source code of Listing 4.5 into a string). The
next step is to create a program (line 60) and build the program (line 64). If the
program in the source code has several kernels (in our example here it has only one
kernel), then you need to do the create kernel step (line 67) for each. This compiles
the kernel for the computation device used. Lines 70–72 specify the arguments for
that kernel. You need an API call for each argument. Finally, we are ready to execute
the kernel. To specify work groups and work items, we use what is called NDRange
(lines 75–78). At the end, you bring the data back to the host (lines 82–83) and clean
up (lines 88–98). For the sake of portability and usage with wider computing devices,
OpenCL had to sacrifice some programmer productivity and some programmer
control. If you have an Intel processor with multicore and embedded GPU as well
as a discrete NVIDIA GPU, then OpenCL can be used to execute on all of them in
parallel.
4.3.4 OpenACC
OpenACC,4 or open accelerators, is designed, like OpenMP, to make using
accelerators easy for most programmers [Chandrasekaran and Juckeland 2018].
It follows the same philosophy as OpenMP. This group of libraries is relatively
new, announced at the annual international supercomputing conference held in
2011. A quick look at Listing 4.6 clearly shows that ease of programming is one of
the goals. Programmers can write a sequential version and then annotate it with
OpenCL #pragmas. Alas, ease of programming always comes at the expense of
more control revoked from the programmer. The loop is parallelized in a way sim-
ilar to OpenMP and in a philosophy similar to OpenCL. The loop iterations are
4. [Link]
84 Chapter 4 Programmability
distributed among workers grouped into gangs. You specify the number of gangs
and number of workers or you leave it to the runtime, as we did in this example.
OpenACC allows for a third level of parallelism (besides gangs and workers) called
vectors.
4.3.5 CUDA
With GPUs becoming widely used in many applications that are nongraphic, GPU-
related languages are becoming widely adopted. Compute unified device architec-
4.3 What Do We Have? The Current Status Quo 85
ture (CUDA)5 is by far one of the most mature and widely adopted languages for
using GPUs, especially those from NVIDIA. It was born in 2006, developed and
maintained by NVIDIA. Following the same philosophy as OpenCL, a kernel is of-
floaded from the host to the device (i.e., the GPU). That kernel is executed by a
grid that is composed of a group of blocks. Each block is composed of a group
of threads. Each thread executes the same kernel but with different data, so we
are again using SPMD. As an example, launching a kernel (line 35 of Listing 4.7)
has two numbers between triple angle brackets. The first number is the number of
blocks and the second number is the number of threads per block. All the blocks
form one grid, in CUDA parlance. So we can say that a grid is a kernel in execu-
tion (one grid per kernel). Blocks and grids can be designed to be 1D, 2D, or 3D.
GPUs and CPUs have their different memory, even if the programming model of
the recent versions of CUDA gives the illusion of unified memory. Moving the data
between the GPU and CPU is very expensive, so it must be managed carefully either
by the programmer or under the hood by the runtime in recent GPU architectures
(namely, Pascal and Volta). As we can see from the code, cudaMalloc dynamically
allocates space in the device’s memory (i.e., the GPU), while malloc dynamically
allocates space in the system memory. CUDA can be combined with, for example,
OpenMP and MPI.
5. [Link]
6. [Link]
86 Chapter 4 Programmability
7. [Link]
88 Chapter 4 Programmability
strategy. That is, if a core finishes its assigned tasks, the runtime can assign an-
other task from an overloaded core to that idle one. This makes programmability
easy, yet not for free. The scheduling operation itself, done by the runtime, has
its overhead. The runtime may not take into account the interconnect among cores
and the heterogeneity in frequency when some cores become hot and hence slower
due to DVFS, as we saw in Section 1.2.2.
Another important model is PThreads, or POSIX threads. Pthreads is a language-
independent standard. That is, it can be implemented in any language to support
multithreading parallel execution. There are many implementations in OS follow-
ing the POSIX standard. It is important to note here that POSIX is not only about
multithreading. It is a full standard for OS interfaces that are UNIX-like (e.g., Linux,
FreeBSD, MAC, Android, etc.). This is why POSIX stands for “portable operating sys-
tem interface.” The Pthreads standard gives a lot of control to the programmer, at
the expense of more coding, which means lower productivity from the program-
mer. All thread management, like creating, scheduling, and deleting threads, can
be done by the application programmer. This has the advantage of less system calls
and hence higher performance. Also, the fine-grain management of threads by the
programmer allows for application-specific thread scheduling. Of course the situ-
ation is more complicated than this if we dig deeper. The programmer is aware of
the threads, but these are user-level threads. The OS, the kernel, has its own threads
of execution for any application. The relationship between user-level threads and
kernel-level threads can be 1:1, N:1, or M:N depending on the implementation.
We will not go further here, but you get the idea. As a programmer for a het-
erogeneous system, you have a lot of choices. You pick what is beneficial to your
application.
In Conclusion
4.4 This chapter has provided a quick tour of the most widely used languages in het-
erogeneous systems as well as a look at the psychology of programming. We did
not cover everything, but we get the idea that we are still a long way to go from our
programming language wish list. Let me try to summarize some challenges I see
here.
Exascale Computing
5.2 A quick look at the Top500 supercomputers reavels that we are already at petascale
computing. Exascale is the next big challenge. It is a much harder challenge than
going from gigascale to petascale, and this is why its delivery deadline has been
pushed several times. The most recent deadline is 2021. With exascale we can have
way more realistic simulations of the human brain, analyzing the whole human
genome very fast (curing cancer?), very fast scientific simulations pushing the
frontiers of science. We can say that the potential is almost endless. So what is
so challenging about getting to exascale? Here are the research challenges.
. If we just expanded the petascale machines to exascale, the power consumed
would be prohibitive. The measure of success is 1 exaflop for 20–30 MW.
. What are the characteristics of a programming model for such a machine?
. How do we deal with silent and transient errors at that scale? Should they be
exposed to programmers, complicating their task? Or should they be dealt
with under the hood, consuming more power? (Look at the first item of this
list.)
. Are our current algorithms scalable to that level?
. How can we scale the OS to deal with such a machine?
Neuromorphic Chips
5.3 Neuromorphic chips, or cognitive computing in general, are still not yet main-
stream. But with many research groups working on it and many application ideas
arising, we will soon see a neuromorphic coprocessor in a heterogeneous system.
Before we proceed, we need to make an important distinction between two goals of
neuromorphic chips. The first goal is to design a biological-like chip. In this case
we are building a tool for neuroscientists to use. The other goal is to be inspired by
the workings of the brain but design a chip that is useful for other cognitive appli-
cations, like pattern recognition, learning, etc. Given the nature of our book, we
concentrate on the second goal.
Schuman et al. [2017] provides an almost exhaustive survey of advances in
neuromorphic computing, with nearly 3000 references! Neuromorphic chips in-
volve experience from many different fields, as shown in Figure 5.1. Neuroscien-
tists formulate, and keep revising and refining, an accurate model for the human
brain. Material engineers find the most efficient material—memristors, for exam-
ple [Sayyaparaju et al. 2017, Shafiee et al. 2016]—to build chips inspired by that
model. Electrical and computer engineers use the material and the model to de-
5.3 Neuromorphic Chips 93
Neuroscience
Neuroscientists
model of the brain
Materials to
Material scientists
build device
Application
Computer scientists
development
sign the chip with the desired goals of speed, power, relability, area, etc. They
build the software framework to train and use those chips, to make it easier for
application developers to use them. Finally, to be used efficiently in the outside
world, application developers need to use those chips in their applications. The
main reasons neuromorphic chips are attractive for applications related to pattern
recognition, learning, prediction, etc. is their low power, speed, and higher reli-
ability than programming the same machine learning algorithm on a traditional
multicore processor.
Besides the above big picture about the ecosystem of neuromorphic chips, the
research directions are related to (1) the neurons and their models, (2) modeling
dendrites, axons, and synapses, if needed, to be as close as possible to the biological
brain, (3) interconnection among neurons, (4) the learning algorithm and whether
94 Chapter 5 Research Directions
it is done online or offline, and (5) the hardware design itself. These directions are
interrelated. For example, the design of the chip and neuron interconnection may
dictate a specific learning algorithm or vice versa.
We summarize the most important research questions in the following list
[US Department of Energy 2016].
Quantum Computing
5.4 Whenever a new hardware model appears, there is a big void between the appli-
cation programmer and the machine itself. As time passes and people gain ex-
perience, the layers in between are filled up with compilers, low-level languages,
high-level languages, workflows, etc. Quantum computers are in this early stage
now. We are not yet at quantum supremacy (where traditional parallel machines
cannot simulate that number of quantum bits (qubits)), but there are a lot of ad-
vances recently in that field. What type of programming languages do we need to
have here? And how can they interact with programming models of traditional
machines? Remember that a quantum computer is unlikely to be a stand-alone
machine but rather a coprocessor in a large heterogeneous machine.
Even though research in quantum programming started as early as 1996, we
still have a long way to go. We have to keep in mind that human intuition is better
adapted to the classical world than the quantum world. Therefore, we can expect
a lot of bugs and faults in designing quantum programs as compared to classical
programs [Ying 2016].
Any program consists of two things: the data and the control that manipulates
the data. In quantum computing, data are quantum (qubits). How about the con-
trol? Control can be classical, similar to what we have in traditional programs (e.g.,
loops, conditional, etc.). In that regard there has been some progress in classical
control with quantum data. There is also quantum control (e.g., superposition), and
quantum control with quantum data is still in its infancy. There is a lot of room for
innovation and invention in the quantum world.
References
J. Archibald and J.-L. Baer. May 1986. Cache coherence protocols: Evaluation using a
multiprocessor simulation model. ACM Transactions on Computer Systems, pp. 273–
298. DOI: 10.1145/6513.6514 34
E. Azarkhish, D. Rossi, I. Loi, and L. Benini. 2016. Design and evaluation of a processing-
in-memory architecture for the smart memory cube. In Proceedings of the 29th
International Conference on Architecture of Computing Systems – ARCS 2016, Volume
9637, pp. 19–31. Springer-Verlag, New York. DOI: 10.1007/978-3-319-30695-7_2 91
R. Balasubramonian, J. Chang, T. Manning, J. H. Moreno, R. Murphy, R. Nair, and
S. Swanson. July 2014. Near-data processing: Insights from a micro-46 workshop. IEE
MICRO Magazine, 34(4): 36–42. DOI: 10.1109/MM.2014.55 91
B. Beckmann and D. Wood. December 2004. Managing wire delay in large chip-
multiprocessor caches. In Proceedings of the 37th International Annual Symposium on
Microarchitecture (Micro-37), pp. 319–330. DOI: 10.1109/MICRO.2004.21 34
L. Benini and G. DeMicheli. January 2002. Networks on chips: A new SoC paradigm. In IEEE
Computer, pp. 70–78. DOI: 10.1109/2.976921 34
M. T. Billingsley III, B. R. Tibbitts, and A. D. George. 2010. Improving UPC productivity via
integrated development tools. In Proceedings of the Fourth Conference on Partitioned
Global Address Space Programming Model, PGAS ’10, pp. 8:1–8:9. ACM, New York.
DOI: 10.1145/2020373.2020381 68
S. Borkar, P. Dubey, K. Kahn, D. Kuck, H. Mulder, S. Pawlowski, and J. Rattner. 2006. Platform
2015: Intel processsor and platform evolution for the next decade. White paper, Intel
Corporation. 35
A. Boroumand, S. Ghose, M. Patel, H. Hassan, B. Lucia, K. Hsieh, K. T. Malladi, H. Zheng,
and O. Mutlu. January 2017. Lazypim: An efficient cache coherence mechanism
for processing-in-memory. IEEE Computer Architecture Letters, 16(1): 46–50. DOI:
10.1109/LCA.2016.2577557 91
P. Bose. February 2013. Is dark silicon real? Technical perspective. Communications of the
ACM, 56(2): 92–92. DOI: 10.1145/2408776.2408796 3
J. Boukhobza, S. Rubini, R. Chen, and Z. Shao. November 2017. Emerging NVM: A survey
on architectural integration and research challenges. ACM Transactions on Design
Automation of Electronic Systems, 23(2): 14:1–14:32. DOI: 10.1145/3131848 7
R. K. Braithwaite, W.-c. Feng, and P. S. McCormick. 2012. Automatic NUMA characterization
using cbench. In Proceedings of the 3rd ACM/SPEC International Conference on
Performance Engineering, ICPE ’12, pp. 295–298. ACM, New York. DOI: 10.1145/
2188286.2188342 6
S. Bratus, N. D’Cunha, E. Sparks, and S. W. Smith. 2008. Toctou, traps, and trusted
computing. In Proceedings of the 1st International Conference on Trusted Computing
and Trust in Information Technologies: Trusted Computing—Challenges and Applications,
pp. 14–32. Springer-Verlag, Berlin, Heidelberg. DOI: 10.1007/978-3-540-68979-9_2 47
Broadcom Corporation, 2006. BCM1455: Quad-core 64-bit MIPS processor.
[Link] 35
References 99
W. Dally and B. Towles. 2001. Route packets, not wires: On-chip interconnection networks.
In Proceedings of the 38th Conference on Design Automation, pp.684–689. DOI: 10.1145/
378239.379048 34
R. H. Dennard, F. H. Gaensslen, V. L. Rideout, E. Bassous, and A. R. LeBlanc. October
1974. Design of ion-implanted MOSFET’s with very small physical dimensions. IEEE
Journal of Solid-State Circuits 9(5): 256–268. DOI: 10.1109/JSSC.1974.1050511 2
R. V. der Pas, E. Stotzer, and C. Terboven. 2017. Using OpenMP—the Next Step. MIT Press,
Cambridge, MA. 77
A. S. Dhodapkar and J. E. Smith. 2002. Managing multi-configuration hardware via dynamic
working set analysis. In Proceedings of the 17th International Symposium on Computer
Architecture, pp. 233–244. DOI: 10.1145/545214.545241 33
G. Di Crescenzo. 2005. Security of erasable memories against adaptive adversaries. In
Proceedings of theedings of the 2005 ACM Workshop on Storage Security and Survivability,
StorageSS ’05, pp. 115–122. DOI: 10.1145/1103780.1103798 47
S. J. Eggers and R. H. Katz. 1989. Evaluating the performance of four snooping cache
coherence protocols. In Proceedings of the 22nd Annual International Symposium on
Computer Architecture, pp. 2–15. DOI: 10.1145/74926.74927 34
M. Ekman, F. Dahlgren, and P. Stenström. August 2002. TLB and snoop energy-reduction
using virtual caches for low-power chip-multiprocessor. In Proceedings of the
IEEE/ACM International Symposium on Low Power Electronics and Design, pp. 243–246.
DOI: 10.1145/566408.566471 34
R. Elbaz, L. Torres, G. Sassatelli, P. Guillemin, C. Anguille, M. Bardouillet, C. Buatois,
and J. B. Rigaud. 2005. Hardware engines for bus encryption: A survey of existing
techniques. In Proceedings of the Conference on Design, Automation and Test in Europe,
DATE ’05, Volume 3, pp. 40–45. DOI: 10.1109/DATE.2005.170 47
H. Esmaeilzadeh, E. Blem, R. St. Amant, K. Sankaralingam, and D. Burger. 2011. Dark silicon
and the end of multicore scaling. In Proceedings of the 38th Annual International
Symposium on Computer Architecture, ISCA ’11, pp. 365–376. ACM, New York. DOI:
10.1145/2000064.2000108 3
K. I. Farkas, P. Chow, N. P. Jouppi, and Z. Vranesic. 1997. The multicluster architecture:
Reducing cycle time through partitioning. In Proceedings of the 30th International
Symposium on Microarchitecture, pp. 149–159. DOI: 10.1023/A:1018782806674 34
F. Fiori and F. Musolino. 2001. Analysis of EME produced by a microcontroller operation.
In Proceedings of the Conference on Design, Automation and Test in Europe, DATE ’01,
pp. 341–347. IEEE Press, Piscataway, NJ. DOI: 10.1109/DATE.2001.915047 49
A. Fiskiran and R. Lee. October 2004. Runtime execution monitoring (REM) to detect and
prevent malicious code execution. In Proceedings of the IEEE International Conference
on Computer Design, pp. 452–457. DOI: 10.1109/ICCD.2004.1347961 49
K. Flautner, N. Kim, S. Martin, D. Blaauw, and T. Mudge. May 2002. Drowsy caches: Simple
techniques for reducing leakage power. In Proceedings of the Annual International
Symposium on Computer Architecture, pp. 147–157. DOI: 10.1145/545214.545232 34
References 101
Architectural Support for Programming Languages and Operating Systems, pp. 371–384.
DOI: 10.1145/1736020.1736062 49
G. F. Hughes and J. F. Murray. February 2005. Reliability and security of RAID storage
systems and D2D archives using SATA disk drives. In IEEE Transactions on Storage,
1(1): 95–107. DOI: 10.1145/1044956.1044961 47
W. W. Hwu. 2015. Heterogeneous System Architecture: A New Compute Platform Infrastructure,
1. Morgan Kaufmann, Burlington, MA. 85
K. Inoue, V. Moshnyaga, and K. Murakami. February 2002. Trends in high-performance,
low-power cache memory architectures. IEICE Transactions on Electronics, E85-C(2):
303–314. 34
T. B. Jablin, P. Prabhu, J. A. Jablin, N. P. Johnson, S. R. Beard, and D. I. August. 2011. Automatic
CPU-GPU communication management and optimization. In Proceedings of the 32nd
ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI
’11, pp. 142–151. DOI: 10.1145/1993498.1993516 19
A. Jaleel, W. Hasenplaugh, M. Qureshi, J. Sebot, S. Steely, and J. Emer. 2008. Adaptive
insertion policies for managing shared caches. In PACT ’08: Proceedings of the
17th International Conference on Parallel Architectures and Compilation Techniques,
pp. 208–219. DOI: 10.1145/1454115.1454145 51
A. Jaleel, J. Nuzman, A. Moga, S. Steely, and J. Emer. February 2015. High performing cache
hierarchies for server workloads: Relaxing inclusion to capture the latency benefits
of exclusive caches. In High Performance Computer Architecture (HPCA), 2015 IEEE
21st International Symposium, pp. 343–353. DOI: 10.1109/HPCA.2015.7056045 51
J. Jeong and M. Dubois. February 2003. Cost-sensitive cache replacement algorithms. In
Proceedings of the 9th IEEE Symposium on High Performance Computer Architecture,
pp. 327–337. DOI: 10.1109/HPCA.2003.1183550 35, 51
N. E. Jerger, T. Krishna, and L.-S. Peh. 2017. On-Chip Networks. Morgan & Claypool Publishers,
San Rafael, CA. 36
Y. Jin, N. Kupp, and Y. Makris. 2009. Experiences in hardware Trojan design and
implementation. In Proceedings of the 2009 IEEE International Workshop on Hardware-
Oriented Security and Trust, HST ’09, pp. 50–57. IEEE Computer Society, Washington,
DC. DOI: 10.1109/HST.2009.5224971 49
N. P. Jouppi, C. Young, N. Patil, D. Patterson, G. Agrawal, R. Bajwa, S. Bates, et al. 2017. In-
datacenter performance analysis of a tensor processing unit. In Proceedings of the
44th Annual International Symposium on Computer Architecture, ISCA ’17, pp. 1–12.
ACM, New York. DOI: 10.1145/3079856.3080246 27
D. R. Kaeli, P. Mistry, D. Schaa, and D. P. Zhang. 2015. Heterogeneous Computing with OpenCL
2.0, 3. Morgan Kaufmann, Burlington, MA. 78
M. Kamble and K. Ghose. August 1997. Analytical energy dissipation models for low
power caches. In Proceedings of the IEEE/ACM International Symposium on Low Power
Electronics and Design, pp. 143–148. DOI: 10.1145/263272.263310 34
References 103
Support for Programming Languages and Operating Systems (ASPLOS), pp. 240–250.
DOI: 10.1145/291006.291053 33
G. Pekhimenko, T. Huberty, R. Cai, O. Mutlu, P. Gibbons, M. Kozuch, and T. Mowry. February
2015. Exploiting compressed block size as an indicator of future reuse. In High
Performance Computer Architecture (HPCA), 2015 IEEE 21st International Symposium,
pp. 51–63. DOI: 10.1109/HPCA.2015.7056021 35
M. Potkonjak, A. Nahapetian, M. Nelson, and T. Massey. 2009. Hardware Trojan horse
detection using gate-level characterization. In Proceedings of the 46th Annual Design
Automation Conference, DAC ’09, pp. 688–693. ACM, New York. DOI: 10.1145/1629911
.1630091 49
S. M. Potter. 2001. What can artificial intelligence get from neuroscience? In Artificial
Intelligence Festschrift: The Next 50 Years, pp. 174–185. Springer-Verlag, New York. 26
K. Punniyamurthy and A. Gerstlauer. 2017. Exploring non-uniform processing in-memory
architectures. In 1st Workshop on Hardware/Software Techniques for Minimizing Data
Movement, held in conjunction with PACT. 91
M. Qureshi, A. Jaleel, Y. Patt, S. C. Steely, and J. Emer. June 2007. Adaptive insertion policies
for high performance caching. In Proceedings of the 34th International Symposium on
Computer Architecture (ISCA), pp. 381–391. DOI: 10.1145/1250662.1250709 51, 60
M. Qureshi, D. Lynch, O. Mutlu, and Y. Patt. June 2006. A case for MLP-aware cache
replacement. In Proceedings of the 33rd International Symposium on Computer
Architecture (ISCA), pp. 167–178. DOI: 10.1109/ISCA.2006.5 35, 51
M. K. Qureshi and Y. N. Patt. 2006. Utility-based cache partitioning: A low-overhead, high-
performance, runtime mechanism to partition shared caches. In Proceedings of the
39th Annual IEEE/ACM International Symposium on Microarchitecture, pp. 423–432.
DOI: 10.1109/MICRO.2006.49 34
R. Ramanathan. 2006. Intel multi-core processors: Making the move to quad-core and
beyond. White paper, Intel Corporation. 35
J. Reineke, D. Grund, C. Berg, and R. Wilhelm. September 2006. Predictability of
cache replacement policies. Reports of SFB/TR 14 AVACS 9, SFB/TR 14 AVACS.
[Link] 35
A. Ros, M. Davari, and S. Kaxiras. February 2015. Hierarchical private/shared classification:
The key to simple and efficient coherence for clustered cache hierarchies. In High
Performance Computer Architecture (HPCA), 2015 IEEE 21st International Symposium,
pp. 186–197. DOI: 10.1109/HPCA.2015.7056032 35
X. Ruan, A. Manzanares, S. Yin, M. Nijim, and X. Qin. 2009. Can we improve energy efficiency
of secure disk systems without modifying security mechanisms? In Proceedings of the
2009 IEEE International Conference on Networking, Architecture, and Storage, NAS ’09,
pp. 413–420. DOI: 10.1109/NAS.2009.71 47
K. Rupp. 2018. 42 years of microprocessor trend data. [Link]
microprocessor-trend-data (last accessed March 2018). 2
References 107
the Conference on Design, Automation and Test in Europe, DATE ’09, pp. 570–573. DOI:
10.1109/DATE.2009.5090729 47
H. Sutter. March 2005. The free lunch is over: A fundamental turn toward concurrency in
software. Dr. Dobb’s Journal, 30(3): 202–210. 4
TCG. April 2008. Trusted platform module (TPM) summary.
[Link] 47
M. Tehranipoor and F. Koushanfar. January 2010. A survey of hardware Trojan taxonomy and
detection. IEEE Design & Test of Computers, 27(1): 10–25. DOI: 10.1109/MDT.2010.7
49
A. Tereshkin. 2010. Evil maid goes after PGP whole disk encryption. In Proceedings of the 3rd
International Conference on Security of Information and Networks, SIN ’10, p. 2. ACM,
New York. DOI: 10.1145/1854099.1854103 49
K. Tiri. 2007. Side-channel attack pitfalls. In Proceedings of the 44th Annual Design Automation
Conference, DAC ’07, pp. 15–20. ACM, New York. DOI: 10.1145/1278480.1278485 47,
49
M. Tomasevic and V. Milutinovic. 1993. The Cache Coherence Problem in Shared-Memory
Multiprocessors: Hardware Solutions. IEEE Computer Society Press, Los Alamitos, CA.
34
D. M. Tullsen, S. Eggers, and H. M. Levy. 1995. Simultaneous multithreading: Maximizing
on-chip parallelism. In Proceedings of the 22nd International Symposium on Computer
Architecture, pp. 392–403. DOI: 10.1109/ISCA.1995.524578 3, 14, 34, 35
D. Unat, A. Dubey, T. Hoefler, J. Shalf, M. Abraham, M. Bianco, B. L. Chamberlain, et al.
October 2017. Trends in data locality abstractions for HPC systems. IEEE Transactions
on Parallel and Distributed Systems, 20(10): 3007–3020. DOI: 10.1109/TPDS.2017
.2703149 64
US Department of Energy. April 2013. Technical challenges of exascale computing. Technical
Report JSR-12-310. [Link]
US Department of Energy. 2016. Neuromorphic computing, architectures, models, and
applications: A beyond-CMOS approach to future computing. Technical report, Oak
Ridge National Laboratory. 94
A. S. Vaidya, A. Shayesteh, D. H. Woo, R. Saharoy, and M. Azimi. 2013. SIMD divergence
optimization through intra-warp compaction. In Proceedings of the 40th Annual
International Symposium on Computer Architecture, ISCA ’13, pp. 368–379. ACM, New
York. DOI: 10.1145/2485922.2485954 22
K. Varadarajan, S. Nandy, V. Sharda, A. Bharadwaj, R. Iyer, S. Makineni, and D. Newell.
June 2006. Molecular caches: A caching structure for dynamic creation of
application-specific heterogeneous cache regions. In Proceedings of the 39th Annual
IEEE/ACM International Symposium on Microarchitecture (MICRO-39), pp. 433–442.
DOI: 10.1109/MICRO.2006.38 34
R. Vaslin, G. Gogniat, J.-P. Diguet, E. Wanderley, R. Tessier, and W. Burleson. February 2009.
A security approach for off-chip memory in embedded microprocessor systems.
References 109
LinkedIn. [Link]
Twitter. [Link]
His webpage. [Link]
If you look around you will find that all computer systems, from your
portable devices to the strongest supercomputers, are heterogeneous
in nature. The most obvious heterogeneity is the existence of computing
nodes of different capabilities (e.g. multicore, GPUs, FPGAs, …). But there
are also other heterogeneity factors that exist in computing systems, like
the memory system components, interconnection, etc. The main reason for
these different types of heterogeneity is to have good performance with
power efficiency.
Heterogeneous computing results in both challenges and
opportunities. This book discusses both. It shows that we need to deal
with these challenges at all levels of the computing stack: from algorithms
all the way to process technology. We discuss the topic of heterogeneous
computing from different angles: hardware challenges, current hardware
state-of-the-art, software issues, how to make the best use of the current
heterogeneous systems, and what lies ahead.
The aim of this book is to introduce the big picture of heterogeneous
computing. Whether you are a hardware designer or a software developer,
you need to know how the pieces of the puzzle fit together. The main goal is
to bring researchers and engineers to the forefront of the research frontier
in the new era that started a few years ago and is expected to continue
for decades. We believe that academics, researchers, practitioners, and
students will benefit from this book and will be prepared to tackle the big
wave of heterogeneous computing that is here to stay.