]> mj.ucw.cz Git - saga.git/blob - ram.tex
Intro on RAM data structures.
[saga.git] / ram.tex
1 \ifx\endpart\undefined
2 \input macros.tex
3 \fi
4
5 \chapter{Fine Details of Computation}
6
7 \section{Models and machines}
8
9 Traditionally, computer scientists use a~variety of computational models
10 for a~formalism in which their algorithms are stated. If we were studying
11 NP-completeness, we could safely assume that all the models are equivalent,
12 possibly up to polynomial slowdown which is negligible. In our case, the
13 differences between good and not-so-good algorithms are on a~much smaller
14 scale. In this chapter, we will replace the usual ``yardsticks'' by a~micrometer,
15 state our computation models carefully and develop a repertoire of basic
16 data structures taking advantage of the fine details of the models.
17
18 We would like to keep the formalism close enough to the reality of the contemporary
19 computers. This rules out Turing machines and similar sequentially addressed
20 models, but even the remaining models are subtly different. For example, some of them
21 allow indexing of arrays in constant time, while the others have no such operation
22 and arrays have to be emulated with pointer structures, requiring $\Omega(\log n)$
23 time to access a~single element of an~$n$-element array. It is hard to say which
24 way is superior --- while most ``real'' computers have instructions for constant-time
25 indexing, it seems to be physically impossible to fulfil this promise regardless of
26 the size of memory. Indeed, at the level of logical gates, the depth of the
27 actual indexing circuits is logarithmic.
28
29 In recent decades, most researchers in the area of combinatorial algorithms
30 have been considering two computational models: the Random Access Machine and the Pointer
31 Machine. The former one is closer to the programmer's view of a~real computer,
32 the latter one is slightly more restricted and ``asymptotically safe.''
33 We will follow this practice and study our algorithms in both models.
34
35 \para
36 The \df{Random Access Machine (RAM)} is not a~single model, but rather a~family
37 of closely related models, sharing the following properties.
38 (See Cook and Reckhow \cite{cook:ram} for one of the usual formal definitions
39 and Hagerup \cite{hagerup:wordram} for a~thorough description of the differences
40 between the RAM variants.)
41
42 The \df{memory} of the model is represented by an~array of \df{memory cells}
43 addressed by non-negative integers, each of them containing a~single non-negative integer.
44 The \df{program} is a~sequence of \df{instructions} of two basic kinds: calculation
45 instructions and control instructions.
46
47 \df{Calculation instructions} have two source arguments and one destination
48 argument, each \df{argument} being either an~immediate constant (not available
49 as destination), a~directly addressed memory cell (specified by its number)
50 or an~indirectly addressed memory cell (its address is stored in a~directly
51 addressed memory cell).
52
53 \df{Control instructions} include branches (to a~specific instruction in
54 the program), conditional branches (e.g., jump if two arguments specified as
55 in the calculation instructions are equal) and an~instruction to halt the program.
56
57 At the beginning of the computation, the memory contains the input data
58 in specified memory cells and arbitrary values in all other cells.
59 Then the program is executed one instruction at a~time. When it halts,
60 specified memory cells are interpreted as the program's output.
61
62 \para\id{wordsize}%
63 In the description of the RAM family, we have omitted several properties
64 on~purpose, because different members of the family define them differently.
65 These are: the size of the available integers, the time complexity of a~single
66 instruction, the space complexity of a~single memory cell and the repertoire
67 of operations available in calculation instructions.
68
69 If we impose no limits on the magnitude of the numbers and we assume that
70 arithmetic and logical operations work on them in constant time, we get
71 a~very powerful parallel computer --- we can emulate an~exponential number
72 of parallel processors using arithmetics and suddenly almost everything can be
73 computed in constant time, modulo encoding and decoding of input and output.
74 Such models are unrealistic and there are two basic possibilities how to
75 avoid this behavior:
76
77 \numlist\ndotted
78 \:Keep unbounded numbers, but increase costs of instructions: each instruction
79   consumes time proportional to the number of bits of the numbers it processes,
80   including memory addresses. Similarly, space usage is measured in bits,
81   counting not only the values, but also the addresses of the respective memory
82   cells.
83 \:Place a~limit on the size of the numbers ---define the \df{word size~$W$,}
84   the number of bits available in the memory cells--- and keep the cost of
85   of instructions and memory cells constant. The word size must not be constant,
86   since we can address only~$2^W$ cells of memory. If the input of the algorithm
87   is stored in~$N$ cells, we need~$W\ge\log N$ just to be able to read the input.
88   On the other hand, we are interested in polynomial-time algorithms only, so $\Theta(\log N)$-bit
89   numbers should be sufficient. In practice, we pick~$w$ to be the larger of
90   $\Theta(\log N)$ and the size of integers used in the algorithm's input and output.
91 \endlist
92
93 Both restrictions easily avoid the problems of unbounded parallelism. The first
94 choice is theoretically cleaner and Cook et al.~show nice correspondences to the
95 standard complexity classes, but the calculations of time and space complexity tend
96 to be somewhat tedious. What more, when compared with the RAM with restricted
97 word size, the complexities are usually exactly $\Theta(w)$ times higher.
98 This does not hold in general (consider a~program which uses many small numbers
99 and $\O(1)$ large ones), but it is true for the algorithms we are interested in.
100 Therefore we will always assume that the operations have unit cost and we make
101 sure that all numbers are limited by the available word size.
102
103 \para
104 As for the choice of RAM operations, the following three instruction sets are often used:
105
106 \itemize\ibull
107 \:\df{Word-RAM} --- allows the ``C-language operators'', i.e., addition,
108   subtraction, multiplication, division, remainder, bitwise {\sc and,} {\sc or,} exclusive
109   {\sc or} ({\sc xor}), negation ({\sc not}) and bitwise shifts ($\ll$ and~$\gg$).
110 \:\df{${\rm AC}^0$-RAM} --- allows all operations from the class ${\rm AC}^0$, i.e.,
111   those computable by constant-depth polynomial-size boolean circuits with unlimited
112   fan-in and fan-out. This includes all operations of the Word-RAM except for multiplication,
113   division and remainders, and also many other operations like computing the Hamming
114   weight (number of bits set in a~given number).
115 \:Both restrictions at once.
116 \endlist
117
118 Thorup discusses the usual techniques employed by RAM algorithms in~\cite{thorup:aczero}
119 and he shows that they work on both Word-RAM and ${\rm AC}^0$-RAM, but the combination
120 of the two restrictions is too weak. On the other hand, the intersection of~${\rm AC}^0$
121 with the instruction set of modern processors (e.g., adding some floating-point
122 operations and multimedia instructions available on the Intel's Pentium~4~\cite{intel:pentium}) is already strong enough.
123
124 We will therefore use the Word-RAM instruction set, mentioning differences from the
125 ${\rm AC}^0$-RAM where necessary.
126
127 \nota
128 When speaking of the \df{RAM,} we implicitly mean the version with numbers limited
129 by a~specified word size of $W$~bits, unit cost of operations and memory cells and the instruction
130 set of the Word-RAM. This corresponds to the usage in recent algorithmic literature,
131 although the authors rarely mention the details. In some cases, a~non-uniform variant
132 of the Word-RAM is considered as well (e.g., in~\cite{hagerup:dd}):
133
134 \defn
135 A~Word-RAM is called \df{weakly non-uniform,} if it is equipped with $\O(1)$-time
136 access to a~constant number of word-sized constants, which depend only on the word
137 size. These are called \df{native constants} and they are available in fixed memory
138 cells when the program starts. (By analogy with the high-level programming languages,
139 these constants can be thought of as computed at ``compile time.'')
140
141 \para
142 The \df{Pointer Machine (PM)} also does not have any well established definition. The
143 various kinds of pointer machines are mapped by Ben-Amram in~\cite{benamram:pm},
144 but unlike the RAM's they turn out to be equivalent up to constant slowdown.
145 Our definition will be closely related to the \em{linking automaton} proposed
146 by Knuth in~\cite{knuth:fundalg}, we will only adapt it to use RAM-like
147 instructions instead of an~opaque control unit.
148
149 The PM works with two different types of data: \df{symbols} from a~finite alphabet
150 and \df{pointers}. The memory of the machine consists of a~fixed amount of \df{registers}
151 (some of them capable of storing a~single symbol, each of the others holds a~single pointer)
152 and an~arbitrary amount of \df{cells}. The structure of all cells is the same: each of them
153 again contains a~fixed number of fields for symbols and pointers. Registers can be addressed
154 directly, the cells only via pointers --- either by using a~pointer stored in a~register,
155 or in a~cell pointed to by a~register (longer chains of pointers cannot be followed in
156 constant time).
157
158 We can therefore view the whole memory as a~directed graph, whose vertices
159 correspond to the cells (the registers are stored in a~single special cell).
160 The outgoing edges of each vertex correspond to pointer fields and they are
161 labelled with distinct labels drawn from a~finite set. In addition to that,
162 each vertex contains a~fixed amount of symbols. The program can directly access
163 vertices within distance~2 from the register vertex.
164
165 The program is a~sequence of instructions of the following kinds:
166
167 \itemize\ibull
168 \:\df{symbol instructions,} which read a~pair of symbols, apply an~arbitrary
169   function on them and write the result to a~symbol register or field;
170 \:\df{pointer instructions} for assignment of pointers to pointer registers/fields
171   and for creation of new memory cells (a~pointer to the new cell is assigned
172   immediately);
173 \:\df{control instructions} --- similarly to the RAM; conditional jumps can decide
174   on~arbitrary unary relations on symbols and compare pointers for equality.
175 \endlist
176
177 Time and space complexity are defined in the straightforward way: all instructions
178 have unit cost and so do all memory cells.
179
180 Both input and output of the machine are passed in the form of a~linked structure
181 pointed to by a~designated register. For example, we can pass graphs back and forth
182 without having to encode them as strings of numbers or symbols. This is important,
183 because with the finite alphabet of the~PM, all symbolic representations of graphs
184 require super-linear space and therefore also time.
185
186 \para
187 Compared to the RAM, the PM lacks two important capabilities: indexing of arrays
188 and arithmetic instructions. We can emulate both with poly-logarithmic slowdown,
189 but it will turn out that they are rarely needed in graph algorithms. We are
190 also going to prove that the RAM is strictly stronger, so we will prefer to
191 formulate our algorithms in the PM model and use RAM only when necessary.
192
193 \thm
194 Every program for the Word-RAM with word size~$W$ can be translated to a~program
195 computing the same\foot{Given a~suitable encoding of inputs and outputs, of course.}
196 on the~PM with $\O(W^2)$ slowdown. If the RAM program does not
197 use multiplication, division and remainder operations, $\O(W)$~slowdown
198 is sufficient.
199
200 \proofsketch
201 Represent the memory of the RAM by a~balanced binary search tree or by a~radix
202 trie of depth~$\O(W)$. Values are encoded as~linked lists of symbols pointed
203 to by the nodes of the tree. Both direct and indirect accesses to the memory
204 can therefore be done in~$\O(W)$ time. Use standard algorithms for arithmetic
205 on big numbers: $\O(W)$ per operation except for multiplication, division and
206 remainders which take $\O(W^2)$.\foot{We could use more efficient arithmetic
207 algorithms, but the quadratic bound it good enough for our purposes.}
208 \qed
209
210 \FIXME{Add references.}
211
212 \thm
213 Every program for the PM running in polynomial time can be translated to a~program
214 computing the same on the Word-RAM with only $\O(1)$ slowdown.
215
216 \proofsketch
217 Encode each cell of the PM's memory to $\O(1)$ integers. Store the encoded cells to
218 memory of the RAM sequentially and use memory addresses as pointers. As the symbols
219 are finite and there is only a~polynomial number of cells allocated during execution
220 of the program, $\O(\log N)$-bit integers suffice.
221 \qed
222
223 \para
224 There are also \df{randomized} versions of both machines. These are equipped
225 with an~additional instruction for generating a~single random bit. The standard
226 techniques of design and analysis of randomized algorithms then apply (see for
227 example Motwani and Raghavan~\cite{motwani:randalg}).
228
229 \FIXME{Consult sources. Does it make more sense to generate random words at once on the RAM?}
230
231 \rem
232 There is one more interesting machine: the \df{Immutable Pointer Machine} (see
233 the description of LISP machines in \cite{benamram:pm}). It differs from the
234 ordinary PM by the inability to modify existing memory cells. Only the contents
235 of the registers are allowed to change. All cell modifications thus have to
236 be performed by creating a~copy of the particular cell with some fields changed.
237 This in turn requires the pointers to the cell to be updated, possibly triggering
238 a~cascade of cell copies. For example, when a~node of a~binary search tree is
239 updated, all nodes on the path from that node to the root have to be copied.
240
241 One of the advantages of this model is that the states of the machine are
242 persistent --- it is possible to return to a~previously visited state by recalling
243 the $\O(1)$ values of the registers (everything else could not have changed
244 since that time) and ``fork'' the computations. This corresponds to the semantics
245 of pure functional languages, e.g., Haskell.
246
247 Unless we are willing to accept a~logarithmic penalty in execution time and space
248 (in fact, our emulation of the Word-RAM on the PM can be easily made immutable),
249 the design of efficient algorithms for the immutable PM requires very different
250 techniques. Therefore, we will concentrate on the imperative models instead
251 and refer the interested reader to the thorough treatment of purely functional
252 data structures in the Okasaki's monograph~\cite{okasaki:funcds}.
253
254 %--------------------------------------------------------------------------------
255
256 \section{Bucket sorting and contractions}\id{bucketsort}%
257
258 The Contractive Bor\o{u}vka's algorithm (\ref{contbor}) needed to contract a~given
259 set of edges in the current graph and flatten it afterwards, all in time $\O(m)$.
260 We have spared the technical details for this section and they will be useful
261 in further algorithms, too.
262
263 As already suggested, the contractions can be performed by building an~auxiliary
264 graph and finding its connected components, so we will take care of the flattening
265 only.
266
267 \para
268 On the RAM, it is sufficient to sort the edges lexicographically (each edge viewed
269 as an~ordered pair of vertex identifiers with the smaller of the identifiers placed
270 first). We can do that by a two-pass bucket sort with~$n$ buckets corresponding
271 to the vertex identifiers.
272
273 However, there is a~catch in this. Suppose that we use the standard representation
274 of graphs as adjacency lists whose heads are stored in an array indexed by vertex
275 identifiers. When we contract and flatten the graph, the number of vertices decreases,
276 but if we inherit the original vertex identifiers, the arrays will still have the
277 same size. Hence we spend a~super-linear amount of time on scanning the arrays,
278 most of the time skipping unused entries.
279
280 To avoid this, we just renumber the vertices after each contraction to component
281 identifiers from the auxiliary graph and we create a~new vertex array. This way,
282 the representation of the graph will be linear with respect to the size of the
283 current graph.
284
285 \para
286 The pointer representation of graphs does not suffer from sparsity as the vertices
287 are always identified by pointers to per-vertex structures. Each such structure
288 then contains all attributes associated with the vertex, including the head of the
289 adjacency list. However, we have to find a~way how to perform bucket sorting
290 without arrays.
291
292 We will keep a~list of the per-vertex structures which defines the order on~vertices.
293 Each such structure will contain a~pointer to the head of the corresponding bucket,
294 again stored as a~list. Putting an~edge to a~bucket can be done in constant time then,
295 scanning all~$n$ buckets takes $\O(n+m)$ time.
296
297 \FIXME{Add an example of locally determined orders, e.g., tree isomorphism?}
298
299 %--------------------------------------------------------------------------------
300
301 \section{Data structures on the RAM}
302
303 There is a~lot of data structures designed specifically for the RAM, taking
304 advantage of both indexing and arithmetic. In many cases, they surpass the known
305 lower bounds for the same problem on the~PM and often achieve constant time
306 per operation, at least when either the magnitude of the values or the size of
307 the data structure are suitably bounded.
308
309 A~classical result of this type are the trees of van Emde Boas~\cite{boas:vebt},
310 which represent a~subset of the integers $\{0,\ldots,U-1\}$, allowing insertion,
311 deletion and order operations (minimum, maximum, successor etc.) in time $\O(\log\log U)$,
312 regardless of the size of the subset. If we plug this structure in the Jarn\'\i{}k's
313 algorithm (\ref{jarnik}), replacing the heap, we immediately get an~algorithm
314 for finding the MST in integer-weighted graphs in time $\O(m\log\log w_{max})$,
315 where $w_{max}$ is the maximum weight. We will show later that it is even
316 possible to achieve linear time complexity for arbitrary integer weights.
317
318 A~real breakthrough has been made by Fredman and Willard, who introduced
319 the Fusion trees~\cite{fw:fusion} which again perform membership and predecessor
320 operation on a~set of integers, but this time with complexity $\O(\log_W n)$
321 per operation on a~Word-RAM with $W$-bit words. This of course assumes that
322 each element of the set fits in a~single word. As $W$ is at least~$\log n$,
323 the operations take $\O(\log n/\log\log n)$ and we are able to sort integers
324 in time~$o(n\log n)$. This was a~beginning of a~long sequence of faster and
325 faster integer sorting algorithms, culminating with the work by Thorup and Han.
326 They have improved the time complexity to $\O(n\log\log n)$ deterministically~\cite{han:detsort}
327 and expected $\O(n\sqrt{\log\log n})$ for randomized algorithms~\cite{hanthor:randsort},
328 both in linear space.
329
330 Despite the recent progress, the corner-stone of most RAM data structures
331 is still the representation of data structures by integers introduced by Fredman
332 and Willard and it will also form a~basis for the rest of this chapter.
333
334 \FIXME{Add more history.}
335
336 %--------------------------------------------------------------------------------
337
338 \section{Bits and vectors}
339
340 \endpart