unj2
unj2

Reputation: 53551

What is the performance hit for the compiler if it initializes variables?

Sutter says this:

"In the low-level efficiency tradition of C and C++ alike, the compiler is often not required to initialize variables unless you do it explicitly (e.g., local variables, forgotten members omitted from constructor initializer lists)"

I have always wondered why the compiler doesn't initialize primitives like int32 and float to 0. What is the performance hit if the compiler initializes it? It should be better than incorrect code.

Upvotes: 1

Views: 1514

Answers (4)

Matthieu M.
Matthieu M.

Reputation: 300349

This argument is incomplete, actually. Unitialized variables may have two reasons: efficiency and the lack of a suitable default.

1) Efficiency

It is, mostly, a left-over of the old days, when C compilers were simply C to assembly translators and performed no optimization whatsoever.

These days we have smart compilers and Dead Store Elimination which in most cases will eliminate redundant stores. Demo:

int foo(int a) {
    int r = 0;
    r = a + 3;
    return r;
}

Is transformed into:

define i32 @foo(i32 %a) nounwind uwtable readnone {
  %1 = add nsw i32 %a, 3
  ret i32 %1
}

Still, there are cases where even the smarter compiler cannot eliminate the redundant store and this may have an impact. In the case of a large array that is later initialized piecemeal... the compiler may not realize that all values will end up being initialized and thus not remove the redundant writes:

int foo(int a) {
    int* r = new int[10]();
    for (unsigned i = 0; i <= a; ++i) {
        r[i] = i;
    }
    return r[a % 2];
}

Note in the following the call to memset (that I required by suffixing the new call with () which is value initialization). It was not eliminated even though the 0 are unneeded.

define i32 @_Z3fooi(i32 %a) uwtable {
  %1 = tail call noalias i8* @_Znam(i64 40)
  %2 = bitcast i8* %1 to i32*
  tail call void @llvm.memset.p0i8.i64(i8* %1, i8 0, i64 40, i32 4, i1 false)
  br label %3

; <label>:3                                       ; preds = %3, %0
  %i.01 = phi i32 [ 0, %0 ], [ %6, %3 ]
  %4 = zext i32 %i.01 to i64
  %5 = getelementptr inbounds i32* %2, i64 %4
  store i32 %i.01, i32* %5, align 4, !tbaa !0
  %6 = add i32 %i.01, 1
  %7 = icmp ugt i32 %6, %a
  br i1 %7, label %8, label %3

; <label>:8                                       ; preds = %3
  %9 = srem i32 %a, 2
  %10 = sext i32 %9 to i64
  %11 = getelementptr inbounds i32* %2, i64 %10
  %12 = load i32* %11, align 4, !tbaa !0
  ret i32 %12
}

2) Default ?

The other issue is the lack of a suitable value. While a float could perfectly be initialized to NaN, what of integers ? There is no integer value that represents the absence of value, none at all! 0 is one candidate (among others), but one could argue it's one of the worst candidate: it's a very likely number, and thus likely has a specific meaning for the usecase at hand; are you sure that you are comfortable with this meaning being the default ?

Food for thought

Finally, there is one neat advantage of unitialized variables: they are detectable. The compiler may issue warnings (if it's smart enough), and Valgrind will raise errors. This make logical issues detectable, and only what is detected can be corrected.

Of course a sentinel value, such as NaN, would be as useful. Unfortunately... there is none for integers.

Upvotes: 4

Mac
Mac

Reputation: 14791

There are two ways in which initialisation might impact performance.

First, initialising a variable takes time. Granted, for a single variable it's probably negligible, but as others have suggested, it can add up with large numbers of variables, arrays, etc.

Second, who's to say that zero is a reasonable default? For every variable for which zero is a useful default, there's probably another one for which it isn't. In that case, if you do initialise to zero you then incur further overhead re-initialising the variable to whatever value you actually want. You essentially pay the initialisation overhead twice, rather than once if the default initialisation does not occur. Note that this is true no matter what you choose as a default value, zero or otherwise.

Given the overhead exists, it's typically more efficient to not initialise and to let the compiler catch any references to uninitialised variables.

Upvotes: 2

Joshua
Joshua

Reputation: 43327

Compile with -Wmaybe-uninitialized and find out. Those are the only places the compiler would not be able to optmize out the primitive initialization.

As for the heap ...

Upvotes: 0

Ghost2
Ghost2

Reputation: 532

Basically, a variable references a place in memory which can be modified to hold data. For an unitialized variable, all that the program needs to know is where this place is, and the compiler usually figures this out ahead of time, so no instructions are required. But when you want it to be initialized (to say, 0), the program needs to use an extra instruction to do so.

One idea might be to zero out the entire heap while the program is starting, with memset, then initialize all of the static stuff, but this isn't needed for anything that is set dynamically before it's read. This would also be a problem for stack-based functions which would need to zero out their stack frame every time a function is called. In short, it's much more efficient to allow variables to default to undefined, particularly when the stack is frequently being overwritten with newly called functions.

Upvotes: 0

Related Questions