#!/usr/bin/python3 # # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Generate java benchmarks for 2238-varhandle-perf """ # TODO: fix constants when converting the test to a Golem benchmark from enum import Enum from pathlib import Path import io import sys class MemLoc(Enum): FIELD = 0 ARRAY = 1 BYTE_ARRAY_VIEW = 2 def to_camel_case(word): return ''.join(c for c in word.title() if not c == '_') class Benchmark: def __init__(self, code, static, vartype, flavour, klass, method, memloc, byteorder="LITTLE_ENDIAN"): self.code = code self.static = static self.vartype = vartype self.flavour = flavour self.klass = klass self.method = method self.byteorder = byteorder self.memloc = memloc def fullname(self): return "{klass}{method}{flavour}{static_name}{memloc}{byteorder}{vartype}Benchmark".format( klass = self.klass, method = to_camel_case(self.method), flavour = self.flavour, static_name = "Static" if self.static else "", memloc = to_camel_case(self.memloc.name), byteorder = to_camel_case(self.byteorder), vartype = to_camel_case(self.vartype)) def gencode(self): if self.klass == "Reflect": method_suffix = "" if self.vartype == "String" else self.vartype.title() static_first_arg = "null" elif self.klass == "Unsafe": method_suffix = "Object" if self.vartype == "String" else self.vartype.title() static_first_arg = "this.getClass()" else: method_suffix = "" static_first_arg = "" first_arg = static_first_arg if self.static else "this" return self.code.format( name = self.fullname(), method = self.method + method_suffix, flavour = self.flavour, static_name = "Static" if self.static else "", static_kwd = "static " if self.static else "", this = first_arg, this_comma = "" if not first_arg else first_arg + ", ", vartype = self.vartype, byteorder = self.byteorder, value1 = VALUES[self.vartype][0], value2 = VALUES[self.vartype][1], value1_byte_array = VALUES["byte[]"][self.byteorder][0], value2_byte_array = VALUES["byte[]"][self.byteorder][1], loop = "for (int pass = 0; pass < 100; ++pass)", iters = ITERATIONS) def BenchVHField(code, static, vartype, flavour, method): return Benchmark(code, static, vartype, flavour, "VarHandle", method, MemLoc.FIELD) def BenchVHArray(code, vartype, flavour, method): return Benchmark(code, False, vartype, flavour, "VarHandle", method, MemLoc.ARRAY) def BenchVHByteArrayView(code, byteorder, vartype, flavour, method): return Benchmark(code, False, vartype, flavour, "VarHandle", method, MemLoc.BYTE_ARRAY_VIEW, byteorder) def BenchReflect(code, static, vartype, method): return Benchmark(code, static, vartype, "", "Reflect", method, MemLoc.FIELD) def BenchUnsafe(code, static, vartype, method): return Benchmark(code, static, vartype, "", "Unsafe", method, MemLoc.FIELD) VALUES = { "int": ["42", "~42"], "float": ["3.14f", "2.17f"], "String": ["\"qwerty\"", "null"], "byte[]": { "LITTLE_ENDIAN": [ "{ (byte) VALUE, (byte) (VALUE >> 8), (byte) (VALUE >> 16), (byte) (VALUE >> 24) }", "{ (byte) VALUE, (byte) (-1 >> 8), (byte) (-1 >> 16), (byte) (-1 >> 24) }", ], "BIG_ENDIAN": [ "{ (byte) (VALUE >> 24), (byte) (VALUE >> 16), (byte) (VALUE >> 8), (byte) VALUE }", "{ (byte) (-1 >> 24), (byte) (-1 >> 16), (byte) (-1 >> 8), (byte) VALUE }", ], }, } # TODO: fix these numbers when converting the test to a Golem benchmark ITERATIONS = 1 # 3000 for a real benchmark REPEAT = 2 # 30 for a real benchmark REPEAT_HALF = (int) (REPEAT / 2) BANNER = '// This file is generated by util-src/generate_java.py do not directly modify!' VH_IMPORTS = """ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; """ VH_START = BANNER + VH_IMPORTS + """ class {name} extends MicroBenchmark {{ static final {vartype} FIELD_VALUE = {value1}; {static_kwd}{vartype} field = FIELD_VALUE; VarHandle vh; {name}() throws Throwable {{ vh = MethodHandles.lookup().find{static_name}VarHandle(this.getClass(), "field", {vartype}.class); }} """ END = """ }} }} @Override public int innerIterations() {{ return {iters}; }} }}""" VH_GET = VH_START + """ @Override public void setup() {{ {vartype} v = ({vartype}) vh.{method}{flavour}({this}); if (v != FIELD_VALUE) {{ throw new RuntimeException("field has unexpected value " + v); }} }} @Override public void run() {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}({this});""" * REPEAT + END VH_SET = VH_START + """ @Override public void teardown() {{ if (field != FIELD_VALUE) {{ throw new RuntimeException("field has unexpected value " + field); }} }} @Override public void run() {{ {vartype} x; {loop} {{""" + """ vh.{method}{flavour}({this_comma}FIELD_VALUE);""" * REPEAT + END VH_CAS = VH_START + """ @Override public void run() {{ boolean success; {loop} {{""" + """ success = vh.{method}{flavour}({this_comma}field, {value2}); success = vh.{method}{flavour}({this_comma}field, {value1});""" * REPEAT_HALF + END VH_CAE = VH_START + """ @Override public void run() {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}({this_comma}field, {value2}); x = ({vartype}) vh.{method}{flavour}({this_comma}field, {value1});""" * REPEAT_HALF + END VH_GAS = VH_START + """ @Override public void run() {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}({this_comma}{value2});""" * REPEAT + END VH_GAA = VH_START + """ @Override public void run() {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}({this_comma}{value2});""" * REPEAT + END VH_GAB = VH_START + """ @Override public void run() {{ int x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}({this_comma}{value2});""" * REPEAT + END VH_START_ARRAY = BANNER + VH_IMPORTS + """ class {name} extends MicroBenchmark {{ static final {vartype} ELEMENT_VALUE = {value1}; {vartype}[] array = {{ ELEMENT_VALUE }}; VarHandle vh; {name}() throws Throwable {{ vh = MethodHandles.arrayElementVarHandle({vartype}[].class); }} """ VH_GET_A = VH_START_ARRAY + """ @Override public void setup() {{ {vartype} v = ({vartype}) vh.{method}{flavour}(array, 0); if (v != ELEMENT_VALUE) {{ throw new RuntimeException("array element has unexpected value: " + v); }} }} @Override public void run() {{ {vartype}[] a = array; {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}(a, 0);""" * REPEAT + END VH_SET_A = VH_START_ARRAY + """ @Override public void teardown() {{ if (array[0] != {value2}) {{ throw new RuntimeException("array element has unexpected value: " + array[0]); }} }} @Override public void run() {{ {vartype}[] a = array; {vartype} x; {loop} {{""" + """ vh.{method}{flavour}(a, 0, {value2});""" * REPEAT + END VH_START_BYTE_ARRAY_VIEW = BANNER + VH_IMPORTS + """ import java.util.Arrays; import java.nio.ByteOrder; class {name} extends MicroBenchmark {{ static final {vartype} VALUE = {value1}; byte[] array1 = {value1_byte_array}; byte[] array2 = {value2_byte_array}; VarHandle vh; {name}() throws Throwable {{ vh = MethodHandles.byteArrayViewVarHandle({vartype}[].class, ByteOrder.{byteorder}); }} """ VH_GET_BAV = VH_START_BYTE_ARRAY_VIEW + """ @Override public void setup() {{ {vartype} v = ({vartype}) vh.{method}{flavour}(array1, 0); if (v != VALUE) {{ throw new RuntimeException("array has unexpected value: " + v); }} }} @Override public void run() {{ byte[] a = array1; {vartype} x; {loop} {{""" + """ x = ({vartype}) vh.{method}{flavour}(a, 0);""" * REPEAT + END VH_SET_BAV = VH_START_BYTE_ARRAY_VIEW + """ @Override public void teardown() {{ if (!Arrays.equals(array2, array1)) {{ throw new RuntimeException("array has unexpected values: " + array2[0] + " " + array2[1] + " " + array2[2] + " " + array2[3]); }} }} @Override public void run() {{ byte[] a = array2; {loop} {{""" + """ vh.{method}{flavour}(a, 0, VALUE);""" * REPEAT + END REFLECT_START = BANNER + """ import java.lang.reflect.Field; class {name} extends MicroBenchmark {{ Field field; {static_kwd}{vartype} value; {name}() throws Throwable {{ field = this.getClass().getDeclaredField("value"); }} """ REFLECT_GET = REFLECT_START + """ @Override public void run() throws Throwable {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) field.{method}({this});""" * REPEAT + END REFLECT_SET = REFLECT_START + """ @Override public void run() throws Throwable {{ {loop} {{""" + """ field.{method}({this_comma}{value1});""" * REPEAT + END UNSAFE_START = BANNER + """ import java.lang.reflect.Field; import jdk.internal.misc.Unsafe; class {name} extends UnsafeMicroBenchmark {{ long offset; {static_kwd}{vartype} value = {value1}; {name}() throws Throwable {{ Field field = this.getClass().getDeclaredField("value"); offset = get{static_name}FieldOffset(field); }} """ UNSAFE_GET = UNSAFE_START + """ @Override public void run() throws Throwable {{ {vartype} x; {loop} {{""" + """ x = ({vartype}) theUnsafe.{method}({this_comma}offset);""" * REPEAT + END UNSAFE_PUT = UNSAFE_START + """ @Override public void run() throws Throwable {{ {loop} {{""" + """ theUnsafe.{method}({this_comma}offset, {value1});""" * REPEAT + END UNSAFE_CAS = UNSAFE_START + """ @Override public void run() throws Throwable {{ {loop} {{""" + """ theUnsafe.{method}({this_comma}offset, {value1}, {value2}); theUnsafe.{method}({this_comma}offset, {value2}, {value1});""" * REPEAT_HALF + END ALL_BENCHMARKS = ( [BenchVHField(VH_GET, static, vartype, flavour, "get") for flavour in ["", "Acquire", "Opaque", "Volatile"] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_SET, static, vartype, flavour, "set") for flavour in ["", "Volatile", "Opaque", "Release"] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_CAS, static, vartype, flavour, "compareAndSet") for flavour in [""] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_CAS, static, vartype, flavour, "weakCompareAndSet") for flavour in ["", "Plain", "Acquire", "Release"] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_CAE, static, vartype, flavour, "compareAndExchange") for flavour in ["", "Acquire", "Release"] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_GAS, static, vartype, flavour, "getAndSet") for flavour in ["", "Acquire", "Release"] for static in [True, False] for vartype in ["int", "String"]] + [BenchVHField(VH_GAA, static, vartype, flavour, "getAndAdd") for flavour in ["", "Acquire", "Release"] for static in [True, False] for vartype in ["int", "float"]] + [BenchVHField(VH_GAB, static, vartype, flavour, "getAndBitwise") for flavour in [oper + mode for oper in ["Or", "Xor", "And"] for mode in ["", "Acquire", "Release"]] for static in [True, False] for vartype in ["int"]] + [BenchVHArray(VH_GET_A, vartype, flavour, "get") for flavour in [""] for vartype in ["int", "String"]] + [BenchVHArray(VH_SET_A, vartype, flavour, "set") for flavour in [""] for vartype in ["int", "String"]] + [BenchVHByteArrayView(VH_GET_BAV, byteorder, vartype, flavour, "get") for flavour in [""] for byteorder in ["BIG_ENDIAN", "LITTLE_ENDIAN"] for vartype in ["int"]] + [BenchVHByteArrayView(VH_SET_BAV, byteorder, vartype, flavour, "set") for flavour in [""] for byteorder in ["BIG_ENDIAN", "LITTLE_ENDIAN"] for vartype in ["int"]] + [BenchReflect(REFLECT_GET, static, vartype, "get") for static in [True, False] for vartype in ["int", "String"]] + [BenchReflect(REFLECT_SET, static, vartype, "set") for static in [True, False] for vartype in ["int", "String"]] + [BenchUnsafe(UNSAFE_GET, static, vartype, "get") for static in [True, False] for vartype in ["int", "String"]] + [BenchUnsafe(UNSAFE_PUT, static, vartype, "put") for static in [True, False] for vartype in ["int", "String"]] + [BenchUnsafe(UNSAFE_CAS, static, vartype, method) for method in ["compareAndSwap", "compareAndSet"] for static in [True, False] for vartype in ["int", "String"]]) MAIN = BANNER + """ public class Main { static MicroBenchmark[] benchmarks; private static void initialize() throws Throwable { benchmarks = new MicroBenchmark[] {""" + "".join([""" new {}(),""".format(b.fullname()) for b in ALL_BENCHMARKS]) + """ }; } public static void main(String[] args) throws Throwable { initialize(); for (MicroBenchmark benchmark : benchmarks) { benchmark.report(); } } }""" def main(argv): final_java_dir = Path(argv[1]) if not final_java_dir.exists() or not final_java_dir.is_dir(): print("{} is not a valid java dir".format(final_java_dir), file=sys.stderr) sys.exit(1) for bench in ALL_BENCHMARKS: file_path = final_java_dir / "{}.java".format(bench.fullname()) with file_path.open("w") as f: print(bench.gencode(), file=f) file_path = final_java_dir / "Main.java" with file_path.open("w") as f: print(MAIN, file=f) if __name__ == '__main__': main(sys.argv)