Why is a ternary operator with two constants faster than one with a variable?

后端 未结 3 442
清酒与你
清酒与你 2020-12-31 10:56

In Java, I have two different statements which accomplish the same result through using ternary operators, which are as follows:

  1. num < 0 ? 0 : num;
3条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-12-31 11:07

    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 interpreter slowing down the code:

    results[i] = num < 0 ? 0 : 1;
    

    Here the interpreter 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.

提交回复
热议问题