3

i'm writting handler for OpenGL texture and i'm thinking about safety and performance. Which level of optimization should remove marked if statements?


struct Texture2D {
    GLuint ID;

    inline Texture2D(): ID(0) {};
    inline explicit Texture2D(GLuint id): ID(id) {};
    ~Texture2D();

    void GenTexture(bool regen = false);
    void DeleteTexture();

    void BindTexture();

    void Parameterf( GLenum pname, GLfloat param );
    void Parameteri( GLenum pname, GLint param );
    void glTexParameterfv( GLenum target, GLenum pname, const GLfloat *params );
    void glTexParameteriv( GLenum target, GLenum pname, const GLint *params );

    static Texture2D binded;
};
inline void Texture2D::GenTexture(bool regen) {
    if(ID){
        if(regen)
            DeleteTexture();
        else
            return;
    }

    glGenTextures(1,&ID);
}

inline void Texture2D::DeleteTexture() {
    glDeleteTextures(1,&ID);
    ID = 0;
}

inline void Texture2D::BindTexture() {
    glBindTexture(GL_TEXTURE_2D, ID);
    binded.ID = ID;
}

inline void Texture2D::Parameterf( GLenum pname, GLfloat param ){
    if(binded.ID == ID)                          // THIS
        BindTexture();                           // THIS

    glTexParameterf(GL_TEXTURE_2D,pname,param);
}

inline void Texture2D::Parameteri( GLenum pname, GLint param ){
    if(binded.ID == ID)                          // THIS
        BindTexture();                           // THIS

    glTexParameterf(GL_TEXTURE_2D,pname,param);
}

inline Texture2D::~Texture2D() {
    DeleteTexture();
}

// in this function
void loadTexture(...) {
    Texture2D t;
    t.GenTexture();
    t.BindTexture();
    // if statements in next functions
    t.Parameterf(...);
    t.Parameterf(...);
    t.Parameterf(...);
    t.Parameterf(...);
    t.Parameterf(...);
}
kravemir
  • 10,636
  • 17
  • 64
  • 111
  • Why should any level of optimisation do so? –  Aug 04 '10 at 14:19
  • 4
    shouldn't those be `!=`? (Also, past participle of bind is bound ) – falstro Aug 04 '10 at 14:21
  • Did you maybe intend for those tests to be `!=` ? The compiler is going to have a VERY tough time proving that the static variable isn't modified during opengl calls, so in general it can't eliminate the conditional test. – Ben Voigt Aug 04 '10 at 14:21
  • @Neil; they're inlined, so only the first is necessary. – falstro Aug 04 '10 at 14:22
  • @row I don't see what inlining has to do with it. –  Aug 04 '10 at 14:26
  • @Neil; in this case, the compiler can't (since you're making a call which might, in theory, affect that variable as it's not local to the function), but since bindtexture is also inlined (it doesn't HAVE to be inlined, but since it is, it can be evaluated locally, making it easier), it'll know that if the first test succeeds, none of the others can. And if the first test fails, so will all the others. – falstro Aug 04 '10 at 14:28
  • @roe: I'm not following your logic. It seems that there would be very little if any optimization being done here. –  Aug 04 '10 at 14:31
  • > shouldn't those be !=? yes should it be != :). so, i don't need these tests if it will be performance hit – kravemir Aug 04 '10 at 14:32
  • @0A0D; See Luther Blissett's answer, if the compiler can rule out any side effects ('binded' or 'binded.ID' are static, and never taken as reference, or they're local to the stack frame and all references to it are accounted for), the compiler can remove the remaining comparisons and branches. – falstro Aug 04 '10 at 14:35
  • @Miro: You will not eat up too much processing time with a single conditional –  Aug 04 '10 at 14:35
  • @roe: Possibly, but in which case you are swapping good design for speed. It may be what the OP wants but we really don't know what he is targeting. My guess is it is just a hypothetical question. Maybe the example is just too small, but I don't see much optimization going on here, static, local or not. –  Aug 04 '10 at 14:37
  • @0A0D; Huh? We're talking automatic optimization here, the question is what would cause to compiler to remove these statements during optimization, as they are only needed in the first call. The whole point of optimization is speed, and compiler optimization has got nothing to do with design. A better approach would probably be to sort all draw calls with regards to render states, and not do this kind of design at all, but that's not the question. – falstro Aug 04 '10 at 14:41
  • @roe: Meaning, you will optimize yourself out of a good OO design. Since the value of binded.ID can be variable, it's hard for the compiler to know what it could be, though the branching itself could possibly be predictable. With a single conditional, there is not much optimization that can be done unless you make binded.ID a constant value that is determined at compile-time rather than runtime or change the conditional so it can optimize the branch not on the expected value (== vs !=). You will save some overhead already by marking the function as inline. –  Aug 04 '10 at 14:47
  • @0A0D; no one's talking about changing the code at all. He's asking if the compiler could remove those during compilation since the first is the only one necessary. The compiler can't in this case of course, since the binded.ID is global (which, by the way is rather poor OO-design). If it wasn't (and there was no other way to reach it) the compiler could remove all but the first if-blocks (in the long line of inlined if-blocks), since they can never be executed. Why are you talking about changing the design? No one has even mentioned that before you did. – falstro Aug 04 '10 at 14:57
  • @roe: I guess I misunderstood the overall comments and question –  Aug 04 '10 at 14:58

3 Answers3

4

None.

Sad story, but C++ assumes that if you call a function, then this function might produce all kinds of side effects, including changing the value of binded.ID (which the function somehow knows to do)

Except

If you make sure that the functions you invoke have absolutely no legal way to know about your bindend.ID, either directly (by referencing it) or indirectly (because somebody else take a pointer of it and passed it around). Here's a simple example (assuming that side_effect() is in a different translation unit)

int side_effect();
int k=1; 

int main()
{
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
}

side_effect() can use and change k legally by declaring it as an external. No call of side_effect can be optimized away.

int side_effect();
static int k=1; 

int main()
{
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
}

It is not possible for side_effect to access k in an allowed manner, because you can't access statics in another translation unit. Therefore the code can be optimized to side_effect(); return 0 because k will not change, as long as side_effect() does not poke around in the memory. Which would be undefined behavior of course.

int side_effect();
void snitch(int*);

static int k=1; 

int main()
{
    snitch(&k); // !!!
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
    side_effect(); 
    if (k!=0) return 0;
}

The compiler has no way to know, if snitch() saves its argument in a place where side_effect() can change it, therefore no call to side_effect() can be eliminated.

You get the same situation, if you have k as a local variable: If there is a possibility that some unknown routine can access k in a legal way, then the compiler can not make optimizations based on the value of k.

PS: Making k const does not help, because it is legal to cast a const away. const-ness can not be used as a optimization hint.

Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
  • Modern compilers that do link time code generation potentially have the information to know if k could be changed by side_effect. However it's still extremely difficult to prove it's safe in c++ so I wouldn't expect it, and agree with the answer in practise – jcoder Aug 04 '10 at 16:26
2

This will depend on the compiler and your best bet is to test - compile and inspect the emitted machine code.

sharptooth
  • 167,383
  • 100
  • 513
  • 979
  • It shouldn't be depended on compiler because as explained Luther Blissett. Variable can be changed during GL calls. – kravemir Aug 04 '10 at 14:50
  • Actually, the best bet is to just write clear code, and optimize only if you've determined that these statements are a bottleneck. But yes, testing is the only way to determine what a compiler does. – Kristopher Johnson Aug 04 '10 at 14:50
2

No level of optimisation can (correctly) remove those tests. They could only be removed if both arguments were compile-time constants, and neither of these are, or if the compiler can prove that they won't change value between the tests.

Since binded is static, the compiler can't know that the calls to the GL functions won't alter it.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Not completely true, it can also be removed if they're local to the stackframe, but these are global. +1 either way. – falstro Aug 04 '10 at 14:31