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 public RingAllocator(Arena session, int size) { 159 this.segment = session.allocate(size, 1); 160 reset(); 161 } 162 163 @Override 164 public MemorySegment allocate(long byteSize, long byteAlignment) { 165 if (rem < byteSize) { 166 reset(); 167 } 168 MemorySegment res = current.allocate(byteSize, byteAlignment); 169 long lastOffset = res.address() - segment.address() + res.byteSize(); 170 rem = segment.byteSize() - lastOffset; 171 return res; 172 } 173 174 void reset() { 175 current = SegmentAllocator.slicingAllocator(segment); 176 rem = segment.byteSize(); 177 } 178 } 179 180 static class SlicingPool { 181 final MemorySegment pool; 182 boolean isAcquired = false; 183 184 public SlicingPool(int size) { 185 this.pool = Arena.ofAuto().allocate(size); 186 } 187 188 public Arena acquire() { 189 if (isAcquired) { 190 throw new IllegalStateException("An allocator is already in use"); 191 } 192 isAcquired = true; 193 return new SlicingPoolAllocator(); 194 } 195 196 class SlicingPoolAllocator implements Arena { 197 198 final Arena arena = Arena.ofConfined(); 199 final SegmentAllocator slicing = SegmentAllocator.slicingAllocator(pool); 200 201 public MemorySegment allocate(long byteSize, long byteAlignment) { 202 return slicing.allocate(byteSize, byteAlignment) 203 .reinterpret(arena, null); 204 } 205 206 @Override 207 public Scope scope() { 208 return arena.scope(); 209 } 210 211 public void close() { 212 isAcquired = false; 213 arena.close(); 214 } 215 } 216 } 217 }