------------------------ Sensitivity approach to CREW time lower bounds: Consider a desired n-ary function f and a prospective CREW algorithm to compute it. On each input x = (x1, ..., xn), ``some things'' ``depend on'' x3 (for example), and some do not. (Other wording: ``x3 *affects* some things,'' etc.) Sample ``things'': value of the desired function, processor 7's state at time 13, the content of shared memory cell 12 at time 5. Rigorous meaning of ``depends on x3'': Switching to some x3' can *change* it. Definition: sensitivity(f) = maximum (over input n-tuples x) number of components on which f depends. Our example: On all-0 input, OR_n depends on *every* component. (On other inputs, it depends only on 0 or 1 of them, though.) Therefore, sensitivity(OR_n) = n. ------------------------ Theorem [Shaw? Actually, Bublitz et al.]: If a *symmetric* Boolean function of n variables is nonconstant, then its sensitivity is at least ceiling((n+1)/2). But here are some nonsymmetric properties to consider: 1. 1 0^i 1 is a (contiguous) substring, for i = sqrt(n). 2. The first lg n bits tell (in binary) a bit position that is the value of the function. What are the sensitivities? ------------------------ On worst-case x, therefore, a correct algorithm's ``answer location'' (either in shared memory, or in some processor's state) at time t_final must also depend on sensitivity(f) many components. (O.w., what? Some switch could ``fool'' the algorithm.) On the other hand, we show an upper bound B(t) on how many components can affect something after just t steps of computation. t_final will have to be so large that B(t) is at least sensitivity(f). What is our bound? B(t) = b^t, for some constant b. So we'll get t_final = Omega(log(sensitivity(f))). For OR_n, therefore, we'll get t_final = Omega(log n). (What is b? 2 + sqrt(3), with care; but we'll settle for 7.) ------------------------ Caveats 1. This upper bound (and our lower-bound consequence) will indeed have to depend on the EW constraint--for CRCW, *every* component can affect a storage location, after just *one* step! 2. Even our function OR_n can be computed faster than you might think the ``pointer jumping'' approach allows. You might think it has to take an entire read-process-write cycle to increase the ``reaches'' of the ``partial sums''; but, in fact, each separate read *or* write can do so by itself! Naive approach: Maintain the partial sums in the input array, doubling the reaches in each read-process-write cycle. (This works for *any* associative operator--not just OR.) Surprising approach: Maintain *two* partial sums, one in the public input array, and the other in local private memory. (No sweat initially--the local reaches are 0, and the public reaches are 1.) On a read, you can easily increase your local reach to the sum of the two reaches. And then on the subsequent write, you can also increase each public reach to the sum of the two current reaches (the old public one and the new private one). The latter step involves a trick specific to the OR operator: You don't need a read! If a contribution to an enlarged partial sum is 1, then you can write 1 regardless of what the other operand is; if a contribution is 0, then you can simply *not write*, since the result will *be* the other operand! Result: In a read-process-write cycle, you can get *more* than a single doubling of reach--you can take *two* ``Fibonacci'' steps. (In the long run, this amounts to multiplication by about 2.618 (1 + sqrt(5), which is comfortably more than 2!).) private 0 1 3 8 21 55 144 ... public 1 2 5 13 34 89 233 ... (Get the limit ratio by solving 1 + r = r^2.) ------------------------ How prove B(t) = b^t works? Induction on t. So our main lemma will be a constant multiplicative bound on the *growth* of dependence in any single step (read-process-write). Suppose B is the bound we already have. (This applies to *every* input, *every* memory location, every processor's state (and even every CREW algorithm).) After the next read-process-write cycle, we want the bound to be at most bB, where b is a constant. Argument for a *processor*, P: (This will be determined entirely by the read phase.) How can xi now affect P's state? 1. Already *before* the read. (At most B such.) 2. Already affected the memory location it is reading. (At most B such.) Therefore, get bound 2B. Argument for a *memory location*, M: Obvious case: Some processor P now writes M. (Unique, because of EW.) How can xi now affect M? *Already* affected P, *before* the write. (At most 2B such--this includes the already analyzed read phase.) Therefore, again get bound 2B. Easily overlooked case: *No* processor writes M! How can xi now affect M? 1. Already *before* now, as usual. (At most B such.) 2. *Change* can *now* cause some processor to write M. (At most r such, say.) Remaining issue: How bound r? Consider the r changes. Consider any pair of them that cause *distinct* processors to write M. Note that there are many such pairs: Since at most 2B of the changes can affect any particular processor (including in the already analyzed read phase), at most 2B can cause any particular processor to write M. So there are at least r(r-2B)/2 such pairs. Consider making *both* changes, in the two possible orders. We know we can't cause *both* processors to write, so at least one of the second changes must also change the state of the other change's writer. But our induction hypothesis, in its generality, includes a bound on the number of such possible state changes --2B for each of the r *first* changes we consider. (A picture might help.) Conclusion: r(r-2B)/2 must not exceed 2rB; i.e., r is at most 6B. Therefore, the total number of components that affect M is (at most B + r, and hence) at most 7B. Note that, without the final, easily overlooked case, the lower bound for OR_n (e.g.) *would* have been the naively expected lg n. For CROW (OW = ``owner write''), this may well be workable.