CS 70

Operation Counting

Here's that example again:

bool lowerHalf(float x, const float* arr, size_t arrSize) {  // 1
    size_t numLessThan = 0;                                  // 2
    for (size_t i = 0; i < arrSize; ++i) {                   // 3
        if (arr[i] < x) {                                    // 4
            ++numLessThan;                                   // 5
        }                                                    // 6
    }                                                        // 7
    return numLessThan < arrSize/2.0;                        // 8
}                                                            // 9

Instead of counting instructions, we could abstract away the CPU entirely and focus on the operations we can see in the algorithm.

  • Duck speaking

    Which operations should we count?

  • LHS Cow speaking

    That's the question! We need to pick, depending on what we want to know or what comparisons we want to make.

For this algorithm, it probably isn't very interesting to count the number of divisions, which is just one no matter what. The things we might be more interested in are increments, less–than comparisons, assignments, and indexing operations. Let's try counting the number of less–than comparisons.

  • RHS Cow speaking

    How many less–than comparisons happen when this code runs?

  • Horse speaking

    Hay, wait. What do you mean, “How many?” It's not like a number. It depends on the size of the array!

  • LHS Cow speaking

    Oh, you're right! We need to make another decision.

Typically, the number of operations that are executed depends on the input in some way!

We want to derive a function \( \mathrm{C} \) for the cost of some quantity from the input \( n \) such that \( \mathrm{C}(n) \) gives us the number of operations performed for that input.

To make this analysis concrete/viable, we need to make some decisions:

  • What operation or operations are we counting? That is, what are the units of the output of \( \mathrm{C}(n) \)?
  • What quantity \( n \) will represent the input in our characterization of the cost?

Example 1

Let's let

  • \( n \) be the number of elements in the array, and
  • \( \mathrm{C}_{\mathrm{lessthan}}(n) \) be the number of less–than comparisons the algorithm performs.

Give the number of less–than operations as an expression in terms of the number of elements in the array.

Looking back at our code, we can see that…

Line(s)
1, 2 There are no comparisons on lines 1 and 2.
3–7 Define a loop, which is run \( n \) times.
In each iteration of the loop, we can see there are two less–than comparisons (one for the loop variable, and one between x and arr[i]).
Then, to end the loop, the last value of the loop variable has to be compared to the loop limit and fail the comparison, so that's one more less–than.
So, we can express the number of comparisons in the loop as \( 1 + \sum^{n}_{i=1}2 = 2n + 1 \).
8 Finally, on line 8 we have one last less–than comparison.
So the total number of less–than comparisons is \( \mathrm{C}_{\mathrm{lessthan}}( n ) = 2n + 2 \), where \( n \) is the size of the array.
  • RHS Cow speaking

    In our working here, we used summation notation. If you're not sure how that works, the video below may help.

  • Hedgehog speaking

    Oooooh, math makes me nervous.

  • LHS Cow speaking

    That's okay; we got you. In this video we'll walk through this analysis.

  • RHS Cow speaking

    If you are feeling comfortable with the above argument, you can skip this next video.

Example 2

This time, let's let

  • \( n \) be the number of bits representing the array and all three input parameters, and
  • \( \mathrm{C}_{\mathrm{index}}(n) \) be the number of indexing operations the algorithm performs.

(We will assume for this analysis that float and size_t have 32 bits and float* has 64 bits).

Work out the number of indexing operations as an expression in terms of the number of bits both in the array and all three input parameters:

Once again, looking at our code,

Line(s)
1, 2 There are no array-indexing operations.
3–7 A loop that runs arrSize times.
In each iteration of the loop, we can see there is a single indexing operation.
So we can express the number of comparisons in the loop as
\[ \sum^{\mathtt{arrSize}}_{i=1}1 = { \mathtt{arrSize} }. \]
But how can we relate arrSize to \( n \)?
The three parameters use 32, 64, and 32 bits, respectively. The array uses 32 bits for each of the arrSize elements.
So \[ \begin{align} n & = 32 + 64 + 32 + ( 32 \times \mathtt{arrSize} ) \\ & = 128 + ( 32 \times \mathtt{arrSize} ) \end{align} \].
We can then solve for arrSize to find that \[ \mathtt{arrSize} = \frac{n - 128}{32} \].
8 Finally, there is no array-indexing operation on line 8.

So we now know that \( \mathrm{C}_{\mathrm{index}}(n) = (n - 128)/32 \) is the number of array-indexing operations, where \( n \) is equal to the number of bits in the array plus the number of bits in each of the three parameters.

  • LHS Cow speaking

    Again, we'll do that in a video, if you like.

  • RHS Cow speaking

    If you are comfortable with the written argument, you can skip this video.

  • Hedgehog speaking

    Yuck! I did not enjoy that!

  • LHS Cow speaking

    You and me both!

  • RHS Cow speaking

    It turns out that the choices we make for the quantities in our analysis can make a big difference.

  • LHS Cow speaking

    Some quantities will be easy to work with, others less so.

  • RHS Cow speaking

    So our choices matter! If we choose n such that it's straightforward to work with, and we pick something easy to count, we'll have an easier time with our analysis. If we make poor choices, we might make work for ourselves, or worse, have something that isn't even meaningful or useful.

(When logged in, completion status appears here.)