Adding numbers using Java Long wrapper and primitive lengths

I run this code and get unexpected results. I expect the loop that adds the primitives to work much faster, but the results will not agree.

import java.util.*; public class Main { public static void main(String[] args) { StringBuilder output = new StringBuilder(); long start = System.currentTimeMillis(); long limit = 1000000000; //10^9 long value = 0; for(long i = 0; i < limit; ++i){} long i; output.append("Base time\n"); output.append(System.currentTimeMillis() - start + "ms\n"); start = System.currentTimeMillis(); for(long j = 0; j < limit; ++j) { value = value + j; } output.append("Using longs\n"); output.append(System.currentTimeMillis() - start + "ms\n"); start = System.currentTimeMillis(); value = 0; for(long k = 0; k < limit; ++k) { value = value + (new Long(k)); } output.append("Using Longs\n"); output.append(System.currentTimeMillis() - start + "ms\n"); System.out.print(output); } } 

Conclusion:

Base Time 359ms Using Lengths 1842ms Using Longs 614ms

I tried to run every single test in my own Java program, but the results are the same. What can cause this?

Small detail: launch java 1.6

Edit: I asked 2 other people to try this code, to get the same exact results that I get. Another gets results that actually make sense! I asked a guy who got normal results to give us his cool binary. We run it and we still get weird results. The problem is not at compile time (I think). I am running 1.6.0_31, the guy who gets normal results is on 1.6.0_16, the guy who gets strange results, like me, is 1.7.0_04.

Edit: Get the same results with Thread.sleep (5000) at the beginning of the program. Also get the same results with a while loop around the entire program (to see if the time will converge to normal time after java is fully run)

+6
source share
3 answers

I suspect this is a JVM warming effect. In particular, the JIT code is compiled at some point, and this distorts the time you see.

Put the entire batch in a loop and ignore the time until they stabilize. (But note that they will not be fully stabilized. Garbage is generated, and therefore the GC will have to be hit periodically. This can distort the timings at least a little. The best way to handle this is to run a huge number of iterations of the outer loop and calculate / display the average time.)

Another problem is that the JIT compiler on some versions of Java can optimize the omissions you are trying to verify:

  • You can understand that the creation and immediate removal of Long objects could be optimized. (Thanks Louis!)

  • You can understand that loops do "busy work" ... and fully optimize them. (The value not used after the completion of each cycle.)


FWIW, it is generally recommended to use Long.valueOf(long) , rather than new Long(long) , because the former can use an instance of cached Long . However, in this case, we can predict that in all but the first few iterations of the loop, there will be no cache miss, so the recommendation will not help. In any case, this is likely to make the cycle in the question slower.


UPDATE

I conducted my own investigation and received the following:

 import java.util.*; public class Main { public static void main(String[] args) { while (true) { test(); } } private static void test() { long start = System.currentTimeMillis(); long limit = 10000000; //10^9 long value = 0; for(long i = 0; i < limit; ++i){} long t1 = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for(long j = 0; j < limit; ++j) { value = value + j; } long t2 = System.currentTimeMillis() - start; start = System.currentTimeMillis(); for(long k = 0; k < limit; ++k) { value = value + (new Long(k)); } long t3 = System.currentTimeMillis() - start; System.out.print(t1 + " " + t2 + " " + t3 + " " + value + "\n"); } } 

which gave me the following result.

 28 58 2220 99999990000000 40 58 2182 99999990000000 36 49 157 99999990000000 34 51 157 99999990000000 37 49 158 99999990000000 33 52 158 99999990000000 33 50 159 99999990000000 33 54 159 99999990000000 35 52 159 99999990000000 33 52 159 99999990000000 31 50 157 99999990000000 34 51 156 99999990000000 33 50 159 99999990000000 

Note that the first two columns are pretty stable, but the third shows significant acceleration in the third iteration ... probably indicates that the JIT compiled.

Interestingly, before I separated the test into a separate method, I did not see acceleration in the third iteration. All numbers looked like the first two lines. And that seems to indicate that the JVM (which I'm using) will not JIT compile the method that is currently executing ... or something like that.

In any case, this shows (to me) that there should be a warming effect. If you don’t see the warm-up effect, your test does what prevents the JIT from compiling ... and therefore does not make sense for real applications.

+4
source

I am also surprised.

My first guess would be the unintentional " autoboxing ", but this is clearly not a problem in your sample code.

This link may give the key:

http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Long.html

valueOf

public static Long valueOf (long l)

Returns a long instance representing the specified long value. If a new long instance is not required, this method should usually be used in preference to the Long (long) constructor, since this method is likely to significantly improve the caching space and time of frequently requested values.

 Parameters: l - a long value. Returns: a Long instance representing l. Since: 1.5 

But yes, I would expect to use a wrapper (such as Long) to take MORE time and MORE place. I would not expect the use of the wrapper to be FASTER three times!

==================================================== ================================

ADDITION:

I got these results with your code:

 Base time 6878ms Using longs 10515ms Using Longs 428022ms 

I am running JDK 1.6.0_16 on a 32-bit single-core pokey processor.

0
source

OK - here is a slightly different version along with my results (JDK 1.6.0_16 pokey 32-bit processor with one code works):

 import java.util.*; /* Test Base longs Longs/new Longs/valueOf ---- ---- ----- --------- ------------- 0 343 896 3431 6025 1 342 957 3401 5796 2 342 881 3379 5742 */ public class LongTest { private static int limit = 100000000; private static int ntimes = 3; private static final long[] base = new long[ntimes]; private static final long[] primitives = new long[ntimes]; private static final long[] wrappers1 = new long[ntimes]; private static final long[] wrappers2 = new long[ntimes]; private static void test_base (int idx) { long start = System.currentTimeMillis(); for (int i = 0; i < limit; ++i){} base[idx] = System.currentTimeMillis() - start; } private static void test_primitive (int idx) { long value = 0; long start = System.currentTimeMillis(); for (int i = 0; i < limit; ++i){ value = value + i; } primitives[idx] = System.currentTimeMillis() - start; } private static void test_wrappers1 (int idx) { long value = 0; long start = System.currentTimeMillis(); for (int i = 0; i < limit; ++i){ value = value + new Long(i); } wrappers1[idx] = System.currentTimeMillis() - start; } private static void test_wrappers2 (int idx) { long value = 0; long start = System.currentTimeMillis(); for (int i = 0; i < limit; ++i){ value = value + Long.valueOf(i); } wrappers2[idx] = System.currentTimeMillis() - start; } public static void main(String[] args) { for (int i=0; i < ntimes; i++) { test_base (i); test_primitive(i); test_wrappers1 (i); test_wrappers2 (i); } System.out.println ("Test Base longs Longs/new Longs/valueOf"); System.out.println ("---- ---- ----- --------- -------------"); for (int i=0; i < ntimes; i++) { System.out.printf (" %2d %6d %6d %6d %6d\n", i, base[i], primitives[i], wrappers1[i], wrappers2[i]); } } } 

==================================================== =======================

5.28.2012:

Below are some additional timings: from a faster (but still modest) dual-core processor running Windows 7/64 and running the same version of the JDK version 1.6.0_16:

 /* PC 1: limit = 100,000,000, ntimes = 3, JDK 1.6.0_16 (32-bit): Test Base longs Longs/new Longs/valueOf ---- ---- ----- --------- ------------- 0 343 896 3431 6025 1 342 957 3401 5796 2 342 881 3379 5742 PC 2: limit = 1,000,000,000, ntimes = 5,JDK 1.6.0_16 (64-bit): Test Base longs Longs/new Longs/valueOf ---- ---- ----- --------- ------------- 0 3 2 5627 5573 1 0 0 5494 5537 2 0 0 5475 5530 3 0 0 5477 5505 4 0 0 5487 5508 PC 2: "for loop" counters => long; limit = 10,000,000,000, ntimes = 5: Test Base longs Longs/new Longs/valueOf ---- ---- ----- --------- ------------- 0 6278 6302 53713 54064 1 6273 6286 53547 53999 2 6273 6294 53606 53986 3 6274 6325 53593 53938 4 6274 6279 53566 53974 */ 

You will notice:

  • I do not use StringBuilder, and I separate all inputs / outputs until the end of the program.

  • "long" primitive is sequentially equivalent to "no-op"

  • "Long" wrappers consistently much, much slower

  • "new Long ()" is slightly faster than "Long.valueOf ()"

  • Changing the loop counts from "int" to "long" makes the first two columns ("base" and "longs" much slower.

  • "JIT warmup" is negligible after the first few iterations ...

  • ... subject to I / O (e.g., System.out) and potentially memory-intensive operations (e.g., StringBuilder) are moved outside the real test sections.

0
source

Source: https://habr.com/ru/post/916771/


All Articles