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