Osiris Team
Osiris Team

Reputation: 101

What is more performant: directly setting a value or first check if the value changed and then set it?

Option 1:

List<Object> listObjects = new ArrayList<>();
for(Object object : listObjects){
  object.setValue1("new value");
}

Option 2:

List<Object> listObjects = new ArrayList<>();
for(Object object : listObjects){
  if(!object.getValue1().equals("new value"))
     object.setValue1("new value");
}

Which of the above options is more performant in terms of cpu and memory usage?

Additional context: Let's imagine the objects list is pretty big and we loop through it every 5 seconds.

Upvotes: 1

Views: 136

Answers (2)

user7571491
user7571491

Reputation:

Here is the test I did (openJDK 11). As I suspected,

  1. "testSetValueNoCheck" and "testSetValueWithCheckUsingStatic" and "testSetValueWithCheckUsingLiteral" are almost same which means you can ignore the difference.
  2. The interesting thing is "testSetValueWithCheckUsingStatic\testSetValueWithCheckUsingLiteral second time" time is always half the time of #1. I believe it is because how equals is implemented in Java for String - the functions that actual do work "StringLatin1.equals" and "StringUTF16.equals" are annotated "@HotSpotIntrinsicCandidate" which may or may not happen. That also means if data is not String, results will differ in scenarios above based on how equals is implemented.

The "second time" case is relevant based you what you wrote:

Additional context: Let's imagine the objects list is pretty big and we loop through it every 5 seconds.

So, in general I do agree with advice in comments - worry about performance when you need to. Personally for me, the time is every time I finish the module and have unit tests, do performance testing. It takes day (or even less if you have automation), but I have never found it to be counter productive.

Output:

testSetValueNoCheck: 39.9644

testSetValueWithCheckUsingStatic: 40.4051

testSetValueWithCheckUsingStatic second time: 13.7888

testSetValueWithCheckUsingLiteral: 33.6455

testSetValueWithCheckUsingLiteral second time: 18.5136

Code:

    class SetValueTest {
    class MyObject {
        private String _val;
        MyObject(final String val) {
            _val = val;
        }
        
        String getValue() {
            return _val;
        }
        
        void setValue(final String val) {
            _val = val;
        }
        
    }
    
    ArrayList<MyObject> getData() {
        final ArrayList<MyObject> data = new ArrayList<>();
        for(int i = 0; i < 1000000; ++i) {
            data.add(new MyObject(String.format("%d", i)));
        }
        return data;
        
    }
    
    SetValueTest() throws InterruptedException {
        long start = 0L, end = 0L; 
        
        final ArrayList<MyObject> data1 = getData();
        
        start = System.nanoTime();
        testSetValueNoCheck(data1);
        end = System.nanoTime();
        System.out.println("testSetValueNoCheck: " + (end - start) / 1e6);
        
        final ArrayList<MyObject> data2 = getData();
        
        Thread.currentThread().sleep(1000);
        
        start = System.nanoTime();
        testSetValueWithCheckUsingStatic(data2);
        end = System.nanoTime();
        System.out.println("testSetValueWithCheckUsingStatic: " + (end - start) / 1e6);
        
        start = System.nanoTime();
        testSetValueWithCheckUsingStatic(data2);
        end = System.nanoTime();
        System.out.println("testSetValueWithCheckUsingStatic second time: " + (end - start) / 1e6);
        
        
        final ArrayList<MyObject> data3 = getData();
        
        Thread.currentThread().sleep(1000);         
        
        start = System.nanoTime();
        testSetValueWithCheckUsingLiteral(data3);
        end = System.nanoTime();
        System.out.println("testSetValueWithCheckUsingLiteral: " + (end - start) / 1e6);
        
        start = System.nanoTime();
        testSetValueWithCheckUsingLiteral(data3);
        end = System.nanoTime();
        System.out.println("testSetValueWithCheckUsingLiteral second time: " + (end - start) / 1e6);
        
    }
    
    void testSetValueNoCheck(List<MyObject> objs) {
        for (MyObject obj : objs) {
            obj.setValue(NewValue);
        }
    }
    
    void testSetValueWithCheckUsingLiteral(List<MyObject> objs) {
        for (MyObject obj : objs) {
            if (!obj.getValue().equals("new Value")) {
                obj.setValue("new Value");
            }
        }
    }       
    
    static final String NewValue = "new Value";
    
    void testSetValueWithCheckUsingStatic(List<MyObject> objs) {
        for (MyObject obj : objs) {
            if (!obj.getValue().equals(NewValue)) {
                obj.setValue(NewValue);
            }
        }
    }
    
}

SECOND ROUND

  • Changes:

--- changed the class so that and ran once per class run so that garbage collection\memory would not interfere with readings

--- sleep changed to 5 seconds to simulate actual scenario

--- ran each test 10 times with average as following:

testSetValueWithCheckUsingStatic: Average First Time: 27.728210 Second Time: 10.979410

testSetValueWithCheckUsingLiteral: Average First Time: 23.161670 Second Time: 12.896330

testSetValueNoCheckStatic: Average First Time: 20.294100 Second Time: 21.679260

testSetValueNoCheckLiteral: Average First Time: 29.137620 Second Time: 19.812040

    static class SetValueTest {
    class MyObject {
        private String _val;
        MyObject(final String val) {
            _val = val;
        }
        
        String getValue() {
            return _val;
        }
        
        void setValue(final String val) {
            _val = val;
        }
        
    }
    
    ArrayList<MyObject> getData() {
        
        final ArrayList<MyObject> data = new ArrayList<>();
        for(int i = 0; i < 1000000; ++i) {
            data.add(new MyObject(String.format("%d", i)));
        }
        return data;
        
    }
    
    private final static int SleepTime = 5000;
    private static final String NewValue = "new Value";
    
    SetValueTest(final int test) throws InterruptedException {

        final ArrayList<Double> firstTimes = new ArrayList<>();
        final ArrayList<Double> secondTimes = new ArrayList<>();
        String testName = "";
        final int times = 10;
        for (int i = 0; i < times; ++i) {
            double firstTime = 0, secondTime = 0;
            final ArrayList<MyObject> data = getData();
            switch (test) {
            case 1:
                testName = "testSetValueWithCheckUsingStatic";
                firstTime = testSetValueWithCheckUsingStatic(data);
                secondTime = testSetValueWithCheckUsingStatic(data);
                break;
            case 2:
                testName = "testSetValueWithCheckUsingLiteral";
                firstTime = testSetValueWithCheckUsingLiteral(data);
                secondTime = testSetValueWithCheckUsingLiteral(data);
                break;
            case 3:                 
                testName = "testSetValueNoCheckStatic";
                firstTime = testSetValueNoCheckStatic(data);
                secondTime = testSetValueNoCheckStatic(data);
                break;
            case 4:                 
                testName = "testSetValueNoCheckLiteral";
                firstTime = testSetValueNoCheckLiteral(data);
                secondTime = testSetValueNoCheckLiteral(data);
                break;
            }
            firstTimes.add(firstTime);
            secondTimes.add(secondTime);
        }
        
        double firstTimeTotal = 0, secondTimeTotal = 0;
        System.out.println("Test: " + testName);
        for (int time = 0; time < times; ++time) {
            System.out.println(String.format("First Time: %f Second Time: %f", firstTimes.get(time), secondTimes.get(time)));
            firstTimeTotal += firstTimes.get(time);
            secondTimeTotal += secondTimes.get(time);
        }
        System.out.println(String.format("Average First Time: %f Second Time: %f", firstTimeTotal / times, secondTimeTotal / times));
        
        
    }
    
    double toMilliseconds(final long start, final long end) {
        return ((end - start) / 1e6);
    }
    
    double testSetValueNoCheckStatic(List<MyObject> objs) throws InterruptedException {
        
        long start = 0L, end = 0L; 
        start = System.nanoTime();
        for (MyObject obj : objs) {
            obj.setValue(NewValue);
        }
        end = System.nanoTime();
        Thread.currentThread().sleep(SleepTime);
        return toMilliseconds(start, end);
        
    }
    
    double testSetValueNoCheckLiteral(List<MyObject> objs) throws InterruptedException {
        
        long start = 0L, end = 0L; 
        start = System.nanoTime();
        for (MyObject obj : objs) {
            obj.setValue("new Value");
        }
        end = System.nanoTime();
        Thread.currentThread().sleep(SleepTime);
        return toMilliseconds(start, end);          
        
    }
    
    
    double testSetValueWithCheckUsingLiteral(List<MyObject> objs) throws InterruptedException {
        long start = 0L, end = 0L; 
        start = System.nanoTime();
        
        for (MyObject obj : objs) {
            if (!obj.getValue().equals("new Value")) {
                obj.setValue("new Value");
            }
        }
        end = System.nanoTime();
        Thread.currentThread().sleep(SleepTime);
        return toMilliseconds(start, end);          
    }       

    
    double testSetValueWithCheckUsingStatic(List<MyObject> objs) throws InterruptedException {
        long start = 0L, end = 0L; 
        start = System.nanoTime();
        
        for (MyObject obj : objs) {
            if (!obj.getValue().equals(NewValue)) {
                obj.setValue(NewValue);
            }
        }
        end = System.nanoTime();
        Thread.currentThread().sleep(SleepTime);
        return toMilliseconds(start, end);          
        
    }
    
}

Actual output:

Test: testSetValueWithCheckUsingStatic First Time: 42.110700 Second Time: 21.398500 First Time: 37.834800 Second Time: 10.124700 First Time: 18.263600 Second Time: 8.967400 First Time: 16.655500 Second Time: 7.146000 First Time: 18.729900 Second Time: 8.753300 First Time: 17.513100 Second Time: 14.622800 First Time: 24.000300 Second Time: 9.534700 First Time: 61.357000 Second Time: 11.677700 First Time: 26.832500 Second Time: 10.160400 First Time: 13.984700 Second Time: 7.408600 Average First Time: 27.728210 Second Time: 10.979410

Test: testSetValueWithCheckUsingLiteral First Time: 54.417700 Second Time: 22.161600 First Time: 20.950000 Second Time: 14.621500 First Time: 31.008100 Second Time: 13.631600 First Time: 14.052500 Second Time: 10.490900 First Time: 20.904800 Second Time: 8.727700 First Time: 13.338300 Second Time: 13.519100 First Time: 18.740800 Second Time: 13.030500 First Time: 14.959100 Second Time: 15.166100 First Time: 25.593000 Second Time: 8.041300 First Time: 17.652400 Second Time: 9.573000 Average First Time: 23.161670 Second Time: 12.896330

Test: testSetValueNoCheckStatic First Time: 34.467700 Second Time: 33.953200 First Time: 15.307500 Second Time: 14.245300 First Time: 34.042700 Second Time: 31.824600 First Time: 11.989300 Second Time: 12.266800 First Time: 15.556500 Second Time: 24.501000 First Time: 12.314800 Second Time: 16.539600 First Time: 14.333300 Second Time: 14.500300 First Time: 11.876900 Second Time: 21.599900 First Time: 39.020400 Second Time: 31.754800 First Time: 14.031900 Second Time: 15.607100 Average First Time: 20.294100 Second Time: 21.679260

Test: testSetValueNoCheckLiteral First Time: 49.325300 Second Time: 27.528700 First Time: 26.355500 Second Time: 27.800100 First Time: 23.862700 Second Time: 15.576400 First Time: 25.133000 Second Time: 16.290500 First Time: 28.379000 Second Time: 18.192400 First Time: 83.170600 Second Time: 18.183800 First Time: 13.570100 Second Time: 14.341100 First Time: 11.793900 Second Time: 13.960000 First Time: 16.238900 Second Time: 30.233200 First Time: 13.547200 Second Time: 16.014200 Average First Time: 29.137620 Second Time: 19.812040

Upvotes: 1

Mark Lavin
Mark Lavin

Reputation: 1242

My vote would be option 1: it's shorter (ignoring the setValue2 call) and if it's an object, the assignment is just loading and storing a pointer, whereas the equal comparison could take a long time if the object is very big.

Upvotes: 1

Related Questions