Skip to content

Commit 1b5d357

Browse files
committed
More work.
1 parent 4275dad commit 1b5d357

File tree

4 files changed

+226
-0
lines changed

4 files changed

+226
-0
lines changed

main.tex

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,71 @@ \section{Array Operations}
572572
functional languages, they have implicitly parallel semantics, and
573573
some restrictions to preserve those semantics.
574574

575+
In addition to the array combinators, there are constructs for
576+
\textit{constructing} arrays. We already demonstrated literal arrays.
577+
Additionally, there is \texttt{iota}, which creates an array of a
578+
range of integers starting from zero:
579+
580+
\begin{lstlisting}
581+
iota 10 == [0,1,2,3,4,5,6,7,8,9]
582+
\end{lstlisting}
583+
584+
The name \texttt{iota} comes from APL, one of the earliest array
585+
programming languages, and is supposed to be mnemonic for creating
586+
\textit{index spaces} of arrays. Put another way, \texttt{iota n}
587+
produces an array of valid indices into an array of size \texttt{n}.
588+
589+
The \texttt{replicate} construct is used to create an array of some
590+
size, with all elements having the same given value:
591+
592+
\begin{lstlisting}
593+
replicate 3 42 == [42,42,42]
594+
\end{lstlisting}
595+
596+
We can use \texttt{concat} to combine several arrays:
597+
598+
\begin{lstlisting}
599+
concat (iota 2) ([1,2,3]) (replicate 4 1) ==
600+
[0,1,1,2,3,1,1,1,1i32]
601+
\end{lstlisting}
602+
603+
Note that the parentheses around the literal array are necessary - if
604+
they were not present, this expression would be parsed as an attempt
605+
to index the expression \texttt{iota 2} using \texttt{[1,2,3]} as the
606+
indices. This would of course result in a type error.
607+
608+
We can use \texttt{zip} to transform $n$ arrays to a single array of
609+
$n$-tuples:
610+
611+
\begin{lstlisting}
612+
zip [1,2,3] [true,false,true] [7.0,8.0,9.0] ==
613+
[(1,true,7.0),(2,false,8.0),(3,true,9.0)]
614+
\end{lstlisting}
615+
616+
That the input arrays may have different types. We can use
617+
\texttt{unzip} to perform the inverse transformation:
618+
619+
\begin{lstlisting}
620+
unzip [(1,true,7.0),(2,false,8.0),(3,true,9.0)] ==
621+
([1,2,3], [true,false,true], [7.0,8.0,9.0])
622+
\end{lstlisting}
623+
624+
Be aware that \texttt{zip} requires all of the input arrays to have
625+
the same size. Transforming between arrays of tuples and tuples of
626+
arrays is common in Futhark programs, as many array operations accept
627+
only one array as input. Due to a clever implementation technique,
628+
\texttt{zip} and \texttt{unzip} also have no runtime cost (no copying
629+
or allocation whatsoever), so you should not shy away from using them
630+
out of efficiency concerns.\footnote{This is enabled via storing all
631+
arrays in ``unzipped'' form. That is, at runtime, arrays of tuples
632+
do not exist, but have always been decomposed into multiple arrays.
633+
This is a common practice for high-performance computing, usually
634+
called ``structs of arrays'' versus ``arrays of structs'', and
635+
serves to permit memory access patterns more friendly to vectorised
636+
operations.}
637+
638+
\subsection{Map}
639+
575640
The simplest SOAC is probably \texttt{map}. It takes two arguments: a
576641
function and an array. The function argument can be a function name,
577642
or an anonymous function using \texttt{fn} syntax. The function is
@@ -613,6 +678,33 @@ \section{Array Operations}
613678
map (+) [1,2,3] [4,5,6] == [5,7,9]
614679
\end{lstlisting}
615680

681+
Be careful when writing \texttt{map} expressions where the function
682+
returns an array. Futhark requires regular arrays, so a map with
683+
\texttt{iota} is unlikely to go well:
684+
685+
\begin{lstlisting}
686+
map (fn n => iota n) ns
687+
\end{lstlisting}
688+
689+
Unless the array \texttt{ns} consisted of identical values, the
690+
program would fail at runtime.
691+
692+
We can use \texttt{map} and \texttt{iota} to duplicate many other
693+
language constructs. For example, if we have two arrays
694+
\texttt{xs:[n]int} and \texttt{ys:[m]int}---that is, two integer
695+
arrays of sizes \texttt{n} and \texttt{m}---we can concatenate them
696+
using:
697+
698+
\lstinputlisting[firstline=2]{src/concat_with_map.fut}
699+
700+
However, it is not a good idea to write code like this, as it hinders
701+
the compiler from using high-level properties to do optimisation.
702+
Using \texttt{map}s over \texttt{iota}s with explicit indexing is
703+
usually only necessary when solving complicated irregular problems
704+
that cannot be represented directly.
705+
706+
\subsection{Scan and Reduce}
707+
616708
While \texttt{map} is an array transformer, the \texttt{reduce} SOAC
617709
is an array aggregator: it uses some function of type \texttt{t -> t
618710
-> t} to combine the elements of an array of type \texttt{[]t} to a
@@ -678,6 +770,37 @@ \section{Array Operations}
678770
Several examples are discussed in
679771
Chapter~\ref{chap:parallel-algorithms}.
680772

773+
\subsection{Filtering}
774+
775+
We have seen \texttt{map}, which permits us to change all the elements
776+
of an array. We have seen \texttt{reduce}, which lets us collapse all
777+
the elements of an array. But we still need something that lets us
778+
remove some, but not all, of the elements of an array. This SOAC is
779+
\texttt{filter}, which behaves much like a filter in any other
780+
functional language:
781+
782+
\begin{lstlisting}
783+
filter (<3) [1,5,2,3,4] == [1,2]
784+
\end{lstlisting}
785+
786+
The use of \texttt{filter} is mostly straightforward, but there are
787+
some patterns that may appear subtle at first glance. For example,
788+
how do we find the \textit{indices} of all nonzero entries in an array
789+
of integers? Finding the values is simple enough:
790+
791+
\begin{lstlisting}
792+
filter (fn x => x != 0) [0,5,2,0,1] ==
793+
[5,2,1]
794+
\end{lstlisting}
795+
796+
But what are the corresponding indices? We can solve this using a
797+
combination of \texttt{zip}, \texttt{filter}, and \texttt{unzip}:
798+
799+
\lstinputlisting[firstline=2]{src/indices_of_nonzero.fut}
800+
801+
Be aware that \texttt{filter} is a somewhat expensive SOAC,
802+
corresponding roughly to a \texttt{scan} plus a \texttt{map}.
803+
681804
\section{Sequential Loops}
682805
\label{sec:sequential-loops}
683806

src/concat_with_map.fut

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fun main (xs: [n]int) (ys: [m]int): []int =
2+
map (fn i => if i < n then xs[i] else ys[i-n])
3+
(iota (n+m))

src/indices_of_nonzero.fut

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fun indices_of_nonzero(xs: [n]int): []int =
2+
let xs_and_is = zip xs (iota n)
3+
let xs_and_is' = filter (fn (x,_) => x != 0) xs_and_is
4+
let (_, is') = unzip xs_and_is'
5+
in is'

src/visualise.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python
2+
#
3+
# A generic visualiser for a Futhark program, using Pygame. You can
4+
# use this to quickly test out things by modifying the
5+
# visualize_model.fut program, or use it as a starting point for a new
6+
# visualization.
7+
8+
import visualise_model
9+
10+
import numpy
11+
import pygame
12+
import time
13+
import sys
14+
15+
preferred_size = (1000,1000) # Set to None for full-screen.
16+
17+
# Pygame initialisation.
18+
pygame.init()
19+
pygame.display.set_caption('Generic Visualization')
20+
if preferred_size != None:
21+
width, height = preferred_size
22+
else:
23+
width = screen.get_width()
24+
height = screen.get_height()
25+
size = (width, height)
26+
print size
27+
screen = pygame.display.set_mode(size)
28+
surface = pygame.Surface(size)
29+
font = pygame.font.Font(None, 36)
30+
pygame.key.set_repeat(500, 50)
31+
32+
# Utility function for printing text.
33+
def showText(what, where):
34+
text = font.render(what, 1, (100, 100, 255))
35+
screen.blit(text, where)
36+
37+
# Create a class instance corresponding to the Futhark program. This
38+
# object exposes three methods (corresponding to entry points in the
39+
# Futhark program):
40+
#
41+
# * 'initial_state', which returns some state.
42+
#
43+
# * 'advance', which takes as argument a state, an integer
44+
# representing some setting, and returns a new state.
45+
#
46+
# * 'render', which takes as arguments the size of the viewport, the
47+
# state, the setting, and returns an array of pixel data to be
48+
# blitted to the screen.
49+
#
50+
# If you create new programs whose state is a tuple, you will need to
51+
# modify these to accept more parameters.
52+
model = visualise_model.visualise_model()
53+
54+
state = model.initial_state()
55+
56+
# This variable is modified on the Python side in response to keyboard input.
57+
setting = 10
58+
59+
def render(model_time):
60+
start = time.time()
61+
# The .get() is necessary to retrieve a CPU-side Numpy array, as
62+
# expected by the blit_array() method we use below. It is likely
63+
# that most of the time is spent copying this array back to main
64+
# memory (only to then push it back to the GPU for rendering...).
65+
frame = model.render(width, height, state, setting).get()
66+
end = time.time()
67+
render_time = (end - start) * 1000
68+
69+
pygame.surfarray.blit_array(surface, frame)
70+
screen.blit(surface, (0, 0))
71+
72+
message = "Advancing took %.2fms; rendering took %.2fms (state setting: %d)" % (model_time, render_time, setting)
73+
showText(message, (10,10))
74+
75+
pygame.display.flip()
76+
77+
while True:
78+
# First, advance the state of our model. Measure how long that takes.
79+
start = time.time()
80+
state = model.advance(state, setting)
81+
end = time.time()
82+
83+
# Then visualise it.
84+
render((end - start) * 1000)
85+
86+
# Now wait for and handle events.
87+
for event in pygame.event.get():
88+
if event.type == pygame.QUIT:
89+
# Our window was closed.
90+
sys.exit()
91+
elif event.type == pygame.KEYDOWN:
92+
if event.key == pygame.K_UP:
93+
setting += 1
94+
if event.key == pygame.K_DOWN:
95+
setting -= 1

0 commit comments

Comments
 (0)