2

This question already has an answer here:

In a case where multiple variables are set equal to an operation using the result of an other operation, is it best to create a temporary variable for the result of the second operation (this would save memory) or is it best to create a temporary variable for the result of the second operation (this would save time)?

For example:

// Option 1 (memory)
totalRevenue += numSold * price;
totalProfit += numSold * price - numSold * cost;
return numSold * price;

// Option 2 (time)
saleRevenue = numSold * price;
totalRevenue += saleRevenue;
totalProfit += saleRevenue - numSold * cost;
return saleRevenue;

I understand that this choice, especially in the example above, is rather trivial. My suspicion is that it makes most sense to create the temporary variable, but I can't help feeling that it is a waste. No, memory isn't very scarce these days, but I feel like Option 1 wastes less time than Option 2 does memory. Am I right?

Why choose one way over the other?

marked as duplicate by gnat, Bart van Ingen Schenau, GlenH7 Nov 17 '15 at 18:31

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • 9
    Have you actually looked at the compiled output produced by each of those? Do you think you can do a better job than the compiler? – user40980 Nov 17 '15 at 14:57
  • 8
    You are micro-optimising your code. Write it in the way that makes it easier to read and less prone to errors (option 2) and leave the compiler to figure out how to optimise it for you. – David Arno Nov 17 '15 at 14:59
  • Assuming that these are simple arithmetic types, the naive assembly of both versions will use the same number of registers. (And the optimized assembly might look completely different anyway.) But I grant you that more complex examples can be constructed where the cached results add up to more stack space. – 5gon12eder Nov 17 '15 at 16:25
  • fyi, the memory cost of a local variable of numeric or pointer type is negligible. Memory is consumed (wasted) more by heap allocated objects than by local variables. – Erik Eidt Nov 17 '15 at 17:00
  • I do not agree. This question is not duplicated. The thread you pointed discuss about micro-optimisations. This one asks if you must favor memory over cpu using this example. Repeating the mantra "don't worry about optimisation" doesn't answer this specific question. – David Rissato Cruz Nov 17 '15 at 21:17
5

This optimization is called common subexpression elimination and is built into nearly all optimizers.

The actual draw back of too many eliminations is register pressure, a.k.a. running out of registers to hold all the temporary values. This could mean spilling the temporary to the stack which may incur a cache miss when reading it back in later and cause a massive slowdown in what should be a function that only does a bit of math. Even if simply recalculating would be faster.

Optimizers can convert in both directions as needed. So don't worry about it too much until profiling indicates that the function is a hotspot.

2

You need to put on more thing in consideration: readability. Option 2 seems much more readable than option 1.

Apart of readability, in this example, I would go to Option 2 for a reason that I'll explain below.

Here is a representation of your code in Java and the bytecode produced:

public class Main {

    public static int totalRevenue;
    public static int totalProfit;

    public static int option1(int numSold, int price, int cost) {
        // Option 1 (memory)
        totalRevenue += numSold * price;
        totalProfit += numSold * price - numSold * cost;
        return numSold * price;
    }

    public static int option2(int numSold, int price, int cost) {
        // Option 2 (time)
        int saleRevenue = numSold * price;
        totalRevenue += saleRevenue;
        totalProfit += saleRevenue - numSold * cost;
        return saleRevenue;
    }
}

Bytecode:

public class Main {
  public static int totalRevenue;

  public static int totalProfit;

  public Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static int option1(int, int, int);
    Code:
       0: getstatic     #2                  // Field totalRevenue:I
       3: iload_0
       4: iload_1
       5: imul
       6: iadd
       7: putstatic     #2                  // Field totalRevenue:I
      10: getstatic     #3                  // Field totalProfit:I
      13: iload_0
      14: iload_1
      15: imul
      16: iload_0
      17: iload_2
      18: imul
      19: isub
      20: iadd
      21: putstatic     #3                  // Field totalProfit:I
      24: iload_0
      25: iload_1
      26: imul
      27: ireturn

  public static int option2(int, int, int);
    Code:
       0: iload_0
       1: iload_1
       2: imul
       3: istore_3
       4: getstatic     #2                  // Field totalRevenue:I
       7: iload_3
       8: iadd
       9: putstatic     #2                  // Field totalRevenue:I
      12: getstatic     #3                  // Field totalProfit:I
      15: iload_3
      16: iload_0
      17: iload_2
      18: imul
      19: isub
      20: iadd
      21: putstatic     #3                  // Field totalProfit:I
      24: iload_3
      25: ireturn
}

For the sake of understandability, iload_* load data from stack and istore_* store data into stack.

So you can notice that you really saved one memory allocation, but you spend precious CPU cycles.

But here is the reason: Memory allocations work in blocks, not at byte level. Each time a thread runs, it will allocate a certain amount of memory to use in stack operations. So unless you are lucky enough to have these 4 bytes overflow memory allocation for this block (and requiring you to setup a larger stack size), you will be ending up spending the same memory amount to run both examples. But in the second one, you will spend more cycles doing math calculations.

0

The only situation where that style matters is if one or more conditions are true:

  • The "operation" involves a non-trivial, non-purely-arithmetic function call (such as a database lookup, or a linear/binary search for an element inside an array, etc.), or if you have good reasons to believe that the operation takes more than a fraction of a second,
  • Or, the result of the operation takes up tens or hundreds of megabytes or more,
  • Or, if you are using a very inefficient, non-optimizing compiler and targeting a very slow computing platform.

(The list is non-exhaustive and may be amended anytime. Anyone with edit-answer privilege are welcome to edit this list without asking. Thanks for your contributions. check the duplicate question first.)

Not the answer you're looking for? Browse other questions tagged or ask your own question.