Everyone hates the deprecation policies of the JDK team but it’s certainly an understandable policy. Millions and millions of lines of code exist out in the wild and there’s no telling what calamity would befall us if methods were actually removed the JRE libs. On the other hand, people still use those deprecated methods despite their failings and the presence of better options. I was reading Joseph Darcy’s blog entry on this policy and ran across an interesting comment.
The idea is pretty simple: make compiling fail against the deprecated methods without actually removing them from code. This preserves binary compatibility while preventing any new code from compiling against these methods. This would actually include new releases of applications written before the method was deprecated as these new versions would no longer compile while the previous releases would still run.
This seems to be the best of both worlds. Building on the idea from comment, I’d offer this enhancement: Introduce an @Obsolete annotation. This would supplant the @Deprecated annotation on these methods. Any usage of a method or class (or field?) with this annotation would return an error at compile time. Reflective access to these items would also be disallowed just to drive the point home. Of course, the annotations would need to include facilities to describe the alternatives to use for these old methods to provide the same functionality as the javadoc comment. Given this simple addition, I think we’d finally start to make some progress on modernizing our code.
Technorati Tags: development, Java
I had intended to do some follow up numbers to my previous post but I got a bit sidetracked by work and the like. My simple tests all work with one String that’s created then thrown away. This test helped me resolve the question I had when I started down that road but stops short of a more general answer. Then I saw this pingback which led me here. There’s some nice analysis and insights to consider. So given the shortcomings of my little benchmark and the comments there, I wanted to expand my test a bit and see what things look like when the loop doesn’t throw away the data. The test is simple enough again:
import java.util.*;
import java.text.*;
public class ConcatenationTest {
private static long concat(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = "Loop " + x + " of " + count + " iterations.";
}
return System.currentTimeMillis() - start;
}
private static long append(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = new StringBuilder("Loop ")
.append(x)
.append(" of ")
.append(count)
.append("iterations.")
.toString();
}
return System.currentTimeMillis() - start;
}
private static long concatAcrossLoops(int count) {
long start = System.currentTimeMillis();
String s = "";
for(int x = 0; x < count; x++) {
s += "Loop " + x + " of " + count + " iterations.";
}
long time = System.currentTimeMillis() - start;
System.out.println("concatAcrossLoops time = " + time);
return time;
}
private static long appendAcrossLoops(int count) {
long start = System.currentTimeMillis();
StringBuilder s = new StringBuilder();
for(int x = 0; x < count; x++) {
s.append("\nLoop ")
.append(x)
.append(" of ")
.append(count)
.append("iterations.");
}
long time = System.currentTimeMillis() - start;
System.out.println("appendAcrossLoops time = " + time);
return time;
}
public static void main(String[] args) {
int count = 10000;
List concats = new ArrayList();
List appends = new ArrayList();
List concatsAcross = new ArrayList();
List appendsAcross = new ArrayList();
for(int x = 0; x < 10; x++) {
concats.add(concat(count));
appends.add(append(count));
concatsAcross.add(concatAcrossLoops(count));
appendsAcross.add(appendAcrossLoops(count));
}
String header = "concats appends concats across loops appends across loops";
String format = "%7d %9d %22d %22d\n";
System.out.println(header);
for(int x = 0; x < 10; x++) {
System.out.printf(format, concats.get(x), appends.get(x), concatsAcross.get(x), appendsAcross.get(x));
}
}
} |
And then the results:
| concats |
appends |
concats across loops |
appends across loops |
| 48 |
14 |
18990 |
1276 |
| 27 |
11 |
14581 |
1442 |
| 4 |
4 |
13206 |
1253 |
| 3 |
3 |
13478 |
1438 |
| 4 |
4 |
12651 |
1444 |
| 4 |
3 |
12485 |
1403 |
| 4 |
3 |
12608 |
1318 |
| 4 |
3 |
13152 |
1312 |
| 3 |
4 |
12535 |
1390 |
| 4 |
3 |
12444 |
1329 |
Notice after the first two loops the numbers for all runs drops. As the JIT compiler kicks in, we get some optimization but as you can see concatenation across loop iterations is incredibly much more expensive. In this case, StringBuilder is still the clear winner.
update
There was a typo in the original test. I was calling toString() in the appendsAcrossLoop test which was entirely unnecessary. (I forgot to remove that call when adapting from the earlier iteration.) The new results are below. I included them here rather than just replacing the table above as it shows just how expensive that toString() is.
| concats |
appends |
concats across loops |
appends across loops |
| 42 |
15 |
16562 |
4 |
| 5 |
8 |
12564 |
5 |
| 4 |
3 |
11601 |
2 |
| 4 |
2 |
11141 |
2 |
| 4 |
3 |
11025 |
3 |
| 3 |
3 |
11260 |
3 |
| 3 |
3 |
11062 |
3 |
| 3 |
3 |
11738 |
2 |
| 4 |
2 |
11078 |
2 |
| 4 |
2 |
11130 |
3 |
Technorati Tags: development, Java, justin lee
There’s a new inspection in IDEA 8 (might just be in the EAP at this point) that will convert string concatentation to a variety of different approaches. One of these options is to use String.format(). I started applying this option to some code I’m working because it’s certainly more readable than some of the concatenation stuff I’d been doing. But I started thinking that I should probably profile this before I get too crazy with it just to make sure I’m not hamstringing myself with this. So I wrote a simple test to see what the fastest option was and I was a little surprised by the results.
First, let’s see the code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| import java.text.*;
public class test {
private static long concat(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = "Loop " + x + " of " + count + " iterations.";
}
return System.currentTimeMillis() - start;
}
private static long format(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = String.format("Loop %s of %s iterations."
, x, count);
}
return System.currentTimeMillis() - start;
}
private static long format2(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = MessageFormat.format("Loop {0} of {1} iterations."
, x, count);
}
return System.currentTimeMillis() - start;
}
private static long append(int count) {
long start = System.currentTimeMillis();
for(int x = 0; x < count; x++) {
String s = new StringBuilder("Loop ")
.append(x)
.append(" of ")
.append(count)
.append("iterations.")
.toString();
}
return System.currentTimeMillis() - start;
}
public static void main(String[] args) {
int count = 1000000;
for(int x = 0; x < 10; x++) {
System.out.println("concat = " + concat(count));
System.out.println("String.format = " + format(count));
System.out.println("MessageFormat.format = " + format2(count));
System.out.println("append = " + append(count));
System.out.println();
}
}
} |
This admittedly naive “benchmark” runs through four options and prints out the basic timing results. I’ve compiled the results below in a table:
| concat |
String.format |
MessageFormat.format |
append |
| 408 |
3164 |
9099 |
376 |
| 338 |
2876 |
8559 |
340 |
| 300 |
3013 |
8655 |
398 |
| 342 |
2938 |
8511 |
311 |
| 308 |
2911 |
8570 |
310 |
| 306 |
2924 |
8726 |
320 |
| 316 |
3019 |
9006 |
414 |
| 306 |
2994 |
8673 |
331 |
| 346 |
3022 |
9588 |
311 |
| 312 |
2988 |
8590 |
313 |
As you can see both format methods take considerably more time than the other two. I was a little surprised to see this though the magnitude of the difference was more surprising than than the difference itself. So neither of those are options you’ll want to consider in areas that get called often. The one that was really suprising for me was the relative similarity between concatenating strings and appending using StringBuilder. While StringBuilder was generally faster than string concats, the difference was rather minimal and in some runs actually slower. What this says to me is that the generally accepted “wisdom” that String concatenation is slower than using StringBuilder is clearly wrong.
On the other, I’m not really a performance expert so I might be doing something stupid here or missing something fundamental. The test seems rather straightforward, though. What do you think?
Technorati Tags: development, Java, justin lee