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 }