1 /* 2 * Copyright (c) 2015, 2019, 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; 24 25 import java.lang.StackWalker.StackFrame; 26 import java.util.concurrent.TimeUnit; 27 import org.openjdk.jmh.annotations.Benchmark; 28 import org.openjdk.jmh.annotations.BenchmarkMode; 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.State; 34 import org.openjdk.jmh.infra.Blackhole; 35 36 /** 37 * Benchmarks for java.lang.StackWalker 38 */ 39 @State(value=Scope.Benchmark) 40 @BenchmarkMode(Mode.AverageTime) 41 @OutputTimeUnit(TimeUnit.NANOSECONDS) 42 public class StackWalkBench { 43 private static final StackWalker WALKER_DEFAULT = StackWalker.getInstance(); 44 45 private static final StackWalker WALKER_CLASS = 46 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 47 48 // TestStack will add this number of calls to the call stack 49 @Param({"4", "100", "1000"}) 50 // For more thorough testing, consider: 51 // @Param({"4", "10", "100", "256", "1000"}) 52 public int depth; 53 54 // Only used by swFilterCallerClass, to specify (roughly) how far back the 55 // call stack the target class will be found. Not needed by other 56 // benchmarks, so not a @Param by default. 57 // @Param({"4"}) 58 public int mark = 4; 59 60 /** Build a call stack of a given size, then run trigger code in it. 61 * (Does not account for existing frames higher up in the JMH machinery). 62 */ 63 public static class TestStack { 64 final long fence; 65 long current; 66 final Runnable trigger; 67 TestStack(long max, Runnable trigger)68 public TestStack(long max, Runnable trigger) { 69 this.fence = max; 70 this.current = 0; 71 this.trigger = trigger; 72 } 73 start()74 public void start() { 75 one(); 76 } 77 one()78 public void one() { 79 if (check()) { 80 two(); 81 } 82 } 83 two()84 void two() { 85 if (check()) { 86 three(); 87 } 88 } 89 three()90 private void three() { 91 if (check()) { 92 one(); 93 } 94 } 95 check()96 boolean check() { 97 if (++current == fence) { 98 trigger.run(); 99 return false; 100 } else { 101 return true; 102 } 103 } 104 } 105 106 /* Class to look for when testing filtering */ 107 static class TestMarker { call(MarkedTestStack test)108 public void call(MarkedTestStack test) { 109 test.marked(); 110 } 111 } 112 113 /** Call stack to test filtering. 114 * TestMarker will make a call on the stack. 115 */ 116 static class MarkedTestStack extends TestStack { 117 long mark; 118 119 /** 120 * @param mark How far back the stack should the TestMarker be found? 121 */ MarkedTestStack(long max, long mark, Runnable trigger)122 public MarkedTestStack(long max, long mark, Runnable trigger) { 123 super(max, trigger); 124 if (mark > max) { 125 throw new IllegalArgumentException("mark must be <= max"); 126 } 127 this.mark = max - mark; // Count backwards from the completed call stack 128 } 129 @Override start()130 public void start() { 131 if (mark == 0) { 132 mark(); 133 } else { 134 super.one(); 135 } 136 } 137 @Override check()138 boolean check() { 139 if (++current == mark) { 140 mark(); 141 return false; 142 } else if (current == fence) { 143 trigger.run(); 144 return false; 145 } else { 146 return true; 147 } 148 } mark()149 void mark() { 150 new TestMarker().call(this); 151 } marked()152 public void marked() { 153 if (current < fence) { 154 if (check()) { 155 one(); 156 } 157 } else { 158 trigger.run(); 159 } 160 } 161 } 162 163 /** 164 * StackWalker.forEach() with default options 165 */ 166 @Benchmark forEach_DefaultOpts(Blackhole bh)167 public void forEach_DefaultOpts(Blackhole bh) { 168 final Blackhole localBH = bh; 169 final boolean[] done = {false}; 170 new TestStack(depth, new Runnable() { 171 public void run() { 172 WALKER_DEFAULT.forEach(localBH::consume); 173 done[0] = true; 174 } 175 }).start(); 176 if (!done[0]) { 177 throw new RuntimeException(); 178 } 179 } 180 181 /** 182 * Use Stackwalker.walk() to fetch class names 183 */ 184 @Benchmark walk_ClassNames(Blackhole bh)185 public void walk_ClassNames(Blackhole bh) { 186 final Blackhole localBH = bh; 187 final boolean[] done = {false}; 188 new TestStack(depth, new Runnable() { 189 public void run() { 190 WALKER_DEFAULT.walk(s -> { 191 s.map(StackFrame::getClassName).forEach(localBH::consume); 192 return null; 193 }); 194 done[0] = true; 195 } 196 }).start(); 197 if (!done[0]) { 198 throw new RuntimeException(); 199 } 200 } 201 202 /** 203 * Use Stackwalker.walk() to fetch method names 204 */ 205 @Benchmark walk_MethodNames(Blackhole bh)206 public void walk_MethodNames(Blackhole bh) { 207 final Blackhole localBH = bh; 208 final boolean[] done = {false}; 209 new TestStack(depth, new Runnable() { 210 public void run() { 211 WALKER_DEFAULT.walk( s -> { 212 s.map(StackFrame::getMethodName).forEach(localBH::consume); 213 return null; 214 }); 215 done[0] = true; 216 } 217 }).start(); 218 if (!done[0]) { 219 throw new RuntimeException(); 220 } 221 } 222 223 /** 224 * Use Stackwalker.walk() to fetch declaring class instances 225 */ 226 @Benchmark walk_DeclaringClass(Blackhole bh)227 public void walk_DeclaringClass(Blackhole bh) { 228 final Blackhole localBH = bh; 229 final boolean[] done = {false}; 230 new TestStack(depth, new Runnable() { 231 public void run() { 232 WALKER_CLASS.walk(s -> { 233 s.map(StackFrame::getDeclaringClass).forEach(localBH::consume); 234 return null; 235 }); 236 done[0] = true; 237 } 238 }).start(); 239 if (!done[0]) { 240 throw new RuntimeException(); 241 } 242 } 243 244 /** 245 * Use StackWalker.walk() to fetch StackTraceElements 246 */ 247 @Benchmark walk_StackTraceElements(Blackhole bh)248 public void walk_StackTraceElements(Blackhole bh) { 249 final Blackhole localBH = bh; 250 final boolean[] done = {false}; 251 new TestStack(depth, new Runnable() { 252 public void run() { 253 WALKER_DEFAULT.walk(s -> { 254 s.map(StackFrame::toStackTraceElement).forEach(localBH::consume); 255 return null; 256 }); 257 done[0] = true; 258 } 259 }).start(); 260 if (!done[0]) { 261 throw new RuntimeException(); 262 } 263 } 264 265 /** 266 * StackWalker.getCallerClass() 267 */ 268 @Benchmark getCallerClass(Blackhole bh)269 public void getCallerClass(Blackhole bh) { 270 final Blackhole localBH = bh; 271 final boolean[] done = {false}; 272 new TestStack(depth, new Runnable() { 273 public void run() { 274 localBH.consume(WALKER_CLASS.getCallerClass()); 275 done[0] = true; 276 } 277 }).start(); 278 if (!done[0]) { 279 throw new RuntimeException(); 280 } 281 } 282 283 /** 284 * Use StackWalker.walk() to filter the StackFrames, looking for the 285 * TestMarker class, which will be (approximately) 'mark' calls back up the 286 * call stack. 287 */ 288 @Benchmark walk_filterCallerClass(Blackhole bh)289 public void walk_filterCallerClass(Blackhole bh) { 290 final Blackhole localBH = bh; 291 final boolean[] done = {false}; 292 293 new MarkedTestStack(depth, mark, new Runnable() { 294 public void run() { 295 // To be comparable with Reflection.getCallerClass(), return the Class object 296 WALKER_CLASS.walk(s -> { 297 localBH.consume(s.filter(f -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass()); 298 return null; 299 }); 300 done[0] = true; 301 } 302 }).start(); 303 304 if (!done[0]) { 305 throw new RuntimeException(); 306 } 307 } 308 309 /** 310 * Use StackWalker.walk() to filter the StackFrames, looking for the 311 * TestMarker class, which will be (approximately) depth/2 calls back up the 312 * call stack. 313 */ 314 @Benchmark walk_filterCallerClassHalfStack(Blackhole bh)315 public void walk_filterCallerClassHalfStack(Blackhole bh) { 316 final Blackhole localBH = bh; 317 final boolean[] done = {false}; 318 319 new MarkedTestStack(depth, depth / 2, new Runnable() { 320 public void run() { 321 // To be comparable with Reflection.getCallerClass(), return the Class object 322 WALKER_CLASS.walk(s -> { 323 localBH.consume(s.filter((f) -> TestMarker.class.equals(f.getDeclaringClass())).findFirst().get().getDeclaringClass()); 324 return null; 325 }); 326 done[0] = true; 327 } 328 }).start(); 329 330 if (!done[0]) { 331 throw new RuntimeException(); 332 } 333 } 334 335 // TODO: add swConsumeFramesWithReflection 336 // TODO: add swFilterOutStreamClasses 337 338 // // This benchmark is for collecting performance counter data 339 // static PerfCounter streamTime = PerfCounter.newPerfCounter("jdk.stackwalk.testStreamsElapsedTime"); 340 // static PerfCounter numStream = PerfCounter.newPerfCounter("jdk.stackwalk.numTestStreams"); 341 // // @Benchmark 342 // public void swStkFrmsTimed(Blackhole bh) { 343 // final Blackhole localBH = bh; 344 // final boolean[] done = {false}; 345 // new TestStack(depth, new Runnable() { 346 // public void run() { 347 // long t0 = System.nanoTime(); 348 // WALKER_DEFAULT.forEach(localBH::consume); 349 // streamTime.addElapsedTimeFrom(t0); 350 // numStream.increment(); 351 // done[0] = true; 352 // } 353 // }).start(); 354 // if (!done[0]) { 355 // throw new RuntimeException(); 356 // } 357 // } 358 } 359