Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions dev/archery/archery/benchmark/jmh.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.

from itertools import filterfalse, groupby, tee
import json
import subprocess
from tempfile import NamedTemporaryFile

from .core import Benchmark
from ..utils.command import Command
from ..utils.maven import Maven


def partition(pred, iterable):
# adapted from python's examples
t1, t2 = tee(iterable)
return list(filter(pred, t1)), list(filterfalse(pred, t2))


class JavaMicrobenchmarkHarnessCommand(Command):
""" Run a Java Micro Benchmark Harness

This assumes the binary supports the standard command line options,
notably `-Dbenchmark_filter`
"""

def __init__(self, build, benchmark_filter=None):
self.benchmark_filter = benchmark_filter
self.build = build
self.maven = Maven()

""" Extract benchmark names from output between "Benchmarks:" and "[INFO]".
Assume the following output:
...
Benchmarks:
org.apache.arrow.vector.IntBenchmarks.setIntDirectly
...
org.apache.arrow.vector.IntBenchmarks.setWithValueHolder
org.apache.arrow.vector.IntBenchmarks.setWithWriter
...
[INFO]
"""

def list_benchmarks(self):
argv = []
if self.benchmark_filter:
argv.append("-Dbenchmark.filter={}".format(self.benchmark_filter))
result = self.build.list(
*argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

lists = []
benchmarks = False
for line in str.splitlines(result.stdout.decode("utf-8")):
if not benchmarks:
if line.startswith("Benchmarks:"):
benchmarks = True
else:
if line.startswith("org.apache.arrow"):
lists.append(line)
if line.startswith("[INFO]"):
break
return lists

def results(self, repetitions):
with NamedTemporaryFile(suffix=".json") as out:
argv = ["-Dbenchmark.runs={}".format(repetitions),
"-Dbenchmark.resultfile={}".format(out.name),
"-Dbenchmark.resultformat=json"]
if self.benchmark_filter:
argv.append(
"-Dbenchmark.filter={}".format(self.benchmark_filter)
)

self.build.benchmark(*argv, check=True)
return json.load(out)


class JavaMicrobenchmarkHarnessObservation:
""" Represents one run of a single Java Microbenchmark Harness
"""

def __init__(self, benchmark, primaryMetric,
forks, warmupIterations, measurementIterations, **counters):
self.name = benchmark
self.primaryMetric = primaryMetric
self.score = primaryMetric["score"]
self.score_unit = primaryMetric["scoreUnit"]
self.forks = forks
self.warmups = warmupIterations
self.runs = measurementIterations
self.counters = {
"mode": counters["mode"],
"threads": counters["threads"],
"warmups": warmupIterations,
"warmupTime": counters["warmupTime"],
"measurements": measurementIterations,
"measurementTime": counters["measurementTime"],
"jvmArgs": counters["jvmArgs"]
}
self.reciprocal_value = True if self.score_unit.endswith(
"/op") else False
if self.score_unit.startswith("ops/"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[idx+1:])
elif self.score_unit.endswith("/op"):
idx = self.score_unit.find("/")
self.normalizePerSec(self.score_unit[:idx])
else:
self.normalizeFactor = 1

@property
def value(self):
""" Return the benchmark value."""
val = 1 / self.score if self.reciprocal_value else self.score
return val * self.normalizeFactor

def normalizePerSec(self, unit):
if unit == "ns":
self.normalizeFactor = 1000 * 1000 * 1000
elif unit == "us":
self.normalizeFactor = 1000 * 1000
elif unit == "ms":
self.normalizeFactor = 1000
elif unit == "min":
self.normalizeFactor = 1 / 60
elif unit == "hr":
self.normalizeFactor = 1 / (60 * 60)
elif unit == "day":
self.normalizeFactor = 1 / (60 * 60 * 24)
else:
self.normalizeFactor = 1

@property
def unit(self):
if self.score_unit.startswith("ops/"):
return "items_per_second"
elif self.score_unit.endswith("/op"):
return "items_per_second"
else:
return "?"

def __repr__(self):
return str(self.value)


class JavaMicrobenchmarkHarness(Benchmark):
""" A set of JavaMicrobenchmarkHarnessObservations. """

def __init__(self, name, runs):
""" Initialize a JavaMicrobenchmarkHarness.

Parameters
----------
name: str
Name of the benchmark
forks: int
warmups: int
runs: int
runs: list(JavaMicrobenchmarkHarnessObservation)
Repetitions of JavaMicrobenchmarkHarnessObservation run.

"""
self.name = name
self.runs = sorted(runs, key=lambda b: b.value)
unit = self.runs[0].unit
time_unit = "N/A"
less_is_better = not unit.endswith("per_second")
values = [b.value for b in self.runs]
times = []
# Slight kludge to extract the UserCounters for each benchmark
counters = self.runs[0].counters
super().__init__(name, unit, less_is_better, values, time_unit, times,
counters)

def __repr__(self):
return "JavaMicrobenchmark[name={},runs={}]".format(
self.name, self.runs)

@classmethod
def from_json(cls, payload):
def group_key(x):
return x.name

benchmarks = map(
lambda x: JavaMicrobenchmarkHarnessObservation(**x), payload)
groups = groupby(sorted(benchmarks, key=group_key), group_key)
return [cls(k, list(bs)) for k, bs in groups]
169 changes: 135 additions & 34 deletions dev/archery/archery/benchmark/runner.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,11 @@

from .core import BenchmarkSuite
from .google import GoogleBenchmarkCommand, GoogleBenchmark
from .jmh import JavaMicrobenchmarkHarnessCommand, JavaMicrobenchmarkHarness
from ..lang.cpp import CppCMakeDefinition, CppConfiguration
from ..lang.java import JavaMavenDefinition, JavaConfiguration
from ..utils.cmake import CMakeBuild
from ..utils.maven import MavenBuild
from ..utils.logger import logger


Expand All@@ -50,40 +53,8 @@ def suites(self):

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)
raise NotImplementedError(
"BenchmarkRunner must implement from_rev_or_path")


class StaticBenchmarkRunner(BenchmarkRunner):
Expand DownExpand Up@@ -210,3 +181,133 @@ def suites(self):
continue

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, cmake_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid CMake build
directory. If so, it creates a CppBenchmarkRunner with this existing
CMakeBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh CMakeBuild.
"""
build = None
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif CMakeBuild.is_build_dir(rev_or_path):
build = CMakeBuild.from_path(rev_or_path)
return CppBenchmarkRunner(build, **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
cmake_def = CppCMakeDefinition(src_rev.cpp, cmake_conf)
build_dir = os.path.join(root_rev, "build")
return CppBenchmarkRunner(cmake_def.build(build_dir), **kwargs)


class JavaBenchmarkRunner(BenchmarkRunner):
""" Run suites for Java. """

# default repetitions is 5 for Java microbenchmark harness
def __init__(self, build, **kwargs):
""" Initialize a JavaBenchmarkRunner. """
self.build = build
super().__init__(**kwargs)

@staticmethod
def default_configuration(**kwargs):
""" Returns the default benchmark configuration. """
return JavaConfiguration(**kwargs)

def suite(self, name):
""" Returns the resulting benchmarks for a given suite. """
# update .m2 directory, which installs target jars
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(
self.build, self.benchmark_filter)

# Ensure there will be data
benchmark_names = suite_cmd.list_benchmarks()
if not benchmark_names:
return None

results = suite_cmd.results(repetitions=self.repetitions)
benchmarks = JavaMicrobenchmarkHarness.from_json(results)
return BenchmarkSuite(name, benchmarks)

@property
def list_benchmarks(self):
""" Returns all suite names """
# Ensure build is up-to-date to run benchmarks
self.build.build()

suite_cmd = JavaMicrobenchmarkHarnessCommand(self.build)
benchmark_names = suite_cmd.list_benchmarks()
for benchmark_name in benchmark_names:
yield "{}".format(benchmark_name)

@property
def suites(self):
""" Returns all suite for a runner. """
suite_name = "JavaBenchmark"
suite = self.suite(suite_name)

# Filter may exclude all benchmarks
if not suite:
logger.debug("Suite {} executed but no results"
.format(suite_name))
return

yield suite

@staticmethod
def from_rev_or_path(src, root, rev_or_path, maven_conf, **kwargs):
""" Returns a BenchmarkRunner from a path or a git revision.

First, it checks if `rev_or_path` is a valid path (or string) of a json
object that can deserialize to a BenchmarkRunner. If so, it initialize
a StaticBenchmarkRunner from it. This allows memoizing the result of a
run in a file or a string.

Second, it checks if `rev_or_path` points to a valid Maven build
directory. If so, it creates a JavaBenchmarkRunner with this existing
MavenBuild.

Otherwise, it assumes `rev_or_path` is a revision and clone/checkout
the given revision and create a fresh MavenBuild.
"""
if StaticBenchmarkRunner.is_json_result(rev_or_path):
return StaticBenchmarkRunner.from_json(rev_or_path, **kwargs)
elif MavenBuild.is_build_dir(rev_or_path):
maven_def = JavaMavenDefinition(rev_or_path, maven_conf)
return JavaBenchmarkRunner(maven_def.build(rev_or_path), **kwargs)
else:
# Revisions can references remote via the `/` character, ensure
# that the revision is path friendly
path_rev = rev_or_path.replace("/", "_")
root_rev = os.path.join(root, path_rev)
os.mkdir(root_rev)

clone_dir = os.path.join(root_rev, "arrow")
# Possibly checkout the sources at given revision, no need to
# perform cleanup on cloned repository as root_rev is reclaimed.
src_rev, _ = src.at_revision(rev_or_path, clone_dir)
maven_def = JavaMavenDefinition(src_rev.java, maven_conf)
build_dir = os.path.join(root_rev, "arrow/java")
return JavaBenchmarkRunner(maven_def.build(build_dir), **kwargs)
Loading