Dependable cmov in Visual C++

Started by
5 comments, last by popsoftheyear 15 years, 9 months ago
Also, this worked in Visual Studio 2008... I will assume it works in 2005 as well but anyone who can testify to this is encouraged to let me know. Anyway, I have googled and searched gamedev occasionally to find a way to get cmov instructions consistently, but have not found a single answer that works every time. Closest is "use ? :". After doing so, I've still had problems getting it to EVER happen. Anyway, I took another stab and here's my results for anyone interested. As far as project configuration options go, SSE has to be enabled, and any form of the overall Optimizations need to be turned on (/O1 /O2 /Ox). Whether you favor size, speed, or neight (/Ot, /Os, none) is irrelavent. Also, this is for code generated for x86, and is only dependable with 32 bit integers, although sometimes works with __int64's for some reason. Essentially, it will generate a cmov with a single conditional and 2 variables, "X = Cond ? X : Y" or "X = Cond ? Y : X". Expressions and constants of any kind will NOT work in place of the variables, but the conditional can be anything, as long is it is only 1 conditional. Another stipulation I found, is that the compiler needs to think the variable ALREADY has something assigned to it, even if that assignment was undefined. For an example:

const int ConstVar;  // int ConstVar = 5 would be equivalent if it doesn't change.
int Condition;
int Var;
int InitVal;
int Result = InitVal;  // If this was just 'int Result;' than it wouldn't always work

Result = Condition ? Result : Var;  // Works every time
if (Condition) Result = Var;  // Also works every time

Result = Condition ? InitVal : Var;  // Only sometimes (suprisingly)
Result = Condition ? Result : ConstVar;  // Doesn't!  Usually turns into a jump!
Result = (Condition && Condition2) ? Result : Var;  // Doesn't!  Only 1 conditional seems to work.
Result = Condition ? Result : 10;  // Just like a const var of course
Result = Condition ? Var : ConstVar;  // Nope!
Result = Condition ? Result : Var / 5;  // Nope!
So a conditional statement, containing only 1 comparison (an expression like 'Var1 || Var2' is 2 comparisons of course where 'Var1 | Var2 / 10 + (Var3 - 6) > Var2 * Var3' isn't), followed by assigning a single variable (not an expression!) to a single result variable, and the original variable to itself otherwise, results in a cmov every time. Anything else might work, but I have found it to be either inconsistent, or not produce it at all. Also, arrays are a bit trickier.

// Works consistently
for (int Index = 0; Index < 10; Index++) {
  long In1 = rand();
  long SomeResult = rand();
  long Out = OutBuffer[Index];  // OutBuffer doesn't have to be volatile
  Out = In1 < InBuffer[Index] ? Out : SomeResult;
  OutBuffer[Index] = Out;  // Notice we did not use an array directly in the resulting assignment
}
You CANNOT use an array in the assignment at the end, directly. If SomeResult, in this case, was assigned an array entry called InBufferTwo[Index], InBufferTwo would have to be declared as volatile to work!!!! Otherwise the compiler will optimize it into the conditional, skipping using 'SomeResult', and not recognize it as a cmovable operation, sadly to say. It also doesn't like pointer dereferencing in place of a straight array lookup (in the conditional). So either assign the dereferenced pointer to another var, or use a direct array lookup [] style, or just assign all your array values to an integer variable! Very interesting the confinements to get cmov ops to generate. If anyone has anything to add, it'd be great... it seems like I'm not the only one that has tried to find a dependable solution to this problem. And of course, always check the assembly output to make sure it worked! Cheers -Scott [Edited by - popsoftheyear on July 14, 2008 3:48:20 PM]
Advertisement
What if you switch to optimizing code for size (/Os) instead of speed?
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
It still compiles to a cmov. However, all this only consistently works for 32 bit integers (compiling for x86). 64 bit integers work occasionally.

SSE seems to be the only configuration option that affects it, along with any optimization level besides "Disabled" turned on.

[Edited by - popsoftheyear on July 14, 2008 1:59:44 PM]
Additional information added to original post. Compiler options, as well as how to use with arrays. Let me know if anything else breaks the consistency factor in this! I hope someone finds this useful.

Cheers
-Scott
And I present one more snippet, only because this can get complicated.

for (int XPos = LI; XPos <= RI; XPos++) {  // Test against z buffer and draw  volatile unsigned int Result1 = ((RVal+DeltaR*D) & 0xff0000) | (((GVal+DeltaG*D) & 0xff0000) >> 8)    | ((BVal+DeltaB*D) >> 16);  unsigned int Res2 = Result1;		  unsigned int Out = Buf[D];  unsigned int In = *ZBuf;		  //No - Doesn't like pointer dereferencing  // Out = *ZBuf > ZVal ? Res2 : Out;  //No - Result1 is volatile so this code doesn't optimize, and if it wasn't volatile the optimizer  //     would say "hey, there is too much going on with Result1 up there, we'll throw a conditional and skip  //     IT ALL if 'In > ZVal' is false...  // Out = In > ZVal ? Result1 : Out;  // Yes - No pointer dereferencing IN the conditional.  You can optimize statements containing Res2,   //       and no array lookups directly in either assignment after the conditional.  Variable assigns to self  //       on one of the assignments.  No expressions involved in the assignments.  This is the safest, best  //       way to guarantee a cmov, although perhaps the compiler was right about skipping all that Result1  //       math....  Out = In > ZVal ? Res2 : Out;  Buf[D] = Out;		  // Writing 'Out = In' on the next line, while equivalent, would destroy BOTH cmov compilations  // below and above.  The compiler needs some pretty specific context clues, but they always seem  // to work once you get them down.  Out = *ZBuf;    Out = Out > ZVal ? ZVal : Out;  *ZBuf = Out;
Would it be possible to write an inlineable function that would then work every time, assuming correct optimisation settings? Building your own intrinsic function in a way?
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
From what I can tell that's actually more difficult. You still have to follow the same rules for the variables you send to it, even if you do some of the same stuff inside the inlined function, and even then it doesn't consistently work... it's a lot more dependant on what is around the function. I've found that seemingly unrelated statements around it affects it, sometimes.

Now while this might be considered a flaw to some, and it's such a pain to get cmovs to generate in VC++, here are a couple articles that are worth noting:
One by Linus Torvalds and one by a couple people who read the previous article.

It seems that Visual Studio will use cmov in specific cases, and you can trick it into thinking it is the best solution (and it may very well be sometimes), but it is definitely not even worth pursuing if the branch is predictable.

Another good point is what I ran into earlier... how it may be more costly to cmov if the cmp, jxx set would have skipped more than a simple assignment. With the cmov you might be unnecessarily calculating a lot of things.

Interesting stuff...

Cheers
-Scott

This topic is closed to new replies.

Advertisement