CS 70 Discrete Mathematics for CS

Fall 2004  Rao  Lecture 15

Two Killer Applications

In this lecture, we will see two “killer apps” of elementary probability in Computer Science.

1. Suppose a hash function distributes keys evenly over a table of size n. How many (randomly chosen) keys can we hash before the probability of a collision exceeds (say) \frac{1}{2}?

2. Consider the following simple load balancing scenario. We are given m jobs and n machines; we allocate each job to a machine uniformly at random and independently of all other jobs. What is a likely value for the maximum load on any machine?

As we shall see, both of these questions can be tackled by an analysis of the balls-and-bins probability space which we have already encountered.

Application 1: Hash functions

As you may recall, a hash table is a data structure that supports the storage of sets of keys from a (large) universe U (say, the names of all 250m people in the US). The operations supported are adding a key to the set, deleteing a key from the set, and testing membership of a key in the set. The hash function h maps U to a table T of modest size. To add a key x to our set, we evaluate h(x) (i.e., apply the hash function to the key) and store x at the location h(x) in the table T. All keys in our set that are mapped to the same table location are stored in a simple linked list. The operations delete and member are implemented in similar fashion, by evaluating h(x) and searching the linked list at h(x). Note that the efficiency of a hash function depends on having only few \underline{collisions} — i.e., keys that map to the same location. This is because the search time for delete and member operations is proportional to the length of the corresponding linked list.

The question we are interested in here is the following: suppose our hash table T has size n, and that our hash function h distributes U evenly over T.^{1} Assume that the keys we want to store are chosen uniformly at random and independently from the universe U. What is the largest number, m, of keys we can store before the probability of a collision reaches \frac{1}{2}?

Let’s begin by seeing how this problem can be put into the balls and bins framework. The balls will be the m keys to be stored, and the bins will be the n locations in the hash table T. Since the keys are chosen uniformly and independently from U, and since the hash function distributes keys evenly over the table, we can see each key (ball) as choosing a hash table location (bin) uniformly and independently from T. Thus the probability space corresponding to this hashing experiment is exactly the same as the balls and bins space.

We are interested in the event A that there is no collision, or equivalently, that all m balls land in different bins. Clearly Pr[A] will decrease as m increases (with n fixed). Our goal is to find the largest value of m such that Pr[A] remains above \frac{1}{2}. [Note: Really we are looking at different sample spaces here, one for each value of m. So it would be more correct to write Pr_{m} rather than just Pr, to make clear which sample space we are talking about. However, we will omit this detail.]

1I.e., |U| = αn (the size of U is an integer multiple α of the size of T), and for each y ∈ T, the number of keys x ∈ U for which h(x) = y is exactly α.

CS 70, Fall 2004, Lecture 15 1

Let’s fix the value of m and try to compute Pr[A]. Since our probability space is uniform (each outcome has probability \frac{1}{n^{m}}), it’s enough just to count the number of outcomes in A. In how many ways can we arrange m balls in n bins so that no bin contains more than one ball? Well, there are n places to put the first ball, then n − 1 remaining places for the second ball (since it cannot go in the same bin as the first), n − 2 places for the third ball, and so on. Thus the total number of such arrangements is

n × (n − 1) × (n − 2) × ··· × (n − m + 2) × (n − m + 1).

This formula is valid as long as m ≤ n: if m > n then clearly the answer is zero. From now on, we’ll assume that m ≤ n.

Now we can calculate the probability of no collision:

Pr[A] = \frac{n(n − 1)(n − 2)...(n − m + 1)}{n^{m}}
= \frac{n}{n}× \frac{n − 1}{n} × \frac{n − 2}{n} × ··· × \frac{n − m + 1}{n}
= ( 1 − \frac{1}{n}) × ( 1 − \frac{2}{n}) × ··· × ( 1 − \frac{m − 1}{n} ) .  (1)

Before going on, let’s pause to observe that we could compute Pr[A] in a different way, as follows. View the probability space as a sequence of choices, one for each ball. For 1 ≤ i ≤ m, let A_{i} be the event that the ith ball lands in a different bin from balls 1,2,...,i − 1. Then

Pr[A] = Pr[⋂ _{i=1}^{n} A_{i}] = Pr[A_{1}] × Pr[A_{2}|A_{1}] × Pr[A_{3}|A_{1}∩ A_{2}] × ··· × Pr[A_{m}|⋂ _{i=1}^{m−1} A_{i}]
= 1 × \frac{n − 1}{n} × \frac{n − 2}{n} × ··· × \frac{n − m + 1}{n}
= ( 1 − \frac{1}{n}) × ( 1 − \frac{2}{n}) × ··· × ( 1 − \frac{m − 1}{n} ) .

Fortunately, we get the same answer as before! [You should make sure you see how we obtained the conditional probabilities in the second line above. For example, Pr[A_{3}|A_{1} ∩ A_{2}] is the probability that the third ball lands in a different bin from the first two balls, given that those two balls also landed in different bins. This means that the third ball has n − 2 possible bin choices out of a total of n.]

Essentially, we are now done with our problem: equation (1) gives an exact formula for the probability of no collision when m keys are hashed. All we need to do now is plug values m = 1,2,3,... into (1) until we find that Pr[A] drops below \frac{1}{2}. The corresponding value of m (minus 1) is what we want.

But this is not really satisfactory: it would be much more useful to have a formula that gives the “critical” value of m directly, rather than having to compute Pr[A] for m = 1,2,3,.... Note that we would have to do this computation separately for each different value of n we are interested in: i.e., whenever we change the size of our hash table.

So what remains is to “turn equation (1) around”, so that it tells us the value of m at which Pr[A] drops below \frac{1}{2}. To do this, let’s take logs: this is a good thing to do because it turns the product into a sum, which is easier to handle. We get

ln(Pr[A]) = ln( 1 − \frac{1}{n}) + ln( 1 − \frac{2}{n}) + ··· + ln( 1 − \frac{m − 1}{n} ) ,  (2)

where “ln” denotes natural (base e) logarithm. Now we can make use of a standard approximation for logarithms: namely, if x is small then ln(1 − x) ≈ −x. This comes from the Taylor series expansion

ln(1 − x) = −x − \frac{x^{2}}{2} − \frac{x^{3}}{3} − ....

So by replacing ln(1−x) by −x we are making an error of at most (\frac{x^{2}}{2} + \frac{x^{3}}{3} +··· ), which is at most 2x^{2} when x ≤ \frac{1}{2}. In other words, we have

−x ≥ ln(1 − x) ≥ −x − 2x^{2}.

And if x is small then the error term 2x^{2} will be much smaller than the main term −x. Rather than carry around the error term 2x^{2} everywhere, in what follows we’ll just write ln(1 − x) ≈ −x, secure in the knowledge that we could make this approximation precise if necessary.

Now let’s plug this approximation into equation (2):

ln(Pr[A]) ≈ −\frac{1}{n}− \frac{2}{n}− \frac{3}{n}− ...− \frac{m − 1}{n}
= −\frac{1}{n} ∑_{i=1}^{m−1} i
= −\frac{m(m − 1)}{2n}
≈ −\frac{m^{2}}{2n}.  (3)

Note that we’ve used the approximation for ln(1−x) with x = \frac{1}{n}, \frac{2}{n}, \frac{3}{n},..., \frac{m−1}{n}. So our approximation should be good provided all these are small, i.e., provided n is fairly big and m is quite a bit smaller than n. Once we’re done, we’ll see that the approximation is actually pretty good even for modest sizes of n.

Now we can undo the logs in (3) to get our expression for Pr[A]:

Pr[A] ≈ e^{− \frac{m^{2}}{2n}} .

The final step is to figure out for what value of m this probability becomes \frac{1}{2}. So we want the largest m such

that e^{− \frac{m^{2}}{2n}} ≥ \frac{1}{2}. This means we must have

−\frac{m^{2}}{2n} ≥ ln(\frac{1}{2}) = −ln2,  (4)

or equivalently

m ≤ \sqrt{(2ln2)n}≈ 1.177\sqrt{n}.

So the bottom line is that we can hash approximately \underline{m = ⌊1.177\sqrt{n}⌋ keys} before the probability of a collision reaches \frac{1}{2}.

Recall that our calculation was only approximate; so we should go back and get a feel for how much error we made. We can do this by using equation (1) to compute the exact value m = m_{0} at which Pr[A] drops below \frac{1}{2}, for a few sample values of n. Then we can compare these values with our estimate m = 1.177\sqrt{n}.

\begin{matrix}n & 10 & 20 & 50 & 100 & 200 & 365 & 500 & 1000 & 10^{4} & 10^{5} & 10^{6} \\ 1.177\sqrt{n} & 3.7 & 5.3 & 8.3 & 11.8 & 16.6 & 22.5 & 26.3 & 37.3 & 118 & 372 & 1177 \\ exact m_{0} & 4 & 5 & 8 & 12 & 16 & 22 & 26 & 37 & 118 & 372 & 1177 \end{matrix}

From the table, we see that our approximation is very good even for small values of n. When n is large, the error in the approximation becomes negligible.

Why \frac{1}{2}?

Our hashing question asked when the probability of a collision rises to \frac{1}{2}. Is there anything special about \frac{1}{2}? Not at all. What we did was to (approximately) compute Pr[A] (the probability of no collision) as a function of m, and then find the largest value of m for which our estimate is smaller than \frac{1}{2}. If instead we were interested in when the collision probability reaches (say) 0.95 (= 95%), we would just replace \frac{1}{2} by 0.05 in equation (4). If you work through the last piece of algebra again, you’ll see that this gives us the critical value m = \sqrt{(2ln20)n}≈ 2.45\sqrt{n}. So no matter what “confidence” probability we specify, our critical value of m will always be c\sqrt{n}for some constant c (which depends on the confidence probability).

Birthdays

Here is a famous problem, often referred to as the “birthday paradox” (though it is not really a paradox at all). You are having some friends over for a party. How many guests do you need to invite in order to have a good chance (say, at least \frac{1}{2}) that two of them have the same birthday? This is exactly our hashing collision problem, with n = 365 (the number of days in the year).^{2} From the above table, we see that 23 people are enough (why?). If you wanted to play it safer and have a higher probability (say, 95%), you would need 47 people (you should check this).

Application 2: Load balancing

One of the most pressing practical issues in distributed computing is how to spread the workload in a distributed system among its processors. This is a huge question, still very much unsolved in general. Here we investigate an extremely simple scenario that is both fundamental in its own right and also establishes a baseline against which more sophisticated methods should be judged.

Suppose we have m identical jobs and n identical processors. Our task is to assign the jobs to the processors in such a way that no processor is too heavily loaded. Of course, there is a simple optimal solution here: just divide up the jobs as evenly as possible, so that each processor receives either ⌈\frac{m}{n}⌉ or ⌊\frac{m}{n}⌋ jobs. However, this solution requires a lot of centralized control, and/or a lot of communication: the workload has to be balanced evenly either by a powerful centralized scheduler that talks to all the processors, or by the exchange of many messages between jobs and processors. This kind of operation is very costly in most distributed systems. The question therefore is: What can we do with little or no overhead in scheduling and communication cost?

The first idea that comes to mind here is... balls and bins! I.e., each job simply selects a processor uniformly at random and independently of all others, and goes to that processor. (Make sure you believe that the probability space for this experiment is the same as the one for balls and bins.) This scheme requires no communication. However, presumably it won’t in general achieve an optimal balancing of the load. Let X be the maximum loading of any processor under our randomized scheme. Note that X isn’t a fixed number: its value depends on the outcome of our balls and bins experiment.^{3} So, as designers or users of this load balancing scheme, what should be our question?

2Well, not exactly: We are ignoring leap years, and more seriously, we are assuming that birthdays are independently and uniformly distributed throughout the year. In fact, the birth rate fluctuates through the year, with peaks (for example) about nine months after winter holidays.

3In fact, X is called a \underline{random variable}: we’ll define this properly in the next lecture.

CS 70, Fall 2004, Lecture 15 4

Q: Find the smallest value k such that

Pr[X ≥ k] ≤ \frac{1}{2}.

If we have such a value k, then we’ll know that, with good probability (at least \frac{1}{2}), the maximum load on any processor in our system won’t exceed k. This will give us a good idea about the performance of the system. Of course, as with our hashing application, there’s nothing special about the value \frac{1}{2}: we’re just using this for illustration. As you can check later, essentially the same analysis can be used to find k such that Pr[X ≥ k] ≤ 0.05 (i.e., 95% confidence), or any other value we like. Indeed, we can even find the k’s for several different confidence levels and thus build up a more detailed picture of the behavior of the scheme. To simplify our problem, we’ll also assume from now on that m = n (i.e., the number of jobs is the same as the number of processors). With a bit more work, we could generalize our analysis to other values of m.

From Application 1 we know that we get collisions already when m ≈ 1.177\sqrt{n}. So when m = n the maximum load will certainly be larger than 1 (with good probability). But how large will it be? If we try to analyze the maximum load directly, we run into the problem that it depends on the number of jobs at every processor (or equivalently, the number of balls in every bin). Since the load in one bin depends on those in the others, this becomes very tricky. Instead, what we’ll do is analyze the load in any one bin, say bin 1; this will be fairly easy. Call the load in bin 1 X_{1} (another random variable). What we’ll do is find k such that

Pr[X_{1} ≥ k] ≤ \frac{1}{2n}.  (5)

Since all the bins are identical, we will then know that, for the same k,

Pr[X_{i} ≥ k] ≤ \frac{1}{2n} for i = 1,2,...,n ,

where X_{i} is the load in bin i. But now, since the event X ≥ k is exactly the union of the events X_{i} ≥ k (why?), we can use the “Union Bound” from the previous lecture:

Pr[X ≥ k]= Pr[⋃ _{i=1}^{n} (X_{i} ≥ k)]
≤ ∑ _{i=1}^{n} Pr[X_{i} ≥ k]
= n × \frac{1}{2n}
= \frac{1}{2}.

It’s worth standing back to notice what we did here: we wanted to conclude that Pr[A] ≤ \frac{1}{2}, where A is the event that X ≥ k. We couldn’t analyze A directly, but we knew that A = ⋃ _{i=1}^{n} A_{i}, for much simpler events A_{i} (namely, A_{i} is the event that X_{i} ≥ k). Since there are n events A_{i}, and all have the same probability, it is enough for us to show that Pr[A_{i}] ≤ \frac{1}{2n}; the union bound then guarantees that Pr[A] ≤ \frac{1}{2}. This kind of reasoning is very common in applications of probability in Computer Science.

Now let’s get back to our problem. Recall that we’ve reduced our task to finding k such that

Pr[X_{1} ≥ k] ≤ \frac{1}{2n},

where X_{1} is the load in bin 1. It’s not hard to write down an \underline{exact} expression for Pr[X_{1} = j], the probability that the load in bin 1 is precisely j:

Pr[X_{1} = j] = \binom{n}{j} ( \frac{1}{n})^{j} (1 − \frac{1}{n} )^{n−j} .  (6)

This can be seen by viewing each ball as a biased coin toss: Heads corresponds to the ball landing in bin 1, Tails to all other outcomes. So the Heads probability is \frac{1}{n}; and all coin tosses are (mutually) independent. As we saw in earlier lectures, (6) gives the probability of exactly j Heads in n tosses.

Thus we have

Pr[X_{1} ≥ k] = ∑_{j=k}^{n} Pr[X_{1} = j] = ∑_{j=k}^{n} \binom{n}{j} ( \frac{1}{n})^{j} (1 − \frac{1}{n} )^{n−j} .  (7)

Now in some sense we are done: we could try plugging values k = 1,2,... into (7) until the probability drops below \frac{1}{2n}. However, as in the hashing example, it will be much more useful if we can massage equation (7) into a cleaner form from which we can read off the value of k more directly. To do this, we’ll replace the exact equation (7) with an approximation obtained from the union bound: it will turn out that this approximation is pretty good in practice.

Let B denote the event “X_{1} ≥ k”, and for each subset S ⊆ {1,2,...,n} of exactly k balls, let B_{S} denote the event that all balls in S land in bin 1. Clearly B = ⋃_{S}B_{S}, because B happens if and only if at least one of the B_{S} happens. So, using the union bound again, we can write

Pr[X_{1} ≥ k] ≡ Pr[B] = Pr[⋃_{S}B_{S}] ≤ ∑_{S} Pr[B_{S}].  (8)

What is Pr[B_{S}]? Well, for any S, this is just the probability that some particular set of k balls land in bin 1, which is just (\frac{1}{n})^{k}. And the number of such sets S is \binom{n}{k}. So the sum in (8) can be written as

Pr[X_{1} ≥ k] ≤ ∑_{S} Pr[B_{S}] = \binom{n}{k}(\frac{1}{n})^{k}.  (9)

This is much simpler to work with than the sum in equation (7).

The one remaining messy thing in equation (9) is the binomial coefficient \binom{n}{k}. This we can deal with using the standard approximation^{4} (\frac{n}{k})^{k} ≤ \binom{n}{k} ≤ (\frac{ne}{k})^{k}, which gives us

Pr[X_{1} ≥ k] ≤ ( \frac{ne}{k})^{k}( \frac{1}{n})^{k} = ( \frac{e}{k})^{k}  (10)

Note that, even though we have made a few approximations, inequality (10) is completely valid: all our approximations were “≤”, so we always have an upper bound on Pr[X_{1} ≥ k]. [You should go back through all the steps and check this.]

Recall from (5) that our goal is to make the probability in (10) less than \frac{1}{2n}. We can ensure this by choosing k so that

( \frac{e}{k})^{k} ≤ \frac{1}{2n}.  (11)

Now we are in good shape: given any value n for the number of jobs/processors, we just need to find the smallest value k = k_{0} that satisfies inequality (11). We will then know that, with probability at least \frac{1}{2}, the maximum load on any processor is at most k_{0}. The table below shows the values of k_{0} for some sample values of n. As an exercise, you are invited to perform the experiment and compare these values of k_{0} with what happens in practice.

n 10 20 50 100 500 1000 10^{4} 10^{5} 10^{6} 10^{7} 10^{8} 10^{15}
exact k_{0} 5 6 6 7 8 8 9 10 11 12 13 19
ln(2n) 3.0 3.7 4.6 5.3 6.9 7.6 9.9 12.2 14.5 16.8 19.1 35.2
\frac{2lnn}{lnlnn} 5.6 5.4 5.8 6.0 6.8 7.2 8.2 9.4 10.6 11.6 12.6 20

4Computer scientists and mathematicians carry around a little bag of tricks for replacing complicated expressions like \binom{n}{k} with simpler approximations. This is just one of these. It isn’t too hard to prove the lower bound, i.e., that \binom{n}{k} ≥ ( \frac{n}{k})^{k}. The upper bound is a bit trickier, and makes use of another approximation for n! known as Stirling’s approximation, which implies that k! ≥ ( \frac{k}{e})^{k}.

We won’t discuss the details here.

CS 70, Fall 2004, Lecture 15 6

Can we come up with a formula for k_{0} as a function of n (as we did for the hashing problem)? Well, let’s take logs in (11):

k(lnk − 1) ≥ ln(2n).  (12)

From this, we might guess that k = ln(2n) is a good value for k_{0}. Plugging in this value of k makes the lefthand side of (12) equal to ln(2n)(lnln(2n)−1), which is certainly bigger than ln(2n) provided lnln(2n) ≥ 2, i.e., n ≥ \frac{1}{2}e^{e^{2}} ≈ 810. So for n ≥ 810 we can claim that the maximum load is (with probability at least \frac{1}{2}) no larger than ln(2n). The table above plots the values of ln(2n) for comparison with k_{0}. As expected, the estimate is quite good for small n, but becomes rather pessimistic when n is large.

For large n we can do better as follows. If we plug the value k = \frac{lnn}{lnlnn} into the left-hand side of (12), it becomes

\frac{lnn}{lnlnn}(lnlnn − lnlnlnn − 1) = lnn( 1 − \frac{lnlnlnn + 1}{lnlnn} ) .  (13)

Now when n is large this is just barely smaller than the right-hand side, ln(2n). Why? — Because the second term inside the parentheses goes to zero as n → ∞,^{5} and because ln(2n) = lnn + ln2, which is very close to lnn when n is large (since ln2 is a fixed small constant). So we can conclude that, for large values of n, the quantity \frac{lnn}{lnlnn} should be a pretty good estimate of k_{0}. Actually for this estimate to become good n has to be (literally) astronomically large. For more civilized values of n, we get a better estimate by taking k = \frac{2lnn}{lnlnn}. The extra factor of 2 helps to wipe out the lower order terms (i.e., the second term in the parenthesis in (13) and the ln2) more quickly. The table above also shows the behavior of this estimate for various values of n.

Finally, here is one punchline from Application 2. Let’s say the total US population is about 250 million. Suppose we mail 250 million items of junk mail, each one with a random US address. Then (see the above table) with probability at least \frac{1}{2}, no one person anywhere will receive more than about a dozen items!

5To see this, note that it is of the form \frac{lnz+1}{z} where z = lnlnn, and of course \frac{lnz}{z} → 0 as z → ∞.

CS 70, Fall 2004, Lecture 15 7