1

I would like to make the properties of a class and it's child classes available at runtime for read and write via by integer id or by the name of the property with performance as close to that of a regular compiled read or write as feasible. There may be many instances of the this class and it's child classes (say up to 1 million) and each class may have hundreds of properties, so I want to minimise the memory used by each property in each class instance.

The broad solution groups that I see are using reflection, making each property an instance of a mutable class and then keeping maps of these, or writing giant when statements.

I've tested the performance of a reflection implementation (see below). This takes 15 times as long as directly accessing the property in my test.

Can this be improved, or is there better way to do this?

class ReflectionClass {

    @FieldId(1)
    var intField = 0

    fun getPropById(id: Int): Any? {
        val property = propertiesById[id]
        return property?.get(this)
    }

    fun setIntPropById(id: Int, value: Int) {
        val property = propertiesById[id]
        if (property is KMutableProperty1) {
            property?.setter?.call(this, value)
        }
    }

    fun getPropByName(name: String): Any? {
        val property = propertiesByName[name]
        return property?.get(this)
    }

    fun setIntPropByName(name: String, value: Int) {
        val property = propertiesByName[name]
        if (property is KMutableProperty1) {
            property as KMutableProperty1<ReflectionClass, Int>
            property.set(this, value)
        }
    }


    companion object {
        //private val propertiesById = HashMap<Int, KProperty1<ReflectionClass,*>>()
        private val propertiesById = HashMap<Int, KProperty1<ReflectionClass, *>?>()
        private val propertiesByName = HashMap<String, KProperty1<ReflectionClass, *>>()

        init {
            val fields = ReflectionClass::class.memberProperties.forEach { property ->
                val id = property.findAnnotation<FieldId>()
                if (id != null) {
                    propertiesById.put(id.id, property)
                    propertiesByName.put(property.name, property)
                }
            }
        }
    }
}
Dean
  • 73
  • 6

1 Answers1

0

I don't think you'll get the performance you want from reflection.

(Reflection wasn't designed for high-performance use -- and in my experience, is very rarely used in production code. It's great for testing, frameworks, build tools, &c; but in most of the questions I see about it, the real answer is to use a better design that doesn't need reflection!)

Neither of the other two approaches is perfect, of course. It probably depends upon the exact requirements. Do these need to be objects with named Kotlin properties at all, or could they be simple maps all along? The latter may be simpler to code and easier to maintain. Else hard-coded tests would probably save memory.

(If you had lots of time, you might look into writing some sort of build tool which could automatically generate a lookup method with those hard-coded tests for you. That would use reflection, of course, but only at compile time. It'd be a big job, though, and I don't know how you'd approach it.)

gidds
  • 16,558
  • 2
  • 19
  • 26
  • Many of the properties need to be accessed frequently, so there is benefit to not having to look them up. If I used maps, and used Strings for the keys of one of the maps, this would use much more memory per instance, wouldn't it? – Dean Mar 03 '19 at 01:24
  • Yes, most Map implementations take quite a bit of memory, due to the need for all those hash buckets, and then (usually) a chain of references within the used buckets. (It's possible to write a low-memory version using just a pair of parallel arrays, but of course lookups are then O(n). I've done it for a specialised application, though.) However: since all your instances will share the same list of properties, you may be able to do something like storing a single array per instance, and then a single global map giving the index into that array for each property name. – gidds Mar 03 '19 at 09:24