1 /*
  2  * Copyright (c) 2023, 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 package org.openjdk.bench.java.lang.foreign;
 24 
 25 import org.openjdk.jmh.annotations.Benchmark;
 26 import org.openjdk.jmh.annotations.BenchmarkMode;
 27 import org.openjdk.jmh.annotations.Fork;
 28 import org.openjdk.jmh.annotations.Measurement;
 29 import org.openjdk.jmh.annotations.Mode;
 30 import org.openjdk.jmh.annotations.OutputTimeUnit;
 31 import org.openjdk.jmh.annotations.Param;
 32 import org.openjdk.jmh.annotations.Scope;
 33 import org.openjdk.jmh.annotations.Setup;
 34 import org.openjdk.jmh.annotations.State;
 35 import org.openjdk.jmh.annotations.Warmup;
 36 
 37 import java.lang.foreign.Arena;
 38 import java.lang.foreign.MemorySegment;
 39 import java.lang.foreign.ValueLayout;
 40 import java.util.concurrent.ThreadLocalRandom;
 41 import java.util.concurrent.TimeUnit;
 42 import java.util.stream.IntStream;
 43 import java.util.stream.Stream;
 44 
 45 import static java.lang.foreign.ValueLayout.*;
 46 import static jdk.internal.foreign.StringSupport.*;
 47 
 48 @BenchmarkMode(Mode.AverageTime)
 49 @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 50 @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
 51 @State(Scope.Benchmark)
 52 @OutputTimeUnit(TimeUnit.NANOSECONDS)
 53 @Fork(value = 3, jvmArgsAppend = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED", "--enable-native-access=ALL-UNNAMED", "--enable-preview"})
 54 public class InternalStrLen {
 55 
 56     private MemorySegment singleByteSegment;
 57     private MemorySegment singleByteSegmentMisaligned;
 58     private MemorySegment doubleByteSegment;
 59     private MemorySegment quadByteSegment;
 60 
 61     @Param({"1", "4", "16", "251", "1024"})
 62     int size;
 63 
 64     @Setup
 65     public void setup() {
 66         var arena = Arena.ofAuto();
 67         singleByteSegment = arena.allocate((size + 1L) * Byte.BYTES);
 68         singleByteSegmentMisaligned = arena.allocate((size + 1L) * Byte.BYTES);
 69         doubleByteSegment = arena.allocate((size + 1L) * Short.BYTES);
 70         quadByteSegment = arena.allocate((size + 1L) * Integer.BYTES);
 71         Stream.of(singleByteSegment, doubleByteSegment, quadByteSegment)
 72                 .forEach(s -> IntStream.range(0, (int) s.byteSize() - 1)
 73                         .forEach(i -> s.set(
 74                                 ValueLayout.JAVA_BYTE,
 75                                 i,
 76                                 (byte) ThreadLocalRandom.current().nextInt(1, 254)
 77                         )));
 78         singleByteSegment.set(ValueLayout.JAVA_BYTE, singleByteSegment.byteSize() - Byte.BYTES, (byte) 0);
 79         doubleByteSegment.set(ValueLayout.JAVA_SHORT, doubleByteSegment.byteSize() - Short.BYTES, (short) 0);
 80         quadByteSegment.set(ValueLayout.JAVA_INT, quadByteSegment.byteSize() - Integer.BYTES, 0);
 81         singleByteSegmentMisaligned = arena.allocate(singleByteSegment.byteSize() + 1).
 82                 asSlice(1);
 83         MemorySegment.copy(singleByteSegment, 0, singleByteSegmentMisaligned, 0, singleByteSegment.byteSize());
 84     }
 85 
 86     @Benchmark
 87     public int elementSingle() {
 88         return legacy_strlen_byte(singleByteSegment, 0);
 89     }
 90 
 91     @Benchmark
 92     public int elementByteMisaligned() {
 93         return legacy_strlen_byte(singleByteSegmentMisaligned, 0);
 94     }
 95 
 96     @Benchmark
 97     public int elementDouble() {
 98         return legacy_strlen_short(doubleByteSegment, 0);
 99     }
100 
101     @Benchmark
102     public int elementQuad() {
103         return legacy_strlen_int(quadByteSegment, 0);
104     }
105 
106     @Benchmark
107     public int chunkedSingle() {
108         return chunkedStrlenByte(singleByteSegment, 0);
109     }
110 
111     @Benchmark
112     public int chunkedSingleMisaligned() {
113         return chunkedStrlenByte(singleByteSegmentMisaligned, 0);
114     }
115 
116     @Benchmark
117     public int chunkedDouble() {
118         return chunkedStrlenShort(doubleByteSegment, 0);
119     }
120 
121     @Benchmark
122     public int changedElementQuad() {
123         return strlenInt(quadByteSegment, 0);
124     }
125 
126     // These are the legacy methods
127 
128     private static int legacy_strlen_byte(MemorySegment segment, long start) {
129         // iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
130         for (int offset = 0; offset >= 0; offset++) {
131             byte curr = segment.get(JAVA_BYTE, start + offset);
132             if (curr == 0) {
133                 return offset;
134             }
135         }
136         throw new IllegalArgumentException("String too large");
137     }
138 
139     private static int legacy_strlen_short(MemorySegment segment, long start) {
140         // iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
141         for (int offset = 0; offset >= 0; offset += 2) {
142             short curr = segment.get(JAVA_SHORT, start + offset);
143             if (curr == 0) {
144                 return offset;
145             }
146         }
147         throw new IllegalArgumentException("String too large");
148     }
149 
150     private static int legacy_strlen_int(MemorySegment segment, long start) {
151         // iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
152         for (int offset = 0; offset >= 0; offset += 4) {
153             int curr = segment.get(JAVA_INT, start + offset);
154             if (curr == 0) {
155                 return offset;
156             }
157         }
158         throw new IllegalArgumentException("String too large");
159     }
160 
161 }