Kotlin—通过id或字符串获取类实例属性值的高性能和可伸缩的方法

Kotlin—通过id或字符串获取类实例属性值的高性能和可伸缩的方法,kotlin,properties,kotlin-reflect,Kotlin,Properties,Kotlin Reflect,我想让类及其子类的属性在运行时通过整数id或属性名称进行读写,性能尽可能接近常规编译读写。这个类和它的子类可能有很多实例(比如100万个),每个类可能有数百个属性,所以我想尽量减少每个类实例中每个属性使用的内存 我看到的广泛的解决方案组正在使用反射,使每个属性成为可变类的实例,然后保留这些属性的映射,或者编写巨型when语句 我已经测试了反射实现的性能(见下文)。这需要15倍于在我的测试中直接访问属性的时间 这一点可以改进吗,或者有更好的方法吗 class ReflectionClass {

我想让类及其子类的属性在运行时通过整数id或属性名称进行读写,性能尽可能接近常规编译读写。这个类和它的子类可能有很多实例(比如100万个),每个类可能有数百个属性,所以我想尽量减少每个类实例中每个属性使用的内存

我看到的广泛的解决方案组正在使用反射,使每个属性成为可变类的实例,然后保留这些属性的映射,或者编写巨型when语句

我已经测试了反射实现的性能(见下文)。这需要15倍于在我的测试中直接访问属性的时间

这一点可以改进吗,或者有更好的方法吗

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)
                }
            }
        }
    }
}
类反射类{
@字段ID(1)
var intField=0
有趣的getPropById(id:Int):有吗{
val property=propertiesById[id]
返回属性?获取(此)
}
fun setIntPropById(id:Int,value:Int){
val property=propertiesById[id]
if(属性为KMutableProperty1){
属性?.setter?.call(此,值)
}
}
有趣的getPropByName(名称:String):有吗{
val property=属性ByName[名称]
返回属性?获取(此)
}
fun setIntPropByName(名称:字符串,值:Int){
val property=属性ByName[名称]
if(属性为KMutableProperty1){
属性作为KMutableProperty1
属性集(此,值)
}
}
伴星{
//private val propertiesById=HashMap()
private val propertiesById=HashMap()
私有val属性ByName=HashMap()
初始化{
val fields=ReflectionClass::class.memberProperties.forEach{property->
val id=property.findAnnotation()
如果(id!=null){
propertiesById.put(id.id,property)
propertiesByName.put(property.name,property)
}
}
}
}
}

我认为你无法从反射中获得想要的性能

(反射不是为高性能使用而设计的——根据我的经验,它很少用于生产代码。它非常适合测试、框架、构建工具等;但在我看到的大多数关于它的问题中,真正的答案是使用不需要反射的更好的设计!)

当然,其他两种方法都不是完美的。这可能取决于具体的要求。这些是否需要是具有命名Kotlin属性的对象,或者它们可以一直是简单的映射?后者可能更易于编码和维护。否则硬编码测试可能会节省内存


(如果您有很多时间,您可能会考虑编写某种构建工具,它可以自动为您生成带有硬编码测试的查找方法。当然,这将使用反射,但仅在编译时使用。不过这将是一项艰巨的工作,我不知道您会如何处理。)

我认为你不会从反思中获得想要的表现

(反射不是为高性能使用而设计的——根据我的经验,它很少用于生产代码。它非常适合测试、框架、构建工具等;但在我看到的大多数关于它的问题中,真正的答案是使用不需要反射的更好的设计!)

当然,其他两种方法都不是完美的。这可能取决于具体的要求。这些是否需要是具有命名Kotlin属性的对象,或者它们可以一直是简单的映射?后者可能更易于编码和维护。否则硬编码测试可能会节省内存


(如果您有很多时间,您可能会考虑编写某种构建工具,它可以自动为您生成带有硬编码测试的查找方法。当然,这将使用反射,但仅在编译时使用。不过这将是一项艰巨的工作,我不知道您会如何处理。)

许多属性需要经常访问,因此不必查找它们有好处。如果我使用映射,并使用字符串作为其中一个映射的键,那么每个实例将占用更多的内存,不是吗?是的,大多数映射实现需要占用相当多的内存,因为需要所有这些散列桶,然后(通常)在使用的桶中使用一系列引用。(只使用一对并行数组就可以编写一个低内存版本,但当然查找是O(n)。不过,我为一个专门的应用程序这样做了。)然而:由于您的所有实例都将共享相同的属性列表,因此您可以做一些事情,比如为每个实例存储一个数组,然后是一个全局映射,为每个属性名提供该数组的索引。许多属性需要频繁访问,因此不必查找它们有好处。如果我使用映射,并使用字符串作为其中一个映射的键,那么每个实例将占用更多的内存,不是吗?是的,大多数映射实现需要占用相当多的内存,因为需要所有这些散列桶,然后(通常)在使用的桶中使用一系列引用。(只使用一对并行数组就可以编写一个低内存版本,但当然查找是O(n)。不过,我为一个专门的应用程序这样做了。)然而:由于您的所有实例都将共享相同的属性列表,因此您可以做一些事情,比如为每个实例存储一个数组,然后是一个全局映射,为每个属性名将索引提供到该数组中。