Skip to content

Commit 014c958

Browse files
authored
feat(java): use JNI instead of JNA (#2773)
Switch from JNA -> JNI. On some microbenchmarks I've seen this result in a ~3x speedup for simple string-passing. I wired this into the Iceberg fork for Vortex and am seeing an immediate ~40% speedup on Citibike scan queries Subsequently #2781 gives us another 2x speedup on Citibike.
1 parent 03aad8b commit 014c958

37 files changed

+2049
-770
lines changed

Cargo.lock

Lines changed: 139 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ members = [
1818
"vortex-flatbuffers",
1919
"vortex-io",
2020
"vortex-ipc",
21+
"vortex-jni",
2122
"vortex-layout",
2223
"vortex-mask",
2324
"vortex-metrics",

java/versions.lock

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ jakarta.validation:jakarta.validation-api:2.0.2 (1 constraints: fd10b6c3)
5757
jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 (5 constraints: 8c647f61)
5858
javax.activation:activation:1.1.1 (1 constraints: 150dc536)
5959
joda-time:joda-time:2.12.5 (1 constraints: 5f0ca305)
60-
net.java.dev.jna:jna:5.17.0 (1 constraints: c50c2618)
61-
net.java.dev.jna:jna-platform:5.17.0 (1 constraints: 3f05563b)
6260
net.razorvine:pickle:1.3 (1 constraints: b80cce1c)
6361
net.sf.py4j:py4j:0.10.9.7 (1 constraints: b10d385f)
6462
org.antlr:antlr4-runtime:4.9.3 (1 constraints: 300ea766)

java/versions.props

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ com.google.guava:failureaccess = 1.0.3
33
com.google.guava:guava = 33.4.5-jre
44
com.google.guava:listenablefuture = 9999.0-empty-to-avoid-conflict-with-guava
55
com.jakewharton.nopen:* = 1.0.1
6-
net.java.dev.jna:* = 5.17.0
76
org.apache.spark:* = 3.5.5
87
org.immutables:value = 2.10.1
98
com.google.protobuf:protobuf-java = 4.30.1

java/vortex-jni/build.gradle.kts

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@ plugins {
99
}
1010

1111
dependencies {
12-
api("net.java.dev.jna:jna-platform")
13-
api("com.google.protobuf:protobuf-java")
14-
1512
compileOnly("org.immutables:value")
1613
annotationProcessor("org.immutables:value")
1714

1815
errorprone("com.google.errorprone:error_prone_core")
1916
errorprone("com.jakewharton.nopen:nopen-checker")
2017

2118
implementation("com.google.guava:guava")
19+
implementation("com.google.protobuf:protobuf-java")
2220
compileOnly("com.google.errorprone:error_prone_annotations")
2321
compileOnly("com.jakewharton.nopen:nopen-annotations")
2422
}
@@ -48,16 +46,64 @@ tasks.build {
4846
dependsOn("shadowJar")
4947
}
5048

49+
tasks.register("generateJniHeaders") {
50+
description = "Generates JNI header files for Java classes with native methods"
51+
group = "build"
52+
53+
// Define input and output properties
54+
val jniClasses =
55+
fileTree("src/main/java") {
56+
// Adjust this include pattern to match only files that need JNI headers
57+
include("**/JNI*.java")
58+
}
59+
60+
inputs.files(jniClasses)
61+
outputs.dir("$buildDir/generated/jni")
62+
63+
doLast {
64+
// Create output directory if it doesn't exist
65+
val headerDir = file("$buildDir/generated/jni")
66+
headerDir.mkdirs()
67+
68+
val classesDir =
69+
sourceSets["main"]
70+
.java.destinationDirectory
71+
.get()
72+
.asFile
73+
74+
// Compile only the selected files with -h option
75+
ant.withGroovyBuilder {
76+
"javac"(
77+
"classpath" to sourceSets["main"].compileClasspath.asPath,
78+
"srcdir" to "src/main/java",
79+
"includes" to jniClasses.includes.joinToString(","),
80+
"destdir" to classesDir,
81+
"includeantruntime" to false,
82+
"debug" to true,
83+
"source" to java.sourceCompatibility,
84+
"target" to java.targetCompatibility,
85+
) {
86+
"compilerarg"("line" to "-h ${headerDir.absolutePath}")
87+
}
88+
}
89+
90+
println("JNI headers generated in ${headerDir.absolutePath}")
91+
}
92+
93+
// Make this task run after the compileJava task
94+
dependsOn("compileJava")
95+
}
96+
5197
publishing {
5298
publications {
5399
create<MavenPublication>("mavenJava") {
54-
from(components["java"]) // Publishes the compiled JAR
100+
artifact(tasks.shadowJar.get())
55101
artifactId = "vortex-jni"
56102
}
57103
}
58104
}
59105

60-
val vortexFFI = projectDir.parentFile.parentFile.resolve("vortex-ffi")
106+
val vortexJNI = projectDir.parentFile.parentFile.resolve("vortex-jni")
61107

62108
val platformLibSuffix =
63109
if (System.getProperty("os.name").contains("Mac")) {
@@ -67,15 +113,15 @@ val platformLibSuffix =
67113
}
68114

69115
val targetDir = projectDir.parentFile.parentFile.resolve("target")
70-
val libraryFile = targetDir.resolve("release/libvortex_ffi.$platformLibSuffix")
116+
val libraryFile = targetDir.resolve("release/libvortex_jni.$platformLibSuffix")
71117

72118
val cargoCheck by tasks.registering(Exec::class) {
73-
workingDir = vortexFFI
119+
workingDir = vortexJNI
74120
commandLine("cargo", "check")
75121
}
76122

77123
val cargoBuild by tasks.registering(Exec::class) {
78-
workingDir = vortexFFI
124+
workingDir = vortexJNI
79125
commandLine(
80126
"cargo",
81127
"build",
@@ -90,7 +136,7 @@ val cargoBuild by tasks.registering(Exec::class) {
90136
}
91137

92138
val cargoClean by tasks.registering(Exec::class) {
93-
workingDir = vortexFFI
139+
workingDir = vortexJNI
94140
commandLine("cargo", "clean")
95141
}
96142

@@ -123,7 +169,7 @@ val copySharedLibrary by tasks.registering(Copy::class) {
123169
dependsOn(cargoBuild)
124170

125171
from(libraryFile)
126-
into(projectDir.resolve("src/main/resources/$resourceDir"))
172+
into(projectDir.resolve("src/main/resources/native/$resourceDir"))
127173

128174
doLast {
129175
println("Copied $libraryFile into resource directory")
@@ -133,3 +179,8 @@ val copySharedLibrary by tasks.registering(Copy::class) {
133179
tasks.withType<ProcessResources>().configureEach {
134180
dependsOn(copySharedLibrary)
135181
}
182+
183+
// Remove the JAR task, replace it with shadowJar
184+
tasks.named("jar").configure {
185+
enabled = false
186+
}

0 commit comments

Comments
 (0)