0

I just did a small speed comparison between Java and Swift on filling a big array. I came up with following results:


Swift

import Foundation

let start = CFAbsoluteTimeGetCurrent()
var intArray = Int[]()
for var i = 0; i <= 300000; ++i {
    intArray.append(0)
}
let timeTaken = CFAbsoluteTimeGetCurrent() - start
println(timeTaken)

Result: 1.66182696819305


Java

long start = System.currentTimeMillis();
int[] intArray;
int i = 0;
intArray = new int[300000];
for (i = 0; i < 300000; i++) {
    intArray[i]=0;
}
System.out.println("Time: "+(System.currentTimeMillis()-start)+"ms");

Result: Time: 3ms


This was kind of shocking me;

  • is Swift 550x slower then Java.. or am I using some not-optimized code?
Community
  • 1
  • 1
El_Chippo
  • 143
  • 1
  • 8
  • 4
    I'd be pretty wary of your Java code because I don't think it'd be unreasonable for the JIT compiler to optimize the entire loop away, as you don't do anything with it. In addition, I'd suspect the Swift `append()` method has some extra behind-the-scenes work to do to tack on another element to an array, but as I don't know the language nearly as well I can't say for sure. In addition, the two snippets aren't totally equivalent, as you're not appending anything in the Java code. I wouldn't call this a fair comparison for sure. – awksp Jun 12 '14 at 21:22
  • I can agree on that. It pretty much sounds like the `append` function executes in a constant time, cause there is no index, where in the java code the time is linear. – Ivaylo Toskov Jun 12 '14 at 21:24
  • @IvayloToskov I don't think that's totally right, as setting an array element in Java is constant time. The only way for `append()` to execute in constant time is if `intArray` were a linked list, and that's definitely not the case. – awksp Jun 12 '14 at 21:25
  • @user3580294 It could be amortized constant without using a linked list or pre-allocating the entire memory block, but would obviously still be considerably slower. – Reinstate Monica Please Jun 12 '14 at 21:27
  • @BroSlow Good point, didn't take that into consideration – awksp Jun 12 '14 at 21:28
  • 3
    The two programs are not equivalent. Your conclusions are invalid. – Thorbjørn Ravn Andersen Jun 12 '14 at 21:28
  • 4
    A bit confused by the downvotes on this, erroneous conclusions != bad question. – Reinstate Monica Please Jun 12 '14 at 21:34
  • StackOverflow trolls downvoting. This is a valid Q, misguided, but the answer helps clarify a good point. – Taylor Halliday Oct 03 '14 at 20:00
  • [link](http://s28.postimg.org/ueff3pzr1/Screen_Shot_2015_09_02_at_21_12_32.png) with the highest optimization level possible in Swift 2 it takes 1ms. I think Java can achieve the same result. Even though Swift does the undesired thing filling the whole array with 0 at the beginning, but I think it the compiler will optimize this process, since it is unneeded in this scenario. – denis631 Sep 02 '15 at 19:16
  • [use `System.nanoTime()` to measure elapsed time in Java](http://stackoverflow.com/a/351571). – Franklin Yu May 24 '16 at 02:17

3 Answers3

32

You are comparing apples and pears..

There's a fundamental difference between your two implementations.

Your java implementation will allocate memory for all 300 000 elements at once, and then set the value of each element.

The swift implementation will however potentially resize the underlying storage on every iteration since you are appending an element, not just storing it at a particular location. Most probably a resize + copy of the old storage will not happen on every iteration, but it's a possibility; and it will certainly happen more than once during your loop.


How would I fix it?

To fix the swift implementation you should use an array-initializer to allocate required storage directly upon creating the intArray, such as in the below example:

var intArray = Int[](count: 300000, repeatedValue: 0)
for var i = 0; i <= 300000; ++i {
    intArray[i] = 0
}
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • 2
    With the *How would I fix it?* section, this answer should probably be marked as the accepted answer. – nhgrif Jun 12 '14 at 21:35
  • Even if you allocated the Swift array all at once, I'd still be wary of the Java code, due to the JIT compiler potentially optimizing things out. – awksp Jun 12 '14 at 21:37
  • @user3580294 that kind of optimization can be disabled, but it's irrelevant; he wanted to compare the semanticall equivalent code in both languages, and this is as close as he gets. – Filip Roséen - refp Jun 12 '14 at 21:39
  • 1
    You can indeed disable the JIT compiler, but you wouldn't really get a valid Swift vs. Java comparison because who disables the JIT compiler on a production Java machine? – awksp Jun 12 '14 at 21:41
  • @user3580294 so, what you are saying is that JIT is a part of *java*? cool, then the comparision still holds. – Filip Roséen - refp Jun 12 '14 at 21:42
  • 2
    But if the goal is to initialize every object in the array to a particular value, [tag:swift] does that with the single line statement. The loop in the code snippet is pointless. – nhgrif Jun 12 '14 at 21:43
  • @FilipRoséen-refp Only if you want every value to be 0. If you want every value to be 1, just send 1 as the `repeatedValue:` argument in Swift. In Java, you have to write the loop. – nhgrif Jun 12 '14 at 21:44
  • @nhgrif surely, but OP used `0` as the value for initialization.. with that only he knows. – Filip Roséen - refp Jun 12 '14 at 21:44
  • The point I'm making is that Java's allocation statement initializes to 0 always. Swift's allocation statement will let you initialize every index to any valid value. – nhgrif Jun 12 '14 at 21:46
  • No, I have no idea why you thought I said that, and you probably know just as well as me that the JIT compiler is in no way required by the language spec. If you disable the JIT compiler, then you won't be getting a valid indication of Java's performance, so the comparison would in fact not hold. – awksp Jun 12 '14 at 21:47
  • @user3580294 so you are saying that by using the optimization we cannot compare the two language, and if we disable it, we still can't compare the two languages? I'm not a big fan of comparing languages this way since it's hard to come up with a fully "equivalent" snippet, but saying that a optimization technique available in one makes a comparision useless is not something I agree with. – Filip Roséen - refp Jun 12 '14 at 21:49
  • OK, so I guess I'm going too broad. You won't get a valid comparison of the standard Swift runtime (well, idk about this because I have no idea what compilation settings are "standard") compared to the standard Java runtime. But you would get a valid benchmark, albeit a pretty useless one. I guess this goes back to the whole "languages don't have a speed, runtimes do" that I've sometimes heard... – awksp Jun 12 '14 at 21:54
  • @nhgrif In Java, you don't have to write the loop. If you want all the values to be 1, you can write `Arrays.fill(intArray, 1)`. – David Conrad Jun 12 '14 at 22:23
  • `i <= 300000` should be `i < 300000`. – Franklin Yu May 24 '16 at 02:33
8

append resizes the array at runtime (Each loop!!), whereas your Java code creates an array of size 300000 and just sets elements in it.

resizing implies a memory copy operation, whereas in your Java code setting an index is constant time.

also, the JIT compiler may have just decided your loop is useless and optimized the whole thing away.

David Xu
  • 5,555
  • 3
  • 28
  • 50
  • 6
    Are we certain the array is resized for each loop? I thought most implementations of `append` would only resize if needed, and when it does resize, it would double the current size. – nhgrif Jun 12 '14 at 21:26
  • understood, thanks for the replay. Is there any possibility to create an array with a fixed size, and add the items just like in the java code? I tried "intArray.insert(0, atIndex: i)" with an even worse result. – El_Chippo Jun 12 '14 at 21:34
  • 1
    you can use this var array = Int[](count: 300000, repeatedValue: 0) That will allocate enough room for you from the start. – David Xu Jun 12 '14 at 21:35
  • 1
    @El_Chippo You don't want to `insert`, that _would_ be worse! You just want a simple assignment: `intArray[i] = 0`. I'd actually suggest changing it to `intArray[i] = i` in both programs so the loop won't get optimized away. – pjs Jun 12 '14 at 23:12
2

I realise that this is pretty old, but I randomly came across it while DuckDucking for something unrelated and I figured I would provide some... update.

First of all, the syntax in Swift is fairly different now from when Filip Roséen answered. There are no more C-style loops, for example. There has also been some work done on the compiler.

Just like the earlier answers explain, you are comparing two different things (growing an array versus initialising an array). However, due to the various changes, Swift is now substantially faster than it was.

Please excuse my Java code if it's suboptimal; this is pretty much the first time I've written Java.

Here's the code I used to dynamically grow an array:

Java:

import java.util.List;
import java.util.ArrayList;

public class java_GrowArray {
    public static void main(String[] args) {
        long start = System.nanoTime();

        List<Integer> intArray = new ArrayList<Integer>();

        for (int i = 0; i < 300000; i++) {
            intArray.add(0);
        }
        System.out.println("Time: "+(float)(System.nanoTime()-start)/1000000000 +" s");

    }
}

Swift:

import Foundation

let start = CFAbsoluteTimeGetCurrent()

var intArray: [Int] = []
for _ in 0..<300000 {
    intArray.append(0)
}
let timeTaken = CFAbsoluteTimeGetCurrent() - start
print("Time: \(timeTaken) s")

And here's the code for just initialising a fixed-size array:

Java:

public class java_InitialiseArray {
    public static void main(String[] args) {
        long start = System.nanoTime();
        int[] intArray;
        int i = 0;
        intArray = new int[300000];
        for (i = 0; i < 300000; i++) {
            intArray[i]=0;
        }
        System.out.println("Time: "+(float)(System.nanoTime()-start)/1000000000 +" s");

    }
}

Swift:

import Foundation

let start = CFAbsoluteTimeGetCurrent()
let intArray = [Int](repeating: 0, count: 300000)
let timeTaken = CFAbsoluteTimeGetCurrent() - start
print("Time: \(timeTaken) s")

Timings (median of five runs each):

Grow array (Java):  0.01376 s
Grow array (Swift): 0.01035 s
Init array (Java):  0.00448 s
Init array (Swift): 0.00181 s

Clearly, the performance behaviour of Swift is very different now in 2018 than it was years ago.

Simon Lundberg
  • 1,413
  • 2
  • 11
  • 23