You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否用Kotlin内联类优化垃圾回收?Vector实现相关疑问

问题描述

我正在开发一款基于Java Swing和Java AWT的游戏,每数毫秒就会重绘一次。查阅开发禁忌时,多次看到关于Garbage Collector (GC) 的警告:尤其在主循环结束时频繁生成并丢弃大量类实例(通常是Vector类),会导致GC频繁回收,引发恼人的微小卡顿。

我发现自己的代码需要大量Vector实例(实际是包装(x,y)整数对的类),其中很多仅用于中间计算。我用Kotlin开发,一开始想创建可重置x、y属性的MutVector类,但觉得这种方案违背直觉且容易出错。

后来想到Kotlin的内联类(现@JVMInline value类),它可作为特定类型的包装器,比如:

@JVMInline 
value class EmailAdress(val value: String) {
    fun getHost() {
        return value.split("@").last()
    }
}

它是与String不同的独立类型,拥有自身方法,但运行时(大多数情况)以普通String形式存在,避免运行时开销。由于原生类型的优化特性,包装Int、Long等类型时能节省大量时间,而且原生类型无需GC回收。

于是我实现了如下代码:

private fun checkWontOverflow(x: Int): Int {
    // Value chosen because 2^31 - 1 misbehaves with shl, and for the usage I'm doing of it, it doesn't matter (it'll store coordinates smaller than 100,000)
    // much between 2^30 and 2^31
    require(abs(x) < 2.0.pow(30))
    return x
}

@JVMInline
value class Vector private constructor(private val storage: Long) {
    constructor(x: Int, y: Int): this(checkWontOverflow(x).toLong() shl 32 + checkWontOverflow(y).toLong())

    val x: Int
        get() = (storage shr 32).toInt()

    val y: Int
        get() = (storage - x shl 32).toInt()
}

我的疑问

  1. 是否存在异常场景(比如溢出问题)?
  2. 它能否真正减轻GC负担、消除卡顿?
  3. 有没有更简便的实现方式?
  4. 我需要(x: Float, y: Float)类型的Vector,能否通过将两个Float合并为一个Double实现类似方案?

解决方案与答疑

1. 异常场景与溢出问题

你的代码存在两个关键问题:

  • 位运算优先级错误:shl的优先级低于+,构造函数里的shl 32 + ...会被解析为shl (32 + ...),完全偏离了合并x、y到Long的逻辑,直接导致存储值错误甚至溢出。
  • y值提取逻辑错误:storage - x shl 32同样存在优先级问题,且计算效率低。

修正后的核心逻辑应该是:

// 构造时正确合并x和y
(checkWontOverflow(x).toLong() shl 32) + checkWontOverflow(y).toLong()
// 提取y时用位掩码直接取低32位
val y: Int get() = (storage and 0xFFFFFFFFL).toInt()

另外,你用2.0.pow(30)做范围检查会引入浮点数计算开销,换成整数常量0x3FFFFFFF更高效。

2. 对GC负担的影响

完全可以减轻GC负担甚至消除相关卡顿。@JVMInline value类在绝大多数场景下会被编译器内联为原始类型(这里就是Long),不会创建额外对象实例。所有中间计算产生的Vector都会以Long形式存在于栈上,不会进入堆内存,自然不会触发GC回收,完美解决频繁创建丢弃实例导致的卡顿问题。

3. 更简便的实现方式

可以简化为以下版本,兼顾正确性和简洁性:

@JVMInline
value class Vector private constructor(private val storage: Long) {
    constructor(x: Int, y: Int) : this(
        (x.toLong() shl 32) or (y.toLong() and 0xFFFFFFFFL)
    ) {
        // 用整数常量替代浮点数计算,明确范围
        require(x in -0x40000000..0x3FFFFFFF) { "x超出安全范围" }
        require(y in -0x40000000..0x3FFFFFFF) { "y超出安全范围" }
    }

    val x: Int get() = (storage shr 32).toInt()
    val y: Int get() = (storage and 0xFFFFFFFFL).toInt()
}

如果你的坐标范围确实远小于100,000,甚至可以去掉范围检查(但建议保留,避免后续扩展时出错)。

4. Float类型Vector的实现方案

可以通过将两个Float合并为一个Double实现类似方案。每个Float是32位,Double是64位,正好可以把两个Float的二进制位分别存入Double的高32位和低32位,实现代码如下:

@JVMInline
value class FloatVector private constructor(private val storage: Double) {
    constructor(x: Float, y: Float) : this(
        (java.lang.Double.doubleToLongBits(x.toDouble()) shl 32) or 
        (java.lang.Double.doubleToLongBits(y.toDouble()) and 0xFFFFFFFFL)
    )

    val x: Float get() = java.lang.Double.longBitsToDouble(storage.toLong() shr 32).toFloat()
    val y: Float get() = java.lang.Double.longBitsToDouble(storage.toLong() and 0xFFFFFFFFL).toFloat()
}

这种方式同样不会产生额外对象,完全避免GC开销,和Int版Vector的优化逻辑一致。


内容的提问来源于stack exchange,提问作者Alphasaft

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 08:26:35