-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPanamaDecimalParser.java
More file actions
123 lines (103 loc) · 4.94 KB
/
Copy pathPanamaDecimalParser.java
File metadata and controls
123 lines (103 loc) · 4.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package io.snapdecimal.panama;
import io.snapdecimal.core.DecimalParserBackend;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.math.BigDecimal;
import java.nio.file.Path;
/**
* Panama FFM 백엔드 — JDK 22+ Foreign Function & Memory API 사용.
*
* <h3>JNI 대비 Panama의 핵심 차이점</h3>
* <pre>
* JNI 경로:
* Java → JNI frame 생성 → native 스택 전환 → Rust
* 오버헤드: ~100-200ns/call (JVM safepoint 관련 비용 포함)
*
* Panama FFM 경로:
* Java → MethodHandle downcall → Rust (직접 C ABI 호출)
* 오버헤드: ~30-50ns/call (JEP 454 벤치마크 기준)
* </pre>
*
* <h3>Zero-copy 가능 경로</h3>
* <p>Panama의 {@code MemorySegment}를 사용하면 Java 힙 외부(off-heap)에
* 버퍼를 할당하고 Rust에 직접 포인터를 전달할 수 있습니다.
* 이 구현에서는 on-heap byte[]를 슬라이스로 래핑하여 복사 없이 전달합니다.
*
* @see <a href="https://openjdk.org/jeps/454">JEP 454: Foreign Function & Memory API</a>
*/
public final class PanamaDecimalParser implements DecimalParserBackend {
private static final PanamaDecimalParser INSTANCE;
// Rust 함수: snap_parse_decimal(ptr, len, out_unscaled, out_scale) → bool
private final MethodHandle snapParseDecimal;
private final Arena sharedArena;
// 호출마다 재사용할 out-param 세그먼트 (off-heap, ThreadLocal)
// Arena.ofConfined() — 스레드 confined, GC 부담 없음
private static final ThreadLocal<Arena> CALL_ARENA = ThreadLocal.withInitial(Arena::ofConfined);
private static final ThreadLocal<MemorySegment> OUT_UNSCALED = ThreadLocal.withInitial(() ->
CALL_ARENA.get().allocate(ValueLayout.JAVA_LONG));
private static final ThreadLocal<MemorySegment> OUT_SCALE = ThreadLocal.withInitial(() ->
CALL_ARENA.get().allocate(ValueLayout.JAVA_INT));
static {
INSTANCE = new PanamaDecimalParser();
}
private PanamaDecimalParser() {
// 공유 라이브러리 로드 (JNI와 동일한 추출 경로 재활용 가능)
String libPath = resolveNativeLibPath();
sharedArena = Arena.ofShared();
SymbolLookup lib = SymbolLookup.libraryLookup(Path.of(libPath), sharedArena);
// Rust 함수 시그니처:
// bool snap_parse_decimal(const uint8_t* bytes, size_t len,
// int64_t* out_unscaled, int32_t* out_scale)
FunctionDescriptor descriptor = FunctionDescriptor.of(
ValueLayout.JAVA_BOOLEAN, // return: bool
ValueLayout.ADDRESS, // bytes_ptr: *const u8
ValueLayout.JAVA_LONG, // len: usize (64-bit JVM)
ValueLayout.ADDRESS, // out_unscaled: *mut i64
ValueLayout.ADDRESS // out_scale: *mut i32
);
Linker linker = Linker.nativeLinker();
MemorySegment funcAddr = lib.find("snap_parse_decimal")
.orElseThrow(() -> new RuntimeException("snap_parse_decimal not found in native lib"));
this.snapParseDecimal = linker.downcallHandle(funcAddr, descriptor);
}
public static PanamaDecimalParser getInstance() {
return INSTANCE;
}
@Override
public BigDecimal parse(byte[] utf8Bytes) {
MemorySegment outUnscaled = OUT_UNSCALED.get();
MemorySegment outScale = OUT_SCALE.get();
try (Arena callArena = Arena.ofConfined()) {
// byte[] → off-heap MemorySegment (1회 복사, String 변환 없음)
MemorySegment bytesSegment = callArena.allocateFrom(
ValueLayout.JAVA_BYTE, utf8Bytes
);
boolean ok = (boolean) snapParseDecimal.invoke(
bytesSegment,
(long) utf8Bytes.length,
outUnscaled,
outScale
);
if (!ok) {
throw new NumberFormatException(
"decimal-rs: invalid decimal (len=" + utf8Bytes.length + ")"
);
}
long unscaled = outUnscaled.get(ValueLayout.JAVA_LONG, 0);
int scale = outScale.get(ValueLayout.JAVA_INT, 0);
return BigDecimal.valueOf(unscaled, scale);
} catch (NumberFormatException e) {
throw e;
} catch (Throwable e) {
throw new RuntimeException("Panama FFM invocation failed", e);
}
}
private static String resolveNativeLibPath() {
// JNI 구현의 임시 파일 추출 로직 재사용 또는 시스템 경로 직접 지정
// TODO: JniDecimalParser의 extractNativeLib() 공통 유틸로 분리
String tmp = System.getProperty("java.io.tmpdir");
String os = System.getProperty("os.name").toLowerCase();
String ext = os.contains("win") ? ".dll" : os.contains("mac") ? ".dylib" : ".so";
return tmp + "/decimal_rs_core" + ext;
}
}