Tuning Java 7 to match performance of Java 6 - java

We have a simple unit test as part of our performance test suite which we use to verify that the base system is sane and performs before we even start testing our code. This way we usually verify that a machine is suitable for running actual performance tests.
When we compare Java 6 and Java 7 using this test, Java 7 takes considerably longer to execute! We see an average of 22 seconds for Java 6 and 24 seconds for Java 7. The test only computes fibonacci, so only bytecode execution in a single thread should be relevant here and not I/O or anything else.
Currently we run it with default settings on Windows with or without "-server", with both 32 and 64 bit JVM, all runs indicate a similar degradation for Java 7.
Which tuning options may be suitable here to try to match Java 7 against Java 6?
public class BaseLinePerformance {
#Before
public void setup() throws Exception{
fib(46);
}
#Test
public void testBaseLine() throws Exception {
long start = System.currentTimeMillis();
fib(46);
fib(46);
System.out.println("Time: " + (System.currentTimeMillis() - start));
}
public static void fib(final int n) throws Exception {
for (int i = 0; i < n; i++) {
System.out.println("fib(" + i + ") = " + fib2(i));
}
}
public static int fib2(final int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fib2(n - 2) + fib2(n - 1);
}
}
Update: I have reduced the test to not do any sleeps and followed the other suggestions from How do I write a correct micro-benchmark in Java?, I still see the same difference between Java 7 and Java 6, additional JVM options to print compilation and GC do not show any output during the actual test, only initially compilation information is printed.

One of my colleagues found out the reason for this after a bit more digging:
There is a JVM flag -XX:MaxRecursiveInlineLevel which has a default value of 1. It seems the handling of this setting was slightly incorrect in previous versions, so Sun/Oracle "fixed" this in Java 7, however it has the side-effect that sometimes the inlining now is done less aggressively and thus pure runtime/CPU time of recursive code can be longer than before.
We are testing setting it to 2 to get the same behavior as in Java 6 at least for the test in question.

This is not an easy answer, there are plenty of things that can account for those 2 seconds.
I am assuming for your comments that you are already familiar with micro benchmarking and that your benchmark is being run after warming up the JVM having your code reach an optimized JIT state and no GCs happening, also assuming that your hardware setup has not changed.
I would recommend CPU profiling your benchmark, that will help you identify where those two seconds are being accounted and perhaps act accordingly.
If you are curious about the bytecode you can take a peek at it.
To do this you can compile your class and do javap -c ClassName on both machines, this will disassemble the class file bytecode and show it to you, here you will surely see changes between both compiled classes.
In conclusion, profile and tune your application accordingly to reach 22 seconds after looking at the data, there is nothing you can do anyways about the bytecode implementation.

Related

general concept-java code and cycle clocks

I am just curious how can we know how many cycle clocks CPU needs by looking through certain java code.
ex:
public class Factorial
{
public static void main(String[] args)
{ final int NUM_FACTS = 100;
for(int i = 0; i < NUM_FACTS; i++)
System.out.println( i + "! is " + factorial(i));
}
public static int factorial(int n)
{ int result = 1;
for(int i = 2; i <= n; i++)
result *= i;
return result;
}
}
I am just curious how can we know how many cycle clocks CPU needs by looking through certain java code.
If you are talking about real hardware clock cycles, the answer is "You can't know"1.
The reason that it is so hard is that a program goes through a number of complicated (and largely opaque) transformations before and during execution:
The source code is compiled to bytecodes ahead of time. This depends on the bytecode compiler used.
The bytecodes are JIT compiled to native code, at some time during the execution. This depends on the JIT compiler in the execution platform AND on the execution behavior of the application.
The number of clock cycles taken to execute a given instruction sequence depends on native code, the CPU model including such things as memory cache sizes and ... the application's memory access patterns.
On top of that, the JVM has various sources of "under the hood" non-determinism, and various launch-time tuning parameters that influence the behavior ... and cycle counts.
But fortunately, there are practical ways to examine software performance that don't depend on measuring hardware clock cycles. You can:
measure or estimate native instructions executed,
measure or estimate bytecodes executed,
estimate Java-level operations or statements executed, or
run the code and measure the time taken.
The last two are generally the most practical.
1 - ... except by running the application / JVM on an accurate hardware-level simulator for your exact hardware configuration and getting the simulator to count the clock cycles. And to be honest, I don't know if simulators that operate to that level actually exist. If they do, they are most likely proprietary to Intel, AMD and so on.
I don't think you'd be able to know the clock cycles.
But you could measure the CPU time it took to run the code.
You'd need to use the java.lang.management API.
Take a look here:
http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/management/ThreadMXBean.html

JamVM on Motorola FX9500 Problems - what should I do?

I am using a Motorola FX9500 RFID reader, which runs Linux with the jamvm version 1.5.0 on it (I can only deploy applications to it - I cannot change the Java VM or anything so my options are limited) - here's what I see when I check the version:
[cliuser#FX9500D96335 ~]$ /usr/bin/jamvm -version
java version "1.5.0"
JamVM version 1.5.4
Copyright (C) 2003-2010 Robert Lougher <rob#jamvm.org.uk>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2,
or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Build information:
Execution Engine: inline-threaded interpreter with stack-caching
Compiled with: gcc 4.2.2
Boot Library Path: /usr/lib/classpath
Boot Class Path: /usr/local/jamvm/share/jamvm/classes.zip:/usr/share/classpath/glibj.zip
I need to write an application so I grabbed the Oracle Java SDK 1.5.0 and installed it onto my Windows 7 PC, so it has this version:
C:\>javac -version
javac 1.5.0
Am I being too idealistic in considering that an application I compile with that compiler would work correctly on the aforementioned JamVM? Anyway, pressing on in ignorance I write this little application:
public final class TestApp {
public static void main(final String[] args) {
long p = Long.MIN_VALUE;
int o = (int)(-(p + 10) % 10);
System.out.println(o);
}
}
Compile it with the aforementioned javac compiler and run it on the PC like so:
C:\>javac TestApp.java
C:\>java TestApp
8
All fine there. Life is good, so I take that .class file and place it on the FX9500 and run it like so:
[cliuser#FX9500D96335 ~]$ /usr/bin/jamvm TestApp
-2
Eek, what the...as you can see - it returns a different result.
So, why and who's wrong or is this something like the specification is not clear about how to deal with this calculation (surely not)? Could it be that I need to compile it with a different compiler?
Why Do I Care About This?
The reason I came to this situation is that a calculation exactly like that happens inside java.lang.Long.toString and I have a bug in my real application where I am logging out a long and getting a java.lang.ArrayIndexOutOfBoundsException. Because the value I am wanting to log may very well be at the ends of a Long.
I think I can work around it by checking for Long.MIN_VALUE and Long.MAX_VALUE and logging "Err, I can't tell you the number but it is really Long.XXX, believe me, would I lie to you?". But when I find this, I feel like my application is built on a sandy foundation now and it needs to be really robust. I am seriously considering just saying that JamVM is not up to the job and writing the application in Python (since the reader also has a Python runtime).
I'm kind of hoping that someone tells me I'm a dullard and I should have compiled it on my Windows PC like .... and then it would work, so please tell me that (if it is true, of course)!
Update
Noofiz got me thinking (thanks) and I knocked up this additional test application:
public final class TestApp2 {
public static void main(final String[] args) {
long p = Long.MIN_VALUE + 10;
if (p != -9223372036854775798L) {
System.out.println("O....M.....G");
return;
}
p = -p;
if (p != 9223372036854775798L) {
System.out.println("W....T.....F");
return;
}
int o = (int)(p % 10);
if (o != 8) {
System.out.println("EEEEEK");
return;
}
System.out.println("Phew, that was a close one");
}
}
I, again, compile on the Windows machine and run it.
It prints Phew, that was a close one
I copy the .class file to the contraption in question and run it.
It prints...
...wait for it...
W....T.....F
Oh dear. I feel a bit woozy, I think I need a cup of tea...
Update 2
One other thing I tried, that did not make any difference, was to copy the classes.zip and glibj.zip files off of the FX9500 to the PC and then do a cross compile like so (that must mean the compiled file should be fine right?):
javac -source 1.4 -target 1.4 -bootclasspath classes.zip;glibj.zip -extdirs "" TestApp2.java
But the resulting .class file, when run on the reader prints the same message.
I wrote JamVM. As you would probably guess, such errors would have been noticed by now, and JamVM wouldn't pass even the simplest of test suites with them (GNU Classpath has its own called Mauve, and OpenJDK has jtreg). I regularly run on ARM (the FX9500 uses a PXA270 ARM) and x86-64, but various platforms get tested as part of IcedTea.
So I haven't much of a clue as to what's happened here. I would guess it only affects Java longs as these are used infrequently and so most programs work. JamVM maps Java longs to C long longs, so my guess would be that the compiler used to build JamVM is producing incorrect code for long long handling on the 32-bit ARM.
Unfortunately there's not much you can do (apart from avoid longs) if you can't replace the JVM. The only thing you can do is try and turn the JIT off (a simple code-copying JIT, aka inline-threading). To do this use -Xnoinlining on the command line, e.g.:
jamvm -Xnoinlining ...
The problem is in different modulus implementations:
public static long mod(long a, long b){
long result = a % b;
if (result < 0)
{
result += b;
}
return result;
}
this code returns -2, while this:
public static long mod2(long a, long b){
long result = a % b;
if (result > 0 && a < 0)
{
result -= b;
}
return result;
}
returns 8. Reasons why JamVM is doing this way are behind my understanding.
From JLS:
15.17.3. Remainder Operator %
The remainder operation for operands that are integers after binary
numeric promotion (§5.6.2) produces a result value such that
(a/b)*b+(a%b) is equal to a.
According to this JamVM breaks language specification. Very bad.
I would have commented, but for some reason, that requires reputation.
Long negation doesn't work on this device. I don't understand its exact nature, but if you do two unary minuses you do get back to where you started, e.g. x=10; -x==4294967286; -x==10. 4294967286 is very close to Integer.MAX_VALUE*2 (2147483647*2 = 4294967294). It's even closer to Integer.MAX_VALUE*2-10!
It seems to be isolated to this one operation, and doesn't affect longs in a further fundamental way. It's simple to avoid the operation in your own code, and with some dextrous abuse of the bootclasspath can avoid the calls in GNU Classpath code, replacing them with *-1s. If you need to start your application from the device GUI, you can include the -Xbootclasspath=... switch into the args parameter for it to be passed to JamVM).
The bug is actually already fixed in latter (than the latest release) JamVM code:
* https://github.com/ansoncat/jamvm/commit/736c2cb76baf1fedddc1eda5825908f5a0511373
* https://github.com/ansoncat/jamvm/commit/ac83bdc886ac4f6e60d684de1b4d0a5e90f1c489
though doesn't help us with the fixed version on the device. Rob Lougher has mentioned this issue as a reason for releasing a new version of JamVM, though I don't know when this would be, or whether Motorola would be enough convinced to update their firmware.
The FX9500 is actually a repackaged Sirit IN610, meaning that both devices share this bug. Sirit are way friendlier that Motorola and are providing a firmware upgrade, to be available in the near future. I hope that Motorola will also include the fix, though I don't know the details of the arrangement between the two parties.
Either way, we have a very big application running on the FX9500, and the long negation operation hasn't proved to be an impassable barrier.
Good luck, Dan.

Stack performance in programming languages

Just for fun, I tried to compare the stack performance of a couple of programming languages calculating the Fibonacci series using the naive recursive algorithm. The code is mainly the same in all languages, i'll post a java version:
public class Fib {
public static int fib(int n) {
if (n < 2) return 1;
return fib(n-1) + fib(n-2);
}
public static void main(String[] args) {
System.out.println(fib(Integer.valueOf(args[0])));
}
}
Ok so the point is that using this algorithm with input 40 I got these timings:
C: 2.796s
Ocaml: 2.372s
Python: 106.407s
Java: 1.336s
C#(mono): 2.956s
They are taken in a Ubuntu 10.04 box using the versions of each language available in the official repositories, on a dual core intel machine.
I know that functional languages like ocaml have the slowdown that comes from treating functions as first order citizens and have no problem to explain CPython's running time because of the fact that it's the only interpreted language in this test, but I was impressed by the java running time which is half of the c for the same algorithm! Would you attribute this to the JIT compilation?
How would you explain these results?
EDIT: thank you for the interesting replies! I recognize that this is not a proper benchmark (never said it was :P) and maybe I can make a better one and post it to you next time, in the light of what we've discussed :)
EDIT 2: I updated the runtime of the ocaml implementation, using the optimizing compiler ocamlopt. Also I published the testbed at https://github.com/hoheinzollern/fib-test. Feel free to make additions to it if you want :)
You might want to crank up the optimisation level of your C compiler. With gcc -O3, that makes a big difference, a drop from 2.015 seconds to 0.766 seconds, a reduction of about 62%.
Beyond that, you need to ensure you've tested correctly. You should run each program ten times, remove the outliers (fastest and slowest), then average the other eight.
In addition, make sure you're measuring CPU time rather than clock time.
Anything less than that, I would not consider a decent statistical analysis and it may well be subject to noise, rendering your results useless.
For what it's worth, those C timings above were for seven runs with the outliers taken out before averaging.
In fact, this question shows how important algorithm selection is when aiming for high performance. Although recursive solutions are usually elegant, this one suffers from the fault that you duplicate a lot of calculations. The iterative version:
int fib(unsigned int n) {
int t, a, b;
if (n < 2) return 1;
a = b = 1;
while (n-- >= 2) {
t = a + b;
a = b;
b = t;
}
return b;
}
further drops the time taken, from 0.766 seconds to 0.078 seconds, a further reduction of 89% and a whopping reduction of 96% from the original code.
And, as a final attempt, you should try out the following, which combines a lookup table with calculations beyond a certain point:
static int fib(unsigned int n) {
static int lookup[] = {
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352,
24157817, 39088169, 63245986, 102334155, 165580141 };
int t, a, b;
if (n < sizeof(lookup)/sizeof(*lookup))
return lookup[n];
a = lookup[sizeof(lookup)/sizeof(*lookup)-2];
b = lookup[sizeof(lookup)/sizeof(*lookup)-1];
while (n-- >= sizeof(lookup)/sizeof(*lookup)) {
t = a + b;
a = b;
b = t;
}
return b;
}
That reduces the time yet again but I suspect we're hitting the point of diminishing returns here.
You say very little about your configuration (in benchmarking, details are everything: commandlines, computer used, ...)
When I try to reproduce for OCaml I get:
let rec f n = if n < 2 then 1 else (f (n-1)) + (f (n-2))
let () = Format.printf "%d#." (f 40)
$ ocamlopt fib.ml
$ time ./a.out
165580141
real 0m1.643s
This is on an Intel Xeon 5150 (Core 2) at 2.66GHz. If I use the bytecode OCaml compiler ocamlc on the other hand, I get a time similar to your result (11s). But of course, for running a speed comparison, there is no reason to use the bytecode compiler, unless you want to benchmark the speed of compilation itself (ocamlc is amazing for speed of compilation).
One possibility is that the C compiler is optimizing on the guess that the first branch (n < 2) is the one more frequently taken. It has to do that purely at compile time: make a guess and stick with it.
Hotspot gets to run the code, see what actually happens more often, and reoptimize based on that data.
You may be able to see a difference by inverting the logic of the if:
public static int fib(int n) {
if (n >= 2) return fib(n-1) + fib(n-2);
return 1;
}
It's worth a try, anyway :)
As always, check the optimization settings for all platforms, too. Obviously the compiler settings for C - and on Java, try using the client version of Hotspot vs the server version. (Note that you need to run for longer than a second or so to really get the full benefit of Hotspot... it might be interesting to put the outer call in a loop to get runs of a minute or so.)
I can explain the Python performance. Python's performance for recursion is abysmal at best, and it should be avoided like the plague when coding in it. Especially since stack overflow occurs by default at a recursion depth of only 1000...
As for Java's performance, that's amazing. It's rare that Java beats C (even with very little compiler optimization on the C side)... what the JIT might be doing is memoization or tail recursion...
Note that if the Java Hotspot VM is smart enough to memoise fib() calls, it can cut down the exponentional cost of the algorithm to something nearer to linear cost.
I wrote a C version of the naive Fibonacci function and compiled it to assembler in gcc (4.3.2 Linux). I then compiled it with gcc -O3.
The unoptimised version was 34 lines long and looked like a straight translation of the C code.
The optimised version was 190 lines long and (it was difficult to tell but) it appeared to inline at least the calls for values up to about 5.
With C, you should either declare the fibonacci function "inline", or, using gcc, add the -finline-functions argument to the compile options. That will allow the compiler to do recursive inlining. That's also the reason why with -O3 you get better performance, it activates -finline-functions.
Edit You need to at least specify -O/-O1 to have recursive inlining, also if the function is declared inline. Actually, testing myself I found that declaring the function inline and using -O as compilation flag, or just using -O -finline-functions, my recursive fibonacci code was faster than with -O2 or -O2 -finline-functions.
One C trick which you can try is to disable the stack checking (i e built-in code which makes sure that the stack is large enough to permit the additional allocation of the current function's local variables). This could be dicey for a recursive function and indeed could be the reason behind the slow C times: the executing program might well have run out of stack space which forces the stack-checking to reallocate the entire stack several times during the actual run.
Try to approximate the stack size you need and force the linker to allocate that much stack space. Then disable stack-checking and re-make the program.

Iteration speed of int vs long

I have the following two programs:
long startTime = System.currentTimeMillis();
for (int i = 0; i < N; i++);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " msecs");
and
long startTime = System.currentTimeMillis();
for (long i = 0; i < N; i++);
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime) + " msecs");
Note: the only difference is the type of the loop variable (int and long).
When I run this, the first program consistently prints between 0 and 16 msecs, regardless of the value of N. The second takes a lot longer. For N == Integer.MAX_VALUE, it runs in about 1800 msecs on my machine. The run time appears to be more or less linear in N.
So why is this?
I suppose the JIT-compiler optimizes the int loop to death. And for good reason, because obviously it doesn't do anything. But why doesn't it do so for the long loop as well?
A colleague thought we might be measuring the JIT compiler doing its work in the long loop, but since the run time seems to be linear in N, this probably isn't the case.
I'm using JDK 1.6.0 update 17:
C:\>java -version
java version "1.6.0_17"
Java(TM) SE Runtime Environment (build 1.6.0_17-b04)
Java HotSpot(TM) 64-Bit Server VM (build 14.3-b01, mixed mode)
I'm on Windows XP Professional x64 Edition, Service Pack 2, with an Intel Core2 Quad CPU at 2.40GHz.
DISCLAIMER
I know that microbenchmarks aren't useful in production. I also know that System.currentTimeMillis() isn't as accurate as its name suggests. This is just something I noticed while fooling around, and I was simply curious as to why this happens; nothing more.
It's an interesting question, but to be honest I'm not convinced that considering Hotspot's behaviour here will yield useful information. Any answers you do get are not going to be transferable in a general case (because we're looking at the optimisations that Hotspot performs in one specific situation), so they'll help you understand why one no-op is faster than another, but they won't help you write faster "real" programs.
It's also incredibly easy to write very misleading micro benchmarks around this sort of thing - see this IBM DW article for some of the common pitfalls, how to avoid them and some general commentary on what you're doing.
So really this is a "no comment" answer, but I think that's the only valid response. A compile-time-trivial no-op loop doesn't need to be fast, so the compiler isn't optimised to be fast in some of these conditions.
You are probably using a 32-bit JVM. The results will be probably different with a 64bit JVM. In a 32-bit JVM an int can be mapped to a native 32-bit integer and be incremented with a single operation. The same doesn't hold for a long, which will require more operations to increment.
See this question for a discussion about int and long sizes.
My guess - and it is only a guess - is this:
The JVM concludes that the first loop effectively does nothing, so it removes it entirely. No variable "escapes" from the for-loop.
In the second case, the loop also does nothing. But it might be that the JVM code that determines that the loop does nothing has a "if (type of i) == int" clause. In which case the optimisation to remove the do-nothing for-loop only works with int.
An optimisation that removes code has to be sure there are no side-effects. The JVM coders seem to have erred on the side of caution.
Micro-benchmarking like this does not make a lot of sense, because the results depend a lot on the internal workings of the Hotspot JIT.
Also, note that the system clock values that you get with System.currentTimeMillis() don't have a 1 ms resolution on all operating systems. You can't use this to very accurately time very short duration events.
Have a look at this article, that explains why doing micro-benchmarks in Java is not as easy as most people think: Anatomy of a flawed microbenchmark

C++ and Java performance

this question is just speculative.
I have the following implementation in C++:
using namespace std;
void testvector(int x)
{
vector<string> v;
char aux[20];
int a = x * 2000;
int z = a + 2000;
string s("X-");
for (int i = a; i < z; i++)
{
sprintf(aux, "%d", i);
v.push_back(s + aux);
}
}
int main()
{
for (int i = 0; i < 10000; i++)
{
if (i % 1000 == 0) cout << i << endl;
testvector(i);
}
}
In my box, this program gets executed in approx. 12 seconds; amazingly, I have a similar implementation in Java [using String and ArrayList] and it runs lot faster than my C++ application (approx. 2 seconds).
I know the Java HotSpot performs a lot of optimizations when translating to native, but I think if such performance can be done in Java, it could be implemented in C++ too...
So, what do you think that should be modified in the program above or, I dunno, in the libraries used or in the memory allocator to reach similar performances in this stuff? (writing actual code of these things can be very long, so, discussing about it would be great)...
Thank you.
You have to be careful with performance tests because it's very easy to deceive yourself or not compare like with like.
However, I've seen similar results comparing C# with C++, and there are a number of well-known blog posts about the astonishment of native coders when confronted with this kind of evidence. Basically a good modern generational compacting GC is very much more optimised for lots of small allocations.
In C++'s default allocator, every block is treated the same, and so are averagely expensive to allocate and free. In a generational GC, all blocks are very, very cheap to allocate (nearly as cheap as stack allocation) and if they turn out to be short-lived then they are also very cheap to clean up.
This is why the "fast performance" of C++ compared with more modern languages is - for the most part - mythical. You have to hand tune your C++ program out of all recognition before it can compete with the performance of an equivalent naively written C# or Java program.
All your program does is print the numbers 0..9000 in steps of 1000. The calls to testvector() do nothing and can be eliminated. I suspect that your JVM notices this, and is essentially optimising the whole function away.
You can achieve a similar effect in your C++ version by just commenting out the call to testvector()!
Well, this is a pretty useless test that only measures allocation of small objects.
That said, simple changes made me get the running time down from about 15 secs to about 4 secs. New version:
typedef vector<string, boost::pool_allocator<string> > str_vector;
void testvector(int x, str_vector::iterator it, str_vector::iterator end)
{
char aux[25] = "X-";
int a = x * 2000;
for (; it != end; ++a)
{
sprintf(aux+2, "%d", a);
*it++ = aux;
}
}
int main(int argc, char** argv)
{
str_vector v(2000);
for (int i = 0; i < 10000; i++)
{
if (i % 1000 == 0) cout << i << endl;
testvector(i, v.begin(), v.begin()+2000);
}
return 0;
}
real 0m4.089s
user 0m3.686s
sys 0m0.000s
Java version has the times:
real 0m2.923s
user 0m2.490s
sys 0m0.063s
(This is my direct java port of your original program, except it passes the ArrayList as a parameter to cut down on useless allocations).
So, to sum up, small allocations are faster on java, and memory management is a bit more hassle in C++. But we knew that already :)
Hotspot optimises hot spots in code. Typically, anything that gets executed 10000 times it tries to optimise.
For this code, after 5 iterations it will try and optimise the inner loop adding the strings to the vector. The optimisation it will do more than likely will include escape analyi o the variables in the method. A the vector is a local variable and never escapes local context, it is very likely that it will remove all of the code in the method and turn it into a no op. To test this, try returning the results from the method. Even then, be careful to do something meaningful with the result - just getting it's length for example can be optimised as horpsot can see the result is alway the same a s the number of iterations in the loop.
All of this points to the key benefit of a dynamic compiler like hotspot - using runtime analysis you can optimise what is actually being done at runtime and get rid of redundant code. After all, it doesn't matter how efficient your custom C++ memory allocator is - not executing any code is always going to be faster.
In my box, this program gets executed in approx. 12 seconds; amazingly, I have a similar implementation in Java [using String and ArrayList] and it runs lot faster than my C++ application (approx. 2 seconds).
I cannot reproduce that result.
To account for the optimization mentioned by Alex, I’ve modified the codes so that both the Java and the C++ code printed the last result of the v vector at the end of the testvector method.
Now, the C++ code (compiled with -O3) runs about as fast as yours (12 sec). The Java code (straightforward, uses ArrayList instead of Vector although I doubt that this would impact the performance, thanks to escape analysis) takes about twice that time.
I did not do a lot of testing so this result is by no means significant. It just shows how easy it is to get these tests completely wrong, and how little single tests can say about real performance.
Just for the record, the tests were run on the following configuration:
$ uname -ms
Darwin i386
$ java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03-226)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02-92, mixed mode)
$ g++ --version
i686-apple-darwin9-g++-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5490)
It should help if you use Vector::reserve to reserve space for z elements in v before the loop (however the same thing should also speed up the java equivalent of this code).
To suggest why the performance both C++ and java differ it would essential to see source for both, I can see a number of performance issues in the C++, for some it would be useful to see if you were doing the same in the java (e.g. flushing the output stream via std::endl, do you call System.out.flush() or just append a '\n', if the later then you've just given the java a distinct advantage)?
What are you actually trying to measure here? Putting ints into a vector?
You can start by pre-allocating space into the vector with the know size of the vector:
instead of:
void testvector(int x)
{
vector<string> v;
int a = x * 2000;
int z = a + 2000;
string s("X-");
for (int i = a; i < z; i++)
v.push_back(i);
}
try:
void testvector(int x)
{
int a = x * 2000;
int z = a + 2000;
string s("X-");
vector<string> v(z);
for (int i = a; i < z; i++)
v.push_back(i);
}
In your inner loop, you are pushing ints into a string vector. If you just single-step that at the machine-code level, I'll bet you find that a lot of that time goes into allocating and formatting the strings, and then some time goes into the pushback (not to mention deallocation when you release the vector).
This could easily vary between run-time-library implementations, based on the developer's sense of what people would reasonably want to do.

Categories

Resources