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 }