Skip to content

高性能多维数组库 (kotlinx-multik) Experimental

源:kotlinx-multik 官方仓库

随着 Android 设备算力的提升,移动端对 AI 模型推理、信号处理以及复杂数学运算的需求日益增加。传统的 Kotlin 集合(如 ListArray)在处理大规模多维数据时,不仅内存开销巨大,且缺乏矩阵运算的表达力。kotlinx-multik 是 Kotlin 官方推出的高性能多维数组库,它通过灵活的计算引擎切换(JVM、Native、OpenBLAS)和类型安全的维度检查,为 Android 开发者提供了类 NumPy 的开发体验。

技术价值与核心优势

  • 多引擎架构:支持纯 Kotlin 实现(Jvm/Native)以保持兼容性,同时也支持接入 OpenBLAS 等高性能 C++/Fortran 库以获得极致性能。
  • 维度安全:利用 Kotlin 类型系统(如 D1, D2, DN)在编译期确保矩阵运算的维度匹配,减少运行时崩溃。
  • 内存效率:底层采用连续的内存布局,极大减少了对象包装开销,且支持零拷贝的视图(View)操作(如切片、转置)。
  • 跨平台支持:原生支持 KMP,逻辑可在 Android 与 iOS 间复用。

依赖配置与版本

查看 Maven Central 最新版本

kotlin
dependencies {
    // 核心 API
    implementation("org.jetbrains.kotlinx:multik-core:0.2.3")
    // 默认 JVM 引擎 (适用于 Android/JVM)
    implementation("org.jetbrains.kotlinx:multik-default:0.2.3")
}
groovy
dependencies {
    implementation 'org.jetbrains.kotlinx:multik-core:0.2.3'
    implementation 'org.jetbrains.kotlinx:multik-default:0.2.3'
}
toml
[versions]
multik = "0.2.3"

[libraries]
multik-core = { group = "org.jetbrains.kotlinx", name = "multik-core", version.ref = "multik" }
multik-default = { group = "org.jetbrains.kotlinx", name = "multik-default", version.ref = "multik" }

核心抽象:NDArray

multik 的核心模型是 NDArray。它通过类型参数指定元素类型和维度。

API 核心签名

kotlin
public interface NDArray<T, D : Dimension> {
    public val data: MemoryView<T>    // 扁平化的底层数据视图
    public val shape: IntArray        // 各维度的长度
    public val dim: D                 // 维度标志(D1, D2 ... DN)
    public val size: Int              // 总元素个数
    
    // 基础算术运算扩展
    public operator fun plus(other: NDArray<T, D>): NDArray<T, D>
    public operator fun times(other: NDArray<T, D>): NDArray<T, D>
}

多维数组实战

数组创建与切片

multik 提供了极为简洁的 DSL 来初始化数组。

kotlin
import org.jetbrains.kotlinx.multik.api.*
import org.jetbrains.kotlinx.multik.ndarray.data.*

// 创建 2D 矩阵 (3x3)
val matrix = mk.ndarray(mk[mk[1, 2, 3], mk[4, 5, 6], mk[7, 8, 9]])

// 快速创建全零或全一矩阵
val zeros = mk.zeros<Double, D2>(3, 3)
val identity = mk.identity<Double>(3)

// 切片操作 (零拷贝视图)
val subMatrix = matrix[0..1, 1..2] // 获取前两行,中间两列
kotlin
// 矩阵乘法 (点积)
val a = mk.ndarray(mk[1, 2, 3], 1, 3)
val b = mk.ndarray(mk[4, 5, 6], 3, 1)
val result = a dot b

// 广播机制 (Broadcasting)
val c = mk.ndarray(mk[10, 20, 30])
val broadcasted = matrix + c // 向量 c 将自动应用到矩阵的每一行

底层机制:引擎切换与内存布局

动态引擎发现

multik 采用了插件化的引擎架构。在运行时,可以通过 mk.engine = EngineType.KOTLIN 动态指定使用的计算后端。

  • multik-kotlin:纯 Kotlin 实现,无平台限制,但性能一般。
  • multik-default:针对 JVM 优化的版本,使用了更高效的循环和向量化。
  • multik-openblas:通过 JNI/C-Interop 调用底层线性代数库,适合大规模科学计算。

内存布局优化

multik 并没有使用 Array<Array<T>> 这种嵌套结构,而是将多维数组映射为一维的连续内存块(MemoryView)。

  • Stride(步长):通过计算步长来定位多维坐标在扁平化数组中的位置。
  • 零拷贝视图:转置(transpose)或切片(slice)操作仅仅是创建了一个拥有不同 shapestride 的新视图,而底层数据完全不发生拷贝。

工程实践准则

Android 端的引擎选择

在 Android 上,建议优先使用 multik-default。如果涉及极大规模的矩阵运算,可以考虑集成 multik-openblas,但需注意这会显著增加 APK 体积(需包含各架构的 .so 文件)。

避免频繁创建临时对象

虽然 multik 做了大量优化,但频繁的矩阵运算仍会产生中间 NDArray 对象。在高性能循环中,尽量使用原地更新(In-place)的操作(如果 API 支持)。

R8/Proguard 配置

由于 multik 涉及引擎的动态加载,混淆时需保留相关类:

proguard
-keep class org.jetbrains.kotlinx.multik.api.** { *; }
-keep class org.jetbrains.kotlinx.multik.ndarray.** { *; }