1 /*
2 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 package org.openjdk.bench.java.lang.foreign;
25
26 import java.lang.foreign.*;
27
28 import org.openjdk.jmh.annotations.Benchmark;
29 import org.openjdk.jmh.annotations.BenchmarkMode;
30 import org.openjdk.jmh.annotations.Fork;
31 import org.openjdk.jmh.annotations.Setup;
32 import org.openjdk.jmh.annotations.Param;
33 import org.openjdk.jmh.annotations.TearDown;
34 import org.openjdk.jmh.annotations.Measurement;
35 import org.openjdk.jmh.annotations.Mode;
36 import org.openjdk.jmh.annotations.OutputTimeUnit;
37 import org.openjdk.jmh.annotations.State;
38 import org.openjdk.jmh.annotations.Warmup;
39
40 import java.lang.foreign.MemorySegment.Scope;
41 import java.lang.invoke.MethodHandle;
42 import java.util.concurrent.TimeUnit;
43
44 import static java.lang.foreign.ValueLayout.JAVA_BYTE;
45
46 @BenchmarkMode(Mode.AverageTime)
47 @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
48 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
49 @State(org.openjdk.jmh.annotations.Scope.Thread)
50 @OutputTimeUnit(TimeUnit.NANOSECONDS)
51 @Fork(value = 3, jvmArgs = { "--enable-native-access=ALL-UNNAMED", "-Djava.library.path=micro/native" })
52 public class StrLenTest extends CLayouts {
53
54 Arena arena = Arena.ofConfined();
55
56 SegmentAllocator segmentAllocator;
57 SegmentAllocator arenaAllocator;
58 SlicingPool pool;
59
60 @Param({"5", "20", "100", "451"})
61 public int size;
62 public String str;
63
64 static {
65 System.loadLibrary("StrLen");
66 }
67
68 static final MethodHandle STRLEN;
69
70 static {
71 Linker abi = Linker.nativeLinker();
72 STRLEN = abi.downcallHandle(abi.defaultLookup().findOrThrow("strlen"),
73 FunctionDescriptor.of(C_INT, C_POINTER));
74 }
75
76 @Setup
77 public void setup() {
78 str = makeString(size);
79 arenaAllocator = new RingAllocator(arena, size + 1);
80 pool = new SlicingPool(size + 1);
81 segmentAllocator = SegmentAllocator.prefixAllocator(arena.allocate(size + 1, 1));
82 }
83
84 @TearDown
85 public void tearDown() {
86 arena.close();
87 }
88
89 @Benchmark
90 public int jni_strlen() throws Throwable {
91 return strlen(str);
92 }
93
94 @Benchmark
95 public int panama_strlen_alloc() throws Throwable {
96 try (Arena arena = Arena.ofConfined()) {
97 MemorySegment segment = arena.allocateFrom(str);
98 return (int)STRLEN.invokeExact(segment);
99 }
100 }
101
102 @Benchmark
103 public int panama_strlen_ring() throws Throwable {
104 return (int)STRLEN.invokeExact(arenaAllocator.allocateFrom(str));
105 }
106
107 @Benchmark
108 public int panama_strlen_pool() throws Throwable {
109 try (Arena arena = pool.acquire()) {
110 return (int) STRLEN.invokeExact(arena.allocateFrom(str));
111 }
112 }
113
114 @Benchmark
115 public int panama_strlen_prefix() throws Throwable {
116 return (int)STRLEN.invokeExact(segmentAllocator.allocateFrom(str));
117 }
118
119 @Benchmark
120 public int panama_strlen_unsafe() throws Throwable {
121 MemorySegment address = makeStringUnsafe(str);
122 int res = (int) STRLEN.invokeExact(address);
123 freeMemory(address);
124 return res;
125 }
126
127 static MemorySegment makeStringUnsafe(String s) {
128 byte[] bytes = s.getBytes();
129 int len = bytes.length;
130 MemorySegment address = allocateMemory(len + 1);
131 MemorySegment str = address.asSlice(0, len + 1);
132 str.copyFrom(MemorySegment.ofArray(bytes));
133 str.set(JAVA_BYTE, len, (byte)0);
134 return address;
135 }
136
137 static native int strlen(String str);
138
139 static String makeString(int size) {
140 String lorem = """
141 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
142 dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
143 ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
144 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
145 mollit anim id est laborum.
146 """;
147 while (lorem.length() < size) {
148 lorem += lorem;
149 }
150 return lorem.substring(0, size);
151 }
152
153 static class RingAllocator implements SegmentAllocator {
154 final MemorySegment segment;
155 SegmentAllocator current;
156 long rem;
157
158 @SuppressWarnings("initialization")
159 public RingAllocator(Arena session, int size) {
160 this.segment = session.allocate(size, 1);
161 reset();
162 }
163
164 @Override
165 public MemorySegment allocate(long byteSize, long byteAlignment) {
166 if (rem < byteSize) {
167 reset();
168 }
169 MemorySegment res = current.allocate(byteSize, byteAlignment);
170 long lastOffset = res.address() - segment.address() + res.byteSize();
171 rem = segment.byteSize() - lastOffset;
172 return res;
173 }
174
175 void reset() {
176 current = SegmentAllocator.slicingAllocator(segment);
177 rem = segment.byteSize();
178 }
179 }
180
181 static class SlicingPool {
182 final MemorySegment pool;
183 boolean isAcquired = false;
184
185 public SlicingPool(int size) {
186 this.pool = Arena.ofAuto().allocate(size);
187 }
188
189 public Arena acquire() {
190 if (isAcquired) {
191 throw new IllegalStateException("An allocator is already in use");
192 }
193 isAcquired = true;
194 return new SlicingPoolAllocator();
195 }
196
197 class SlicingPoolAllocator implements Arena {
198
199 final Arena arena = Arena.ofConfined();
200 final SegmentAllocator slicing = SegmentAllocator.slicingAllocator(pool);
201
202 public MemorySegment allocate(long byteSize, long byteAlignment) {
203 return slicing.allocate(byteSize, byteAlignment)
204 .reinterpret(arena, null);
205 }
206
207 @Override
208 public Scope scope() {
209 return arena.scope();
210 }
211
212 public void close() {
213 isAcquired = false;
214 arena.close();
215 }
216 }
217 }
218 }