0

I am following an online course about data structures and algorithms. In that course, the instructor tells that the time complexities of following ways are different.

Method 1:

 Declare:

                int arr[]------------>O(1)

 Instantiation:

                arr = new int[size]------>O(1)


 Initialization:
               arr[0]=0;------------>O(1)
                                     -------------->O(n)
               arr[1]=1;------------>O(1)

Method 2:

 Declaration,instantiation and initialization:

               int arr[]={10,20,30}---------------->O(1)

I need to know that by following the second method can we optimise our program and how can it possible to tell that it's having O(1) ,what's the difference between both these methods.

I mean that I think although the second method is having fewer steps it internally follows all the steps which are in the first method, so it can't be O(1) it's also O(n) , Iam I correct?

azro
  • 53,056
  • 7
  • 34
  • 70
  • Does it *actually* follow all the same steps? Can you think of any ways it might be different based on how the code is constructed? – Dave Newton Nov 30 '19 at 11:23
  • Is `new int[size]` really a constant-time operation? Maybe in C, where the contents of the new array are undefined, but in Java, the memory has to be initialized to all `0`, which will take some effort linear to the length of the array. – Thilo Nov 30 '19 at 11:55
  • @Thilo FWIW, it's implementation-dependent. [Many OSes actually provide 0-initialized blocks of memory ready to be given to any app](https://stackoverflow.com/questions/8029584/why-does-malloc-initialize-the-values-to-0-in-gcc), so it's not exactly true that Java *has* to have linear time for 0-initialized alloc. The edit [here](https://stackoverflow.com/questions/5640850/java-whats-the-big-o-time-of-declaring-an-array-of-size-n) (and the discussion in comments there) points exactly at this. –  Nov 30 '19 at 16:51
  • @vaxquis: I believe you are conflating a couple of things: Yes, many OSes will give you memory 0-initialized (mostly for security reasons), but that will not be O(1) as the OS needs to do the zeroing. It is possible for the JVM to initialize the backing array lazily (but that just defers the O(n) to later). And it is theoretically possible for special hardware to zero out whole regions in constant time, or to use something like the Mill CPU memory model, which can also allocate zeroed-out memory, but none of this is in actual use anywhere. So "implementation-dependent" is too easy an answer. – Thilo Nov 30 '19 at 23:55
  • @Thilo I won't argue with you, because you've a point that the time complexity usually amortizes to O(n) eventually - but, from JVM perspective, the actual amount of CPU time spent on `new int[size]` *can* be constant, even if it ain't in most practical situations. I just pointed out that, quote, `in Java, the memory has to be initialized to all 0, which [makes it] linear [time]` is not strictly true - or at least *doesn't have to be*, while still being OK with the spec. It's just a nitpick, and not something I want to die arguing about :) Still, *implementation-dependent* **is** an answer. –  Dec 01 '19 at 14:03
  • 1
    @Thilo depending on the scenario, the JVM is capable of eliminating the filling with default values, e.g. when the subsequent operations guaranty that these values are never seen, however, for all practical scenarios, this implies that the subsequent operation is some kind of filling with actual values and since that is an O(n) operation itself, it doesn’t matter whether the allocation is O(1) or O(n), the net result is always O(n). – Holger Dec 06 '19 at 16:45

2 Answers2

1

Java’s array initializer syntax is just syntactic sugar for the instructions of allocating an array and assigning a value to each element.

This can be easily verified by the following code:

import java.io.IOException;
import java.nio.file.Paths;

public class ArrayInitializer {
    public void form1() {
        int arr[] = new int[3];
        arr[0] = 10;
        arr[1] = 20;
        arr[2] = 30;
    }

    public void form2() {
      int arr[] = { 10, 20, 30 };
    }

    public static void main(String[] args) throws Exception {
        decompile();
    }

    private static void decompile() throws InterruptedException, IOException
    {
      new ProcessBuilder(Paths.get(System.getProperty("java.home"))
          .getParent().resolve(Paths.get("bin", "javap")).normalize().toString(),
          "-c", "-cp", System.getProperty("java.class.path"),
          ArrayInitializer.class.getName())
      .inheritIO()
      .start().waitFor();
    }

    private ArrayInitializer() {}
}

which prints

Compiled from "ArrayInitializer.java"
public class ArrayInitializer {
  public void form1();
    Code:
       0: iconst_3
       1: newarray       int
       3: astore_1
       4: aload_1
       5: iconst_0
       6: bipush        10
       8: iastore
       9: aload_1
      10: iconst_1
      11: bipush        20
      13: iastore
      14: aload_1
      15: iconst_2
      16: bipush        30
      18: iastore
      19: return

  public void form2();
    Code:
       0: iconst_3
       1: newarray       int
       3: dup
       4: iconst_0
       5: bipush        10
       7: iastore
       8: dup
       9: iconst_1
      10: bipush        20
      12: iastore
      13: dup
      14: iconst_2
      15: bipush        30
      17: iastore
      18: astore_1
      19: return

  public static void main(java.lang.String[])  throws java.lang.Exception;
    Code:
       0: invokestatic  #22                 // Method decompile:()V
       3: return
}

So, if the compiled code does not differ, there can’t be a performance difference caused by the form of the source code.

This doesn’t preclude runtime optimizations made by the JVM, e.g. when it detects that this is an allocation followed by filling with values whose evaluation can not fail in a way that would leak an uninitialized array. But these optimizations would apply in either case, as the source code form doesn’t matter.

A practical example is discussed in this article regarding the toArray method of collections, whose performance turns out to be dependent on the JVM’s ability to recognize an array allocation which is immediately followed by a copy operation overwriting the entire created array.

Holger
  • 285,553
  • 42
  • 434
  • 765
0

It is possible to initialize an array in constant time. I do not know which, if any, JVM implementation do it, but it is possible. Since this technique requires 3 times as much memory, it may very well not be used by any JVM implementation.

Assuming the JVM do not use this technique, then

arr = new int[size]

and

int arr[]={10,20,30}

will both run in O (n) (in the first case, all element in the array need to be zeroed).

But in case this (or a similar technique) is used, your instructor is correct.

Edit

Without fancy initialization technique, the complexity is O (n) in both case. But the constant factor is probably different. So I did a benchmark using jmh with 100 sized arrays (sorry, I was to lazy to write longer arrays). The result is below:

# Run complete. Total time: 00:20:13

Benchmark                                  Mode  Cnt         Score       Error  Units
ArrayInitBenchmark.initialize_and_set     thrpt  200  32341391,483 ± 46429,821  ops/s
ArrayInitBenchmark.initialize_only        thrpt  200  32523162,079 ± 34682,391  ops/s
ArrayInitBenchmark.initialize_with_zeros  thrpt  200  36267571,539 ± 34839,701  ops/s
  • initialize_and_set is the OP’s first method.
  • initialize_only is the OP’s second method.
  • initialize_with_zeros is a mere new int[size].

As you can see, the second method is a bit faster, but less than 1% faster than the first one (32.3 M ops/s vs 32.5 M ops/s). That is, on my machine, with oracle JDK 1.8.0_201. (testing with Java 13 would have required me to setup a new jmh project, while I had everything already setup for Java 8).

Étienne Miret
  • 6,448
  • 5
  • 24
  • 36
  • That addresses initialization, which isn't the only thing happening in the first code snippet. Whether or not an initialization optimization is used, there's both initialization and setting values, while in the second example the array can be initialized with the given values instead of having those values set later. – Dave Newton Nov 30 '19 at 12:11
  • @DaveNewton I assumed it was obvious to everyone that setting the values in the first first code snippet was *O (n)*. Or maybe, I’m missing something? – Étienne Miret Nov 30 '19 at 13:10
  • My point was that the instructor is correct whether or not any fancy initialization techniques are used. – Dave Newton Nov 30 '19 at 15:00
  • @DaveNewton ?? It seems me that without some fancy initialization techniques, initialization will be *O (n)* in both case. So the instructor is wrong saying it is *O (1)*. – Étienne Miret Nov 30 '19 at 15:25
  • OP states "time complexity" which I think I misread as "time". The time taken to initialize in the first example will be longer (pre-jit, and depends on implementation). This is my only point. – Dave Newton Nov 30 '19 at 15:30
  • @EtienneMiret So based on your logic arr=new int[size] should have a time complexity of O(n) instead of O(1), I'm I correct? Finally, I need to know that is the best practice to use the second method instead of using first in java(I mean kind of small arrays which we can initialize all the indexes)? –  Nov 30 '19 at 15:44
  • @HasinduDahanayake yes, `new int[size]` is *O (n)*, see https://stackoverflow.com/questions/5640850/java-whats-the-big-o-time-of-declaring-an-array-of-size-n. Regarding the best method, in my opinion it is the one you find easier to read. – Étienne Miret Nov 30 '19 at 16:41
  • 2
    FWIW, the "constant time" you've provided from the blog amortizes to linear time eventually, as soon as the amount of random accesses is big enough; if we can't treat the source data as sparse array, which is the usual case, the big O is still linear. –  Nov 30 '19 at 16:49
  • @EtienneMiret Thank you very much for your answer,but I need to know how can second method takes O(n) time ,because it's already initializing all the elements in the beginning no (I mean we initialize all the values ). –  Nov 30 '19 at 18:18
  • 1
    @DaveNewton the instructor may have a C/C++ background, as for Java, it’s completely wrong. See [my answer](https://stackoverflow.com/a/59217099/2711488), the difference between these source code forms ceases to exist at the bytecode level already, so any runtime performance difference is just noise. The JVM simply doesn’t know which form you used. Talking of “time complexity” makes no sense here, as long as you only use a fixed array length. You would need to perform tests with different length to see, how it *scales*. But anyway, looking at the bytecode is already enough anyway… – Holger Dec 06 '19 at 16:55
  • @Holger It doesn't know which you used for the declaration, but the second example isn't just declaration, it's declaration plus individual filling. The generated byte code is different, and the second will take longer. But yes, it'll be implementation-dependent. – Dave Newton Dec 06 '19 at 17:10
  • @DaveNewton did you read my answer? Both constructs compile to exactly the same “individual filling” instructions. – Holger Dec 06 '19 at 17:11
  • @Holger Hm, ok. Maybe I had the wrong Java selected when I compiled this--I have to switch between a resource-constrained largely-custom one and a "regular" one, so sorry about that if that's what happened. – Dave Newton Dec 06 '19 at 17:14
  • 1
    @DaveNewton the standard bytecode does not even have a construct for defining a pre-initialized array. Even if it had, since Java has no immutable arrays either, creating a defensive copy would be needed at the allocation site, which still would be an O(n) operation. – Holger Dec 06 '19 at 17:22