1 /*
  2  * Copyright (c) 2021, 2022, 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, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" })
 52 public class StrLenTest extends CLayouts {
 53 
 54     Arena arena = Arena.ofConfined();
 55 
 56     SegmentAllocator segmentAllocator;
 57     SegmentAllocator arenaAllocator = new RingAllocator(arena);
 58     SlicingPool pool = new SlicingPool();
 59 
 60     @Param({"5", "20", "100"})
 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().find("strlen").get(),
 73                 FunctionDescriptor.of(C_INT, C_POINTER));
 74     }
 75 
 76     @Setup
 77     public void setup() {
 78         str = makeString(size);
 79         segmentAllocator = SegmentAllocator.prefixAllocator(arena.allocate(size + 1, 1));
 80     }
 81 
 82     @TearDown
 83     public void tearDown() {
 84         arena.close();
 85     }
 86 
 87     @Benchmark
 88     public int jni_strlen() throws Throwable {
 89         return strlen(str);
 90     }
 91 
 92     @Benchmark
 93     public int panama_strlen() throws Throwable {
 94         try (Arena arena = Arena.ofConfined()) {
 95             MemorySegment segment = arena.allocateFrom(str);
 96             return (int)STRLEN.invokeExact(segment);
 97         }
 98     }
 99 
100     @Benchmark
101     public int panama_strlen_ring() throws Throwable {
102         return (int)STRLEN.invokeExact(arenaAllocator.allocateFrom(str));
103     }
104 
105     @Benchmark
106     public int panama_strlen_pool() throws Throwable {
107         Arena arena = pool.acquire();
108         int l = (int) STRLEN.invokeExact(arena.allocateFrom(str));
109         arena.close();
110         return l;
111     }
112 
113     @Benchmark
114     public int panama_strlen_prefix() throws Throwable {
115         return (int)STRLEN.invokeExact(segmentAllocator.allocateFrom(str));
116     }
117 
118     @Benchmark
119     public int panama_strlen_unsafe() throws Throwable {
120         MemorySegment address = makeStringUnsafe(str);
121         int res = (int) STRLEN.invokeExact(address);
122         freeMemory(address);
123         return res;
124     }
125 
126     static MemorySegment makeStringUnsafe(String s) {
127         byte[] bytes = s.getBytes();
128         int len = bytes.length;
129         MemorySegment address = allocateMemory(len + 1);
130         MemorySegment str = address.asSlice(0, len + 1);
131         str.copyFrom(MemorySegment.ofArray(bytes));
132         str.set(JAVA_BYTE, len, (byte)0);
133         return address;
134     }
135 
136     static native int strlen(String str);
137 
138     static String makeString(int size) {
139         String lorem = """
140                 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
141                  dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
142                  ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
143                  fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
144                  mollit anim id est laborum.
145                 """;
146         return lorem.substring(0, size);
147     }
148 
149     static class RingAllocator implements SegmentAllocator {
150         final MemorySegment segment;
151         SegmentAllocator current;
152         long rem;
153 
154         public RingAllocator(Arena session) {
155             this.segment = session.allocate(1024, 1);
156             reset();
157         }
158 
159         @Override
160         public MemorySegment allocate(long byteSize, long byteAlignment) {
161             if (rem < byteSize) {
162                 reset();
163             }
164             MemorySegment res = current.allocate(byteSize, byteAlignment);
165             long lastOffset = res.address() - segment.address() + res.byteSize();
166             rem = segment.byteSize() - lastOffset;
167             return res;
168         }
169 
170         void reset() {
171             current = SegmentAllocator.slicingAllocator(segment);
172             rem = segment.byteSize();
173         }
174     }
175 
176     static class SlicingPool {
177         final MemorySegment pool = Arena.ofAuto().allocate(1024);
178         boolean isAcquired = false;
179 
180         public Arena acquire() {
181             if (isAcquired) {
182                 throw new IllegalStateException("An allocator is already in use");
183             }
184             isAcquired = true;
185             return new SlicingPoolAllocator();
186         }
187 
188         class SlicingPoolAllocator implements Arena {
189 
190             final Arena arena = Arena.ofConfined();
191             final SegmentAllocator slicing = SegmentAllocator.slicingAllocator(pool);
192 
193             public MemorySegment allocate(long byteSize, long byteAlignment) {
194                 return slicing.allocate(byteSize, byteAlignment)
195                         .reinterpret(arena, null);
196             }
197 
198             @Override
199             public Scope scope() {
200                 return arena.scope();
201             }
202 
203             public void close() {
204                 isAcquired = false;
205                 arena.close();
206             }
207         }
208     }
209 }