0

From some resources, I had come to know that under Java collection, ArrayList is faster than Vector. To check that, I was trying with a small program in which one method adds some data to an ArrayList and then retrieve those and the another method adds and retrieves same data to and from a Vector. Now the main() method calls two of them under two different time calculation process which calculates the execution time of those method separately. In the result, I am getting the execution time for ArrayList is 18385831 ns and for Vector is 2190242 ns. My project lead also said that the ArrayList is faster than Vector. But the result of my program is saying something different. Can anyone please explain me the correct thing with its reason? And also the reason of this result if ArrayList is really faster than the Vector.

This is my source code:

import java.util.*;

public class TestArraylist {

    void Arraylistpart() {
        ArrayList<Object> a1 = new ArrayList<Object>();
        ArrayList<Object> a2 = new ArrayList<Object>();
        a2 = a1;

        a1.add(1);
        a1.add('c');
        a1.add("gh");
        a1.add(2);
        a1.set(2, "ab");
        int count = 0;
        System.out.println(a1);
        try {
            for (Object i : a1) {
                a2.set(count, i.toString());
                count = count + 1;
            }
            a2.sort(null);
            System.out.println(a2);
        } catch (ClassCastException e) {
            System.err.println("Exception occurs");
        }
    }

    void vectorpart() {
        Vector<Object> v1 = new Vector<Object>();
        Vector<Object> v2 = new Vector<Object>();
        v2 = v1;
        v1.add(1);
        v1.add('c');
        v1.add("ab");
        v1.add(2);
        int count1 = 0;
        System.out.println(v1);
        try {
            for (Object i : v1) {
                v2.setElementAt(i.toString(), count1);
                count1 = count1 + 1;
            }
            v2.sort(null);
            System.out.println(v2);
        } catch (ClassCastException e) {
            System.err.println("Exception occurs");
        }
    }

    public static void main(String[] args) {
        TestArraylist objct = new TestArraylist();
        System.out.println("Arraylist program");
        long startmili = System.currentTimeMillis();
        long starttime = System.nanoTime();
        objct.Arraylistpart();
        long endtime = System.nanoTime();
        long endmili = System.currentTimeMillis();
        long totaltime = endtime - starttime;
        long totaltimemili = endmili - startmili;
        System.out.println(totaltime);
        System.out.println("Time in mili is: " + totaltimemili);

        System.out.println("\nVector program");
        long startmili1 = System.currentTimeMillis();
        long starttime1 = System.nanoTime();
        objct.vectorpart();
        long endtime1 = System.nanoTime();
        long endmili1 = System.currentTimeMillis();
        long totaltime1 = endtime1 - starttime1;
        long totaltimemili1 = endmili1 - startmili1;
        System.out.println(totaltime1);
        System.out.println("Time in mili is: " + totaltimemili1);
    }
}
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Samiran Banerjee
  • 23
  • 1
  • 1
  • 8
  • Please post the full source code of your performance test and we will say what's wrong with it (edit your question to do this). – Tagir Valeev Mar 04 '16 at 05:40
  • From the Stackoverflow.com, I have also come to know that Arraylist is (20 - 30)% faster than Vector. Though there is not results provided to support the answer. – Samiran Banerjee Mar 04 '16 at 05:41
  • Post your benchmark code please. Benchmarking Java a very non-trivial thing and its easy to do something wrong that will alter the results. – MartinS Mar 04 '16 at 05:43
  • @Tagir Valeev, This is my source code. – Samiran Banerjee Mar 04 '16 at 05:43
  • 1
    @SamiranBanerjee, ... where? – Tagir Valeev Mar 04 '16 at 05:46
  • This is the Method for Vector void vectorpart(){ Vector v1 = new Vector(); Vector v2 = new Vector(); v2=v1; v1.add(1); v1.add('c'); v1.add("ab"); v1.add(2); int count1=0; System.out.println(v1); try{ for (Object i : v1){ v2.setElementAt(i.toString(), count1); count1=count1 + 1; } v2.sort(null); System.out.println(v2); } catch(ClassCastException e){ System.err.println("Exception occurs"); } } – Samiran Banerjee Mar 04 '16 at 05:57
  • This is the Method for Arraylist void Arraylistpart(){ ArrayList a1 = new ArrayList(); ArrayList a2 = new ArrayList(); a2 = a1; a1.add( 1); a1.add( 'c'); a1.add( "gh"); a1.add( 2); a1.set(2, "ab"); int count=0; System.out.println(a1); try{ for (Object i : a1){ a2.set(count, i.toString()); count=count+1; } a2.sort(null); System.out.println(a2); } catch(ClassCastException e){ System.err.println("Exception occurs"); } } – Samiran Banerjee Mar 04 '16 at 05:58
  • @SamiranBanerjee, use "edit" button under your question and put your code directly into question. Comments are not suitable for the code. I added the posted methods for you, but the time measurement code is still missing. Please post the *full code*. – Tagir Valeev Mar 04 '16 at 05:59
  • @Tagir Valeev, Thanks for your suggestion, I have edited my question there, you may refer that. – Samiran Banerjee Mar 04 '16 at 06:09
  • @SamiranBanerjee your benchmark is flawed, just try excuting the `Vector` part first and then the `ArrayList` part – MartinS Mar 04 '16 at 06:11
  • @MartinS, Thanks. Yes it is now working. Now I am getting 726366 ns for Vector and 164803 ns for Arraylist. But, to get actual result, why is it important to call Vector before Arraylist? – Samiran Banerjee Mar 04 '16 at 06:18
  • @SamiranBanerjee it's not important, it just shows that your benchmark is broken and that you can show pretty much anything with it. benchmarking Java code needs a lot of experience and knowledge of how the JVM internally works. – MartinS Mar 04 '16 at 06:19
  • @MartinS, yea its correct. But if I use the main() method only to execute the code, i.e., if I execute the complete operation in main() method, then also it is showing the same result what I was getting previously. – Samiran Banerjee Mar 04 '16 at 06:29
  • @SamiranBanerjee see my answer, it was too long for a comment – MartinS Mar 04 '16 at 06:41
  • @MartinS, Thanks. I am a beginner in this field. I'm trying to understand those things. – Samiran Banerjee Mar 04 '16 at 06:50

2 Answers2

2

Your benchmark is wrong. First, you've measured not only the time of array/vector-based operations, but also time of printing which could be several magnitudes slower than everything else. Second, you did no warmup, so most of your code is likely to be executed by interpreter, not JIT-compiled. Third, you launch two tests in the same JVM, putting them into unequal conditions: during the first test execution JVM does more warm-up steps (e.g. JIT compilation), so the first test is handicapped from the very start. Let's try to write somewhat equivalent using the JMH:

import org.openjdk.jmh.annotations.*;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Warmup(iterations = 20, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 20, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class TestArraylist {

    @Benchmark
    public String Arraylistpart() {
        ArrayList<Object> a1 = new ArrayList<>();
        ArrayList<Object> a2 = new ArrayList<>();
        a2 = a1;

        a1.add(1);
        a1.add('c');
        a1.add("gh");
        a1.add(2);
        a1.set(2, "ab");
        int count = 0;
        for (Object i : a1) {
            a2.set(count, i.toString());
            count = count + 1;
        }
        a2.sort(null);
        return a1.toString()+a2.toString();
    }

    @Benchmark
    public String vectorpart() {
        Vector<Object> v1 = new Vector<>();
        Vector<Object> v2 = new Vector<>();
        v2 = v1;
        v1.add(1);
        v1.add('c');
        v1.add("ab");
        v1.add(2);
        int count1 = 0;
        for (Object i : v1) {
            v2.setElementAt(i.toString(), count1);
            count1 = count1 + 1;
        }
        v2.sort(null);
        return v1.toString()+v2.toString();
    }
}

I returned a1.toString()+a2.toString() instead of printing to preserve the .toString() calls (which were done in your test inside System.out.println).

The summary result is the following:

Benchmark                    Mode  Cnt  Score   Error  Units
TestArraylist.Arraylistpart  avgt   20  0,382 ± 0,003  us/op
TestArraylist.vectorpart     avgt   20  0,421 ± 0,002  us/op

See, your tests actually performed much, much faster. Only 382 nanoseconds and 421 nanoseconds. Vector is indeed somewhat slower, due to additional synchronization. But seems that C2 JIT compiler did a good job removing some unnecessary synchronization sections, so the time difference is not very big. It's also interesting to check the per-iteration stats. For ArrayList:

# Warmup Iteration   1: 0,544 us/op
# Warmup Iteration   2: 0,471 us/op
# Warmup Iteration   3: 0,383 us/op
# Warmup Iteration   4: 0,377 us/op
# Warmup Iteration   5: 0,377 us/op
# Warmup Iteration   6: 0,373 us/op
...
Iteration  14: 0,374 us/op
Iteration  15: 0,376 us/op
Iteration  16: 0,381 us/op
Iteration  17: 0,376 us/op
Iteration  18: 0,379 us/op
Iteration  19: 0,383 us/op
Iteration  20: 0,385 us/op

For Vector:

# Warmup Iteration   1: 0,889 us/op
# Warmup Iteration   2: 0,630 us/op
# Warmup Iteration   3: 0,689 us/op
# Warmup Iteration   4: 0,662 us/op
# Warmup Iteration   5: 0,671 us/op
# Warmup Iteration   6: 0,673 us/op
# Warmup Iteration   7: 0,669 us/op
# Warmup Iteration   8: 0,657 us/op
# Warmup Iteration   9: 0,427 us/op
# Warmup Iteration  10: 0,421 us/op
# Warmup Iteration  11: 0,421 us/op
...
Iteration  17: 0,423 us/op
Iteration  18: 0,420 us/op
Iteration  19: 0,422 us/op
Iteration  20: 0,419 us/op

As you can see, ArrayList reaches the full speed on iteration#3, while Vector reaches it on iteration#9.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • The JVM & JIT seem to be pretty good at figuring out if multiple threads can execute the code at the same time and if not the synchronization just gets optimized away (modulo some remaining overhead). – MartinS Mar 04 '16 at 06:46
  • @MartinS, it's a part of method inlining and escape analysis. If the object was created in the current method, stored into local variable and did not escape yet to some unknown heap location, then other threads cannot see it (as in Java, local variables cannot be seen by other threads). So synchronizations could be removed. – Tagir Valeev Mar 04 '16 at 06:52
  • I think there is also something more going on because it does not match what I observed. In my case the main-thread and one other thread could "see" the object but only the other thread was working on it. Yet the synchronization got removed. But when using a second thread to work on it, it used synchronization. – MartinS Mar 04 '16 at 06:57
  • @MartinS, even if synchronization is present in the JIT-compiled code, there're thin locks and fat locks. When you have no contention, thin lock is created which is very fast (probably like single uncontended CAS write). When contention (situation when two threads actually tried to lock the same object at the same time) occurs, thin lock is converted into fat lock which is much slower. – Tagir Valeev Mar 04 '16 at 07:01
  • Do you have any material on all that? – MartinS Mar 04 '16 at 07:02
  • @TagirValeev, I was executing the code you modified and uploaded. When I am calling the benchmarks, then also it is giving the same output as previous. Can you please explain a little bit? – Samiran Banerjee Mar 04 '16 at 10:49
  • @SamiranBanerjee, How did you build and execute it? – Tagir Valeev Mar 04 '16 at 10:54
  • @TagirValeev, I first have built the jmh annotations and using those, I just update my code as per your suggestion. Then called those two in the main() method. – Samiran Banerjee Mar 04 '16 at 11:18
  • @SamiranBanerjee, you need to process annotations properly during the build. It's better to build via maven archetype following the instructions on the official site. Main method will be generated automatically. – Tagir Valeev Mar 04 '16 at 12:55
  • @TagirValeev, can you please explain how can I get the output exactly same with what you have got? And what is the advantage of using "return a1.toString()+a2.toString();" where as by returning only "a2.toString()" I can get the exact output. – Samiran Banerjee Mar 04 '16 at 13:41
0

There are multiple things wrong with your benchmark which makes it look like Vector is faster than ArrayList. To see that it is broken, just execute the Vector benchmkar before you execute the ArrayList benchmark and suddenly you get the inverse results.

Here a list of things you have to keep in mind when benchmarking Java code:

Warm up the code - never benchmark cold code: The JIT optimizer does some pretty amazing things at runtime to optimize the Java code. But it does that only after the code got executed multiple times. The performance difference can be very significant. So execute your benchmark methods a few times without measuring the time to allow the JIT compiler to do its work. It also makes sure that all classes you need have been loaded and that this time is not counted towards the runtime. Your benchmark does not do that.

Make sure the benchmark runs long enough: In your benchmark, just 5 objects get added to the Vector/ArrayList. This code executes in about 1ms which is too short to get any usefull results. If your benchmark does not run long enough, "random" effects (i.e. scheduling of threads) the will influence the results too much. Try adding a few million elements.

Make sure you consume the result of your benchmark code: Imagine that you benchmark how long it takes to sum up 1 million values but you do not do anything with the result. The JIT optimizer might figure out that you do not need it and throw away the entire computation which renders the benchmark useless. Make sure you consume the result e.g. by printing it.

There are some more things to consider if you want to get really serious about it, but these are maybe the most important ones.

You might want to take a look at some frameworks that help with benchmarking like JMH.

MartinS
  • 2,759
  • 1
  • 15
  • 25