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 }