android kotlin - are Function References slow due to reflection?

802 views Asked by At

Reflection is suppose to be a bit time consuming on android. so i was wondering given a function reference like this:

fun isOdd(x: Int) = x % 2 != 0
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))

is ::isOdd call an unnecessary burden ?

would it be more efficient to not use them ?

UPDATE: doing some light metrics i did the following:

    class MainActivity : Activity() {

    val numbers = listOf(1, 2, 3)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        doRegular()
        doReflection()
    }

    fun isOdd(x: Int) = x % 2 != 0

    fun doRegular() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter { it % 2 != 0 }
        }
        Log.v("myTag", "regular: +${System.currentTimeMillis() - start}")
    }

    fun doReflection() {
        val start = System.currentTimeMillis()
        for (i in 0..999999) {
            numbers.filter(::isOdd)
        }
        Log.v("myTag", "reflection: ${System.currentTimeMillis() - start}")
    }
}

and the print statement results are:

//*****this was the first attempt before compiler could optimise anything
        regular: +577     
        reflection: 318
  //*********
  
    regular: +376
    reflection: 327
    
    regular: +593
     reflection: 343
    
     regular: +417
     reflection: 251
     
     regular: +243
     reflection: 238
     
     regular: +241
     reflection: 238
     
     regular: +238
     reflection: 237
     
     regular: +235
     reflection: 247
     
     regular: +237
     reflection: 247
     
     regular: +230
     reflection: 268

What would you conclude given these results?

update: some are asking why i think its using reflection. its based on this:

This stackoverflow answer seems to state its reflection: and the title for the official doc has reflection as the main heading: hence my confusion.

3

There are 3 answers

2
Animesh Sahu On BEST ANSWER

Surely ::isOdd is used for referencing the function, but unless it is really required to use the "reflection", it (reflection) is not used. Similar to how Int are changed into int in the bytecode unless we need to store the reference, if we create collections then only it is represented as java.lang.Integer.

The Kotlin compiler is smart and does the trick behind the hood.

/**
 * Returns a list containing only elements matching the given [predicate].
 * 
 * @sample samples.collections.Collections.Filtering.filter
 */
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

The filter function is inline function that embeds the lambda/reference directly at the call-site without actually using the reflection.

If you see the JVM bytecode, you'll see the following decompiled Java code:

// of numbers.filter { it % 2 != 0 }
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int it = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (it % 2 != 0) {              // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

// of numbers.filter(::isOdd)
while(var6.hasNext()) {
    Object element$iv$iv = var6.next();
    int p1 = ((Number)element$iv$iv).intValue();
    int var9 = false;
    if (isOdd(p1)) {                  // <- check this out
        destination$iv$iv.add(element$iv$iv);
    }
}

Realistically there is no reflection is involved here.

Sidenote: I've had a similar question over the official Kotlinlang slack at here, and I've got much attention but only 1 reply regarding it is only about metafacory as stated here in softwareengineering stackexchange subdomain.

Here's a ss: enter image description here

1
daniel galarza On

I would say that the best way to figure this out is to time it! Although, I would expect that Reflection would be more time consuming.

val numbers = listOf(1, 2, 3)

fun isOdd(x: Int) = x % 2 != 0

fun doRegular() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter { it % 2 != 0 }
    }
    println(System.currentTimeMillis() - start)
}

fun doReflection() {
    val start = System.currentTimeMillis()
    for (i in 0..999999) {
        numbers.filter(::isOdd)
    }
    println(System.currentTimeMillis() - start)
}
2
Fabio On

Function references don't use reflection, they're like accessing a field, but for functions.

As such, these references are solved during compile time. If you made a mistake like misspelling the function name it won't compile. Where reflection for finding and calling the same misspelled function would fail at run time.