Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

pointers on pointers - reason for performance penalty

I answered this question, and noticed what I consider as a strange behavior of the compiler.

I first wrote this program (as part of my answer there):

class Vector {
private:
  double** ptr;


public:

  Vector(double** _ptr): ptr(_ptr) {}

  inline double& operator[](const int iIndex) const {
    return *ptr[iIndex];
  }
};

extern "C" int test(const double a);

int main() {
    double a[2] = { 1.0, 2.0 };
    Vector va((double**) &a);

    double a1 = va[0];
    test(a1);

    double a2 = va[0];
    test(a2);
}

which generates two load instructions when compiled with :

clang -O3 -S -emit-llvm main.cpp -o main.ll

This can be seen in the llvm-IR (and could be seen in the assembly):

    define i32 @main() #0 {
    entry:
      %a.sroa.0.0.copyload = load double*, double** bitcast ([2 x double]* @_ZZ4mainE1a to double**), align 16
      %0 = load double, double* %a.sroa.0.0.copyload, align 8, !tbaa !2
      %call1 = tail call i32 @test(double %0)
      %1 = load double, double* %a.sroa.0.0.copyload, align 8, !tbaa !2
      %call3 = tail call i32 @test(double %1)
      ret i32 0
    }

I would expect only one load instruction, since no function with side effect on memory has been called, and I didn't link this object to something with side effects. In fact, when reading the program, I just expect two calls to

test(1.0);

since my array is constant in memory and everything can be inlined properly.

Just to be sure, I replaced the double pointer by a simple pointer:

class Vector {
private:
  double* ptr;

public:
  Vector(double* _ptr): ptr(_ptr) {}

  inline double& operator[](const int iIndex) const {
    return ptr[iIndex];
  }
};

extern "C" int test(const double a);

int main() {
    double a[2] = { 1.0, 2.0 };
    Vector va(a);

    double a1 = va[0];
    test(a1);

    double a2 = va[0];
    test(a2);
}

Compiled with the same line , I get the expected result:

define i32 @main() #0 {
entry:
  %call1 = tail call i32 @test(double 1.000000e+00)
  %call3 = tail call i32 @test(double 1.000000e+00)
  ret i32 0
}

Which looks like way better optimized :)

My question is therefore:

What reason prevents the compiler to perform the same inlining on the first code sample? Is that double pointers?

like image 941
Regis Portalez Avatar asked May 19 '16 12:05

Regis Portalez


1 Answers

The error is in these lines:

double a[2] = { 1.0, 2.0 };
Vector<double> va((double**) &a);

a is an array of two doubles. It decays to a double *, but &a is not a double **. Arrays and pointers are not same animal.

In fact you have the following: (void *) a == (void *) &a because the address of an array is the address of its first element.

If you want to build a pointer to pointer you must create explicitely a true pointer:

double a[2] = { 1.0, 2.0 };
double *pt = a; // or &(a[0]) ...
Vector<double> va((double**) &pt);
like image 138
Serge Ballesta Avatar answered Oct 16 '22 07:10

Serge Ballesta