Features of Implicit Typecasting in Groovy

    Not so long ago, I asked a question in Groovy mail-list - is there any stable list of things that should be avoided when writing high-performance Groovy. Among other tips, one of Groovy’s main developers, Jochen “blackdrag” Theodorou, pointed out that in the general case, often using a particular type when declaring a variable (for example, MyType var = ... instead of def var = ...) can degrade performance due to overhead costs for type checking and, if necessary, their conversion.

    According to Groovy developers, many of the problems in this area were observed in versions up to 1.7 and were then fixed during a lot of work on general optimization of runtime done in version 1.9. Below, however, is a small experiment that these overheads show even on Groovy 1.8.3.

    Before this experiment, it will be useful to review the following article - groovy.codehaus.org/From+source+code+to+bytecode , which tells how, in steps, the source code on Groovy is converted to a JVM bytecode, as well as some input an article in actual bytecode, for example this one - www.ibm.com/developerworks/ibm/library/it-haggar_bytecode .



    So the simplest code is:

    class NoStrictType {
       void myMethod1() {
         def a = 4.2
       }
    }
    class StrictType {
      void myMethod1() {
        int a = 4.2
      }
    }
    


    The only difference is that in the second class the type of the local variable is explicitly defined. We compile both classes and look at the bytecode of these methods.
    For the first case (without a specific type):

    image

    So the code is pretty obvious. We omit the first line, this is getting an array of cached CallSites, they do not relate directly to type operations. Next, we create a new object of type BigDecimal (everyone remembers that by default in Groovy all non-integer numbers are represented as BidDecimal?), Duplicate the current value at the top of the operand stack, get the value 4.2 from the constant pool, initialize the BigDecimal object, keep a link to this created object in the second cell of the array of local variables of the current frame, then load it from there to the top of the stack, and finally use pop; return to return this link from the method. Again, as everyone remembers, in Groovy, even without an explicit return statement, any method returns the last variable that was used in the calculations (more precisely, the last link,

    Now, the bytecode for the second class is StrictType.

    image

    What is the difference compared to the first case? A call to the static method DefaultTypeTransformation.intUnbox () was added. Let's look at the documentation for this method about what it does.
    groovy.codehaus.org/api/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.html
    We see that this method simply converts the reference object into an object of type Number, and returns a primitive.

    public static int intUnbox(Object value) {
       Number n = castToNumber(value, int.class);
       return n.intValue();
    }
    


    We look how exactly this type conversion is performed:

     public static Number castToNumber(Object object, Class type) {
            if (object instanceof Number)
                return (Number) object;
            if (object instanceof Character) {
                return Integer.valueOf(((Character) object).charValue());
            }
            if (object instanceof GString) {
                String c = ((GString) object).toString();
                if (c.length() == 1) {
                    return Integer.valueOf(c.charAt(0));
                } else {
                    throw new GroovyCastException(c, type);
                }
            }
            if (object instanceof String) {
                String c = (String) object;
                if (c.length() == 1) {
                    return Integer.valueOf(c.charAt(0));
                } else {
                    throw new GroovyCastException(c, type);
                }
            }
            throw new GroovyCastException(object, type);
        }
    


    Several instanceof operators that are not the cheapest, several explicit type conversions, conditional operators, work with exceptions. I did not measure how this affects the speed of work in real applications, but judging by the fact itself, how many additional bytecodes must be performed in this case for type casting is impressive. Remember - the original method itself - only 10 bytecodes.

    Also popular now: