Pages

Sunday, October 7, 2012

Performance of string comparison in Java

Have you ever wonder how fast Java can compare two strings? Which method is the best? The answer is.... it depends what you are compare. Let's do some tests. We will try to compare the intern strings and dynamic created by both '==' and equals methods. Here is the code which do the checking:
String s1 = "test";
String s2 = "test";
String s3 = s1 + "";
String s4 = s3 + "";
String s5 = s4;
long iterations = 100000000;
long time = System.currentTimeMillis();

for (int i = 0; i < iterations; i++) {
    if (s1 == s2);
}
System.out.println("s1==s2: " + (System.currentTimeMillis() - time));

time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s1 == s3.intern());
}
System.out.println("s1==s3.intern(): " + (System.currentTimeMillis() - time));

for (int i = 0; i < iterations; i++) {
    if (s1.equals(s3.intern()));
}
System.out.println("s1.equals(s3.intern()): " + (System.currentTimeMillis() - time));

time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s1.equals(s2));
}
System.out.println("s1.equals(s2): " + (System.currentTimeMillis() - time));

time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s1.equals(s3));
}
System.out.println("s1.equals(s3): " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s3.equals(s1));
}
System.out.println("s3.equals(s1): " + (System.currentTimeMillis() - time));

time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s3.equals(s4));
}
System.out.println("s3.equals(s4): " + (System.currentTimeMillis() - time));

time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s3.intern() == s4.intern());
}
System.out.println("s3.intern()==s4.intern(): " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s3.intern().equals(s4.intern()));

}
System.out.println("s3.intern().equals(s4.intern()): " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
    if (s5 == s4);
}
System.out.println("s5==s4: " + (System.currentTimeMillis() - time));
}
And the result is:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
s1==s2: 95
s1==s3.intern(): 12925
s1.equals(s3.intern()): 25778
s1.equals(s2): 88
s1.equals(s3): 90
s3.equals(s1): 89
s3.equals(s4): 314
s3.intern()==s4.intern(): 26148
s3.intern().equals(s4.intern()): 26083
s5==s4: 310

So as you can see there is no big difference in comparison strings using reference comparison nd equals method (test 1 and  4 for intern strings, 7 and 10 for dynamically created). What is interesting that comparing two dynamically created string is takes nearly 3.5 times longer than in case when at least of the arguments is intern string. As you can also see using the intern() method is really time consuming. The interesting result is also comparison of the second and third case. Why the time is more than doubled when equals is used?

Now, lets check how the results change when we use the longer string. The same tests but now s1 and s2 strings are 248 characters long.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
s1==s2: 135
s1==s3.intern(): 187042
s1.equals(s3.intern()): 374946
s1.equals(s2): 90
s1.equals(s3): 89
s3.equals(s1): 89
s3.equals(s4): 316
s3.intern()==s4.intern(): 373573
s3.intern().equals(s4.intern()): 374662
s5==s4: 311

The results are quite surprising. The equals() method doesn't depend on the length of its arguments! However the intern() method needs much more time.

Saturday, October 6, 2012

SvcUtils generates incorect code with multidimensional arrays

There may be situation when you need create .NET client for some webservice. The easiest way is to get the WSDL file and generates the clients using SvcUtils.exe tool. In most cases it everything goes fine and you can quickly finish your work. But there is a special case when the SvcUtils and its predecessor - wsdl.exe will fail.

Lets assume that schema contains such element:
<xs:element name="Description" minOccurs="0" maxOccurs="unbounded">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="Text" minOccurs="0" maxOccurs="unbounded" type="xs:string"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>
SvcUtils tries to be smart and generates:
private string[][] text;

[System.Xml.Serialization.XmlArrayAttribute(Order = 1)]
[System.Xml.Serialization.XmlArrayItemAttribute("Text", typeof(string), IsNullable = false)]
public string[][] Description {
    get {
        return this.text;
    }
    set {
        this.text = value;
    }
}
OK, nearly great. But when you try to use this code you experience the cast exception as there won't be possibility of casting string to string[]. The typeof parameter of XmlArrayItemAttribute is wrong! The attribute is an array of arrays, then the correct type is string[]!

Quick changes is enough to make the code work properly. But if you are the author of the service and wont to your user to avoid doing this there is one simple solution - just move the occurrences definition from Text element and place it in sequence node, that is:
<xs:element name="Describtion" minOccurs="0" maxOccurs="unbounded">
    <xs:complexType>
        <xs:sequence minOccurs="0" maxOccurs="unbounded">
            <xs:element name="Text" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
</xs:element>
After this SvcUtils will generate separate class for Description element and everything will work out of the box.

Tuesday, August 28, 2012

Reading compatible-screen in compiled AndroidManifest file

The android manifest file is transformed  to binary representation during compilation. We can used  AXMLPrinter2 to recreate the XML version from binary one. But it won't be the same as original one, more sometimes it cannot be compiled again because it is not valid!

Such situation can appear when we have compatible-screen node in manifest. According to specification the its syntax is:
<compatible-screens>
    <screen android:screenSize=[ "small" | "normal" | "large" | "xlarge"]
    android:screenDensity=[ "ldpi" | "mdpi" | "hdpi" | "xhdpi"] />
    ...
</compatible-screens>
However in recreated file we doesn't find any of the above values but its numeric equivalent. Check the following table for precise values.
large

screenSize value [screen dpi]
small 200
normal 300
large 400
xlarge 500
screenDensity value [lowest of screen size in px]
ldpi 120
mdpi 160
hdpi 240
xhdpi 320

Sunday, August 26, 2012

How to mock location on Android devices

You may get a headache when you try to use the mocking location feature in your android program for the first time. The Location API is quite simple but not intuitive. To play with location first we need to give our application the following permissions:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
Then lest assume if we just want to get our location using build-in real GPS. The code to obtain the location may look like:
final LocationManager locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
But if we try it then we will see that it is useless in that form. This is because the getLastKnownLocation returns last cached value, if there is non then returns null. The proper mechanism to work with GPS receiver is by asynchronous handler:
LocationListener myLocationListener = new LocationListener() {
    public void onLocationChanged(Location loc) {
        Log.i("LocationListener", "I am at " + loc.getLongitude() + ", " + loc.getLatitude());
    }
    public void onProviderDisabled(String provider) {}
    public void onProviderEnabled(String provider) {}
    public void onStatusChanged(String provider, int status, Bundle extras) {}
};
final LocationManager locationManager;
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 50, 0, myLocationListener);
In this case our program is not waiting for fix. We need to remember if our main thread stops for more than 5 seconds then system can automatically terminate it. Ok, we know how to use GPS receiver. Now let try to do some mocking. Before we start we need to turn on in preference that we are allowing mocking our location. If we check the LocationManager API we can see that there is a method setTestProviderLocation. You may try do something like:
Location loc = new Location(LocationManager.GPS_PROVIDER);
loc.setLongitude(12);
loc.setLatitude(10);
loc.setTime(System.currentTimeMillis());
locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, loc);
But don't do that if you your device has GPS receiver! And in fact it is not enough if has not. In both cases you will get the exception. In the first is hard to say why, because it is not documented, the latter fails because provider doesn't exists. It needs to be register first by addTestProvider method. It is possible to use register it using LocationManager.GPS_PROVIDER name. But I rather not recommend it - your code fail on devices with real GPS receiver. To make it work choose the unique provider name, register it and you may start setting your fake location:
final String providerName = "MyFancyGPSProvider";
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//Remember to remove your your provider before using it or after.
//In other case it won't be remove till restarting the phone.
if (locationManager.getProvider(providerName) != null) {
    locationManager.removeTestProvider(providerName);
}
locationManager.addTestProvider(providerName, true, false, false, false, true, true, true,
                               Criteria.POWER_LOW, Criteria.ACCURACY_FINE);
Location loc = new Location(providerName);
loc.setLongitude(13);
loc.setTime(System.currentTimeMillis());
loc.setLatitude(10);
locationManager.setTestProviderLocation(providerName, loc);
But what if I want to provide location to other applications? They are using LocationManager.GPS_PROVIDER, how can they now about may custom provider? Good question. They cannot and they won't know about your changes. I need to admit that above code is useless. You cannot even register the listener as for LocationManager.GPS_PROVIDER - it won't be fired. Then now I can tell you how to do it right. Any test provider are designed to mock the real one which is used by other application, so when we set the location in our mock the changes are visible in real LocationManager.GPS_PROVIDER as long as our Location object is set as obtained from it:
new Location(LocationManager.GPS_PROVIDER); 
Also remember that you need to set the correct time for location update, in other way it is set to 0 and ignored as update.