I have discovered what makes the second statement take longer, but I cannot explain why it happens, if that makes sense. That said, I do believe this should gives some greater insight into the issue we have here.
Before I explain my reasoning I'll just tell you my discoveries outright: This has nothing to do with returning a constant or a variable from a ternary operation. It has everything to do with returning an integer or a float from a ternary operation. It comes down to this: returning a float from a ternary operation is "significantly" slower than returning an integer.
I cannot explain why, but that is the root cause at least.
Here's my reasoning: I used the following code to create a small text document with results, very similar to your example code.
Random rand = new Random();
final int intOne = 1;
final int intZero = 0;
final float floatOne = 1f;
final float floatZero = 0f;
final long startTime = System.nanoTime();
float[] results = new float[100000000];
for (int i = 0; i < 100000000; i++) {
float num = (rand.nextFloat() * 2) - 1;
// results[i] = num < 0 ? 0 : num;
// results[i] = num * (num < 0 ? 0 : 1);
// results[i] = num < 0 ? 0 : 1;
// results[i] = (num < 0 ? 0 : 1);
// results[i] = (num < 0 ? 0 : num);
// results[i] = 1 * (num < 0 ? 0 : num);
// results[i] = num < 0 ? 0 : one;
// results[i] = num < 0 ? 0 : 1f;
// results[i] = (num < 0 ? 0 : one);
// results[i] = (num < 0 ? 0 : 1f);
// results[i] = (num < 0 ? 0 : 1);
// results[i] = (num < 0 ? 0f : 1f);
// results[i] = (num < 0 ? 0 : 1);
// results[i] = (num < 0 ? floatZero : floatOne);
// results[i] = (num < 0 ? intZero : intOne);
// results[i] = num < 0 ? intZero : intOne;
// results[i] = num * (num < 0 ? 0 : 1);
// results[i] = num * (num < 0 ? 0f : 1f);
// results[i] = num < 0 ? 0 : num;
}
final long endTime = System.nanoTime();
String str = (endTime - startTime) + "\n";
System.out.println(str);
Files.write(Paths.get("test.txt"), str.getBytes(), StandardOpenOption.APPEND);
For reasons I won't go into now but you can read about here, I used nanoTime()
instead of currentTimeMillis()
. The last line just adds the resulting time value to a text document so i can easily add comments.
Here's the final text document, it includes the entire process of how I came to this conclusion:
num < 0 ? 0 : num // standard "intuitive" operation
1576953800
1576153599
1579074600
1564152100
1571285399
num * (num < 0 ? 0 : 1) // strange operation that is somehow faster
1358461100
1347008700
1356969200
1343784400
1336910000
// let's remove the multiplication and focus on the ternary operation
num < 0 ? 0 : 1 // without the multiplication, it is actually slower...?
1597369200
1586133701
1596085700
1657377000
1581246399
(num < 0 ? 0 : 1) // Weird, adding the brackets back speeds it up
1797034199
1294372700
1301998000
1286479500
1326545900
(num < 0 ? 0 : num) // adding brackets to the original operation does NOT speed it up.
1611220001
1585651599
1565149099
1728256000
1590789800
1 * (num < 0 ? 0 : num) // the speedup is not simply from multiplication
1588769201
1587232199
1589958400
1576397900
1599809000
// Let's leave the return value out of this now, we'll just return either 0 or 1.
num < 0 ? 0 : one // returning 1f, but from a variable
1522992400
1590028200
1605736200
1578443700
1625144700
num < 0 ? 0 : 1f // returning 1f as a constant
1583525400
1570701000
1577192000
1657662601
1633414701
// from the last 2 tests we can assume that returning a variable or returning a constant has no significant speed difference.
// let's add the brackets back and see if that still holds up.
(num < 0 ? 0 : floatOne) // 1f as variable, but with ()
1573152100
1521046800
1534993700
1630885300
1581605100
(num < 0 ? 0 : 1f) // 1f as constant, with ()
1589591100
1566956800
1540122501
1767168100
1591344701
// strangely this is not faster, where before it WAS. The only difference is that I now wrote 1f instead of 1.
(num < 0 ? 0 : 1) // lets replace 1f with 1 again, then.
1277688700
1284385000
1291326300
1307219500
1307150100
// the speedup is back!
// It would seem the speedup comes from returning an integer rather than a float. (and also using brackets around the operation.. somehow)
// Let's try to confirm this by replacing BOTH return values with floats, or integers.
// We're also keeping the brackets around everything, since that appears to be required for the speedup
(num < 0 ? 0f : 1f)
1572555600
1583899100
1595343300
1607957399
1593920499
(num < 0 ? 0 : 1)
1389069400
1296926500
1282131801
1283952900
1284215401
// looks promising, now lets try the same but with variables
// final int intOne = 1;
// final int intZero = 0;
// final float floatOne = 1f;
// final float floatZero = 0f;
(num < 0 ? floatZero : floatOne)
1596659301
1600570100
1540921200
1582599101
1596192400
(num < 0 ? intZero : intOne)
1280634300
1300473900
1304816100
1285289801
1286386900
// from the looks of it, using a variable or constant makes no significant difference, it definitely has to do with the return type.
// That said, this is still only noticeable when using brackets around the operation, without them the int operation is still slow:
num < 0 ? intZero : intOne
1567954899
1565483600
1593726301
1652833999
1545883500
// lastly, lets add the multiplication with num back, knowing what we know now.
num * (num < 0 ? 0 : 1) // the original fast operation, note how it uses integer as return type.
1379224900
1333161000
1350076300
1337188501
1397156600
results[i] = num * (num < 0 ? 0f : 1f) // knowing what we know now, using floats should be slower again.
1572278499
1579003401
1660701999
1576237400
1590275300
// ...and it is.
// Now lets take a look at the intuitive solution
num < 0 ? 0 : num // the variable num is of type float. returning a float from a ternary operation is slower than returning an int.
1565419400
1569075400
1632352999
1570062299
1617906200
This all still begs the question: Why is a ternary operation that returns a float slower than one returning an int? Both an int and float are 32 bits. Without the ternary operation floats are not particularly slow, we can see that because we can multiply the returned int with a float variable, and that does not slow it down. I do not have the answer to that.
As for why the brackets speed up the operation: I am no expert, but I'm guessing it probably has to do with the compiler slowing down the code by doing what should be an optimization:
results[i] = num < 0 ? 0 : 1;
Here the compiler sees results
is an array of type float and simply replaces the integers with floats as an "optimization", this way it doesn't have to convert between types.
results[i] = (num < 0 ? 0 : 1);
Here the brackets force the interpreter to compute everything within them before doing anything else, this results in an int. Only AFTER that will the result be converted to a float so that it can fit in the array, type conversion isn't slow at all.
Again, I have no technical knowledge to back this up, it is only my educated guess.
Hopefully this is a good enough answer, if not at least it should point people with more technical knowledge than me in the right direction.