mercredi 16 février 2022

Performance difference between getDeclaredMethod() and getDeclaredMethods() with different levels of class inheritance

I hope to find the declaring class of a method in Android. The basic idea is to find if the target method is declared in a class. If not, I will recursively find the target method in its super classes. For a specific reason, efficiency matters. So I have two implementations and I compared their performance differences.

Version 1: use the API Class.getDeclaredMethod to check if a method exists in a class. If not, NoSuchMethodException will be thrown.

public Class getDeclaringClass1(Class cls, String mtdName, Class[] paraTypes) {
        Class declaringCls = null;
        try {
            declaringCls = cls.getDeclaredMethod(mtdName, paraTypes).getDeclaringClass();
        } catch (NoSuchMethodException | SecurityException e) {
            Class supr = cls.getSuperclass();
            if(supr != null) declaringCls = getDeclaringClass1(supr, mtdName, paraTypes);
        }
        return declaringCls;
}

Version 2: Manually check if a method is declared in a class using Class.getDeclaredMethods. We will match each method one by one with the method name and parameter types.

public Class getDeclaringClass2(Class cls, String mtdName, Class[] paraTypes) {
        Class declaringCls = null;
        Method[] methods = cls.getDeclaredMethods();
        boolean containsMtd = false;
        for(Method method: methods) {
            if(method.getName().equals(mtdName)) {
                boolean allEqual = true;
                for(int i=0; i< method.getParameterTypes().length; i++) {
                    if(! method.getParameterTypes()[i].equals(paraTypes[i])) {
                       allEqual = false;
                       break;
                    }
                }
                if(allEqual) {
                    containsMtd = true;
                    declaringCls = cls;
                    break;
                }
            }
        }
        if(! containsMtd) {
           Class supr = cls.getSuperclass();
           if(supr != null) declaringCls = getDeclaringClass2(supr, mtdName, paraTypes);
        }
        return declaringCls;
}

I made some interesting observations when testing the efficiency of these two versions. Basically I created several empty classes C, CC, CCC, CCCC. Their relationships are

CC extends C
CCC extends CC
CCCC extends CCC

All these classes does not declare any methods and I use the toString method as the target method. I testing two getDeclaringClass with a loop of 10000 times:

start = System.currentTimeMillis();
for(long i=0; i<10000; i++) {
     getDeclaringClass(cls, "toString", new Class[0]).getName()); // getDeclaringClass will be set to version 1 or 2
}
end = System.currentTimeMillis();
System.out.println((end - start) + "ms");

Here are the results:

cls Version 1 Version 2
C 1168ms 1632ms
CC 2599ms 1397ms
CCC 3495ms 1680ms
CCCC 4908ms 1559ms

We can see that version 1's performance drops significantly when we have more levels of class inheritance. But version 2's performance does not change a lot.

So what makes the difference between version 1 and 2? What makes version 1 so slow?





Aucun commentaire:

Enregistrer un commentaire