chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
from bench import Benchmark, Suite, Runner
|
||||
|
||||
__all__ = ["Benchmark", "Suite", "Runner"]
|
||||
@@ -0,0 +1,349 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import subprocess
|
||||
import venv
|
||||
import tornado
|
||||
from timeit import timeit
|
||||
from perspective import (
|
||||
Table,
|
||||
PerspectiveManager,
|
||||
PerspectiveTornadoHandler,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
ARROW_PATH = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"tools",
|
||||
"perspective-bench",
|
||||
"dist",
|
||||
"benchmark-python.arrow",
|
||||
)
|
||||
|
||||
BASELINE = {
|
||||
"name": [
|
||||
"arrow",
|
||||
"csv",
|
||||
"one",
|
||||
"one_1_pivot",
|
||||
"one_2_pivot",
|
||||
"to_arrow",
|
||||
"to_arrow_r1",
|
||||
"to_arrow_r1_c1",
|
||||
"to_arrow_r2",
|
||||
"to_arrow_r2_c2",
|
||||
"to_columns",
|
||||
"to_columns_r1",
|
||||
"to_columns_r1_c1",
|
||||
"to_columns_r2",
|
||||
"to_columns_r2_c2",
|
||||
"to_csv",
|
||||
"to_csv_r1",
|
||||
"to_csv_r1_c1",
|
||||
"to_csv_r2",
|
||||
"to_csv_r2_c2",
|
||||
"to_records",
|
||||
"to_records_r1",
|
||||
"to_records_r1_c1",
|
||||
"to_records_r2",
|
||||
"to_records_r2_c2",
|
||||
"two",
|
||||
"two_1x1_pivot",
|
||||
"two_2x2_pivot",
|
||||
"zero",
|
||||
],
|
||||
"time": [
|
||||
0.007100801400002865,
|
||||
0.2905632236999963,
|
||||
0.31363245369999504,
|
||||
0.5827624402000027,
|
||||
0.5723526209999988,
|
||||
0.3929776023999921,
|
||||
0.00021701669998037688,
|
||||
0.005837792799991348,
|
||||
0.0012358780999875308,
|
||||
0.06712399980000328,
|
||||
1.5685727534000022,
|
||||
0.00040022939999744266,
|
||||
0.007451989500009404,
|
||||
0.0027319223000063174,
|
||||
0.08117095989999826,
|
||||
0.5763108057000068,
|
||||
0.0003611342000112927,
|
||||
0.008143171199992593,
|
||||
0.0019054690000075425,
|
||||
0.07623720379999668,
|
||||
2.040859387900002,
|
||||
0.00047493509999867455,
|
||||
0.008475803500004986,
|
||||
0.0036788466000075458,
|
||||
0.09640174390000311,
|
||||
1.1455542396000056,
|
||||
1.9211396786000023,
|
||||
1.9244194315999947,
|
||||
0.10238117819999956,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class VirtualEnvHandler(object):
|
||||
"""Creates and manages a virtualenv for benchmarking, which allows for
|
||||
clean dependency management and benchmarking of multiple versions without
|
||||
contaminating the system's `site-packages` folder."""
|
||||
|
||||
def __init__(self, virtualenv_path):
|
||||
self._virtualenv_path = virtualenv_path
|
||||
self._is_activated = False
|
||||
|
||||
def virtualenv_exists(self):
|
||||
"""Returns whether the directory specified by `VIRTUALENV_PATH`
|
||||
exists."""
|
||||
return os.path.exists(self._virtualenv_path)
|
||||
|
||||
def activate_virtualenv(self):
|
||||
"""Activates the virtualenv at `VIRTUALENV_PATH`."""
|
||||
logging.info("Activating virtualenv at: `{}`".format(self._virtualenv_path))
|
||||
self._is_activated = True
|
||||
return ". {}/bin/activate".format(self._virtualenv_path)
|
||||
|
||||
def create_virtualenv(self):
|
||||
"""Clears the folder and creates a new virtualenv at
|
||||
`self._virtualenv_path`."""
|
||||
if self.virtualenv_exists():
|
||||
logging.ERROR("Virtualenv already exists at: `{0}`".format(self._virtualenv_path))
|
||||
return
|
||||
logging.info("Creating virtualenv at: `{}`".format(self._virtualenv_path))
|
||||
venv.create(self._virtualenv_path, clear=True, with_pip=True)
|
||||
|
||||
def deactivate_virtualenv(self):
|
||||
if self.virtualenv_exists() and self._is_activated:
|
||||
subprocess.check_output("deactivate", shell=True)
|
||||
logging.info("Virtualenv deactivated!")
|
||||
self._is_activated = False
|
||||
|
||||
|
||||
class BenchmarkTornadoHandler(tornado.web.RequestHandler):
|
||||
"""Host the results of the benchmark suite over a websocket."""
|
||||
|
||||
def set_default_headers(self):
|
||||
self.set_header("Access-Control-Allow-Origin", "*")
|
||||
self.set_header("Access-Control-Allow-Headers", "x-requested-with")
|
||||
self.set_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
|
||||
|
||||
def get(self):
|
||||
self.render("benchmark_hosted.html")
|
||||
|
||||
|
||||
class Benchmark(object):
|
||||
"""A single Benchmark function. Use as a wrapper for stateless lambdas
|
||||
with no parameters.
|
||||
|
||||
Example:
|
||||
>>> func = Benchmark(lambda: self._view.to_records(), meta={
|
||||
"name": "to_records",
|
||||
"group": "view"
|
||||
})
|
||||
"""
|
||||
|
||||
def __init__(self, func, meta={}):
|
||||
"""A decorator for a benchmark function, which will attach each attribute
|
||||
of the `meta` dictionary to the decorated function as well as provide
|
||||
it with the `benchmark` attribute.
|
||||
|
||||
Args:
|
||||
meta (dict) : a metadata dictionary, whose keys and values will
|
||||
become attributes on the benchmark function. The metadata dictionary
|
||||
should be consistent within each suite, i.e. there should be no
|
||||
additional values in between different methods decorated with
|
||||
`@benchmark`.
|
||||
"""
|
||||
self._func = func
|
||||
self._meta = meta
|
||||
self.benchmark = True
|
||||
|
||||
for k, v in self._meta.items():
|
||||
marked_key = "__BENCH__{0}".format(k)
|
||||
setattr(self, marked_key, v)
|
||||
|
||||
def __call__(self):
|
||||
"""Call the lambda bound to the decorator. This call asserts that the
|
||||
lambda has no parameters and no reference to a `self` object.
|
||||
"""
|
||||
self._func()
|
||||
|
||||
|
||||
class Suite(object):
|
||||
"""A benchmark suite stub that contains `register_benchmarks` and generic
|
||||
before/after methods.
|
||||
|
||||
Inherit from this class and implement `register_benchmarks`, which should
|
||||
set all benchmark methods as attributes on the class.
|
||||
"""
|
||||
|
||||
def register_benchmarks(self):
|
||||
"""Registers all callbacks with `Runner`.
|
||||
|
||||
This function must be implemented in all child classes of `Suite.`
|
||||
"""
|
||||
raise NotImplementedError("Must implement `register_benchmarks` to run benchmark suite.")
|
||||
|
||||
def before_all(self):
|
||||
pass
|
||||
|
||||
def after_all(self):
|
||||
pass
|
||||
|
||||
def before_each(self):
|
||||
pass
|
||||
|
||||
def after_each(self):
|
||||
pass
|
||||
|
||||
|
||||
class Runner(object):
|
||||
ITERATIONS = 10
|
||||
|
||||
def __init__(self, suite):
|
||||
"""Initializes a benchmark runner for the `Suite`.
|
||||
|
||||
Args:
|
||||
suite (Suite) : A class that inherits from `Suite`, with any number
|
||||
of instance methods decorated with `@benchmark`.
|
||||
"""
|
||||
self._suite = suite
|
||||
self._benchmarks = []
|
||||
self._table = None
|
||||
self._WROTE_RESULTS = False
|
||||
self._HOSTING = False
|
||||
|
||||
self._suite.register_benchmarks()
|
||||
|
||||
class_attrs = self._suite.__class__.__dict__.items()
|
||||
instance_attrs = self._suite.__dict__.items()
|
||||
|
||||
for k, v in class_attrs:
|
||||
if hasattr(v, "benchmark") and getattr(v, "benchmark") is True:
|
||||
logging.debug("Registering {0}".format(k))
|
||||
self._benchmarks.append(v)
|
||||
|
||||
for k, v in instance_attrs:
|
||||
if hasattr(v, "benchmark") and getattr(v, "benchmark") is True:
|
||||
logging.debug("Registering {0}".format(k))
|
||||
self._benchmarks.append(v)
|
||||
|
||||
# Write results on SIGINT
|
||||
signal.signal(signal.SIGINT, self.sigint_handler)
|
||||
|
||||
def sigint_handler(self, signum, frame):
|
||||
"""On SIGINT, host the results over a websocket."""
|
||||
if not self._WROTE_RESULTS:
|
||||
self.write_results()
|
||||
if not self._HOSTING:
|
||||
self.host_results()
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
def host_results(self):
|
||||
"""Create a tornado application that hosts the results table over a
|
||||
websocket."""
|
||||
if self._table is None:
|
||||
return
|
||||
MANAGER = PerspectiveManager()
|
||||
MANAGER.host_table("benchmark_results", self._table)
|
||||
application = tornado.web.Application(
|
||||
[
|
||||
(r"/", BenchmarkTornadoHandler),
|
||||
# create a websocket endpoint that the client Javascript can access
|
||||
(
|
||||
r"/websocket",
|
||||
PerspectiveTornadoHandler,
|
||||
{"manager": MANAGER, "check_origin": True},
|
||||
),
|
||||
]
|
||||
)
|
||||
self._HOSTING = True
|
||||
application.listen(8888)
|
||||
logging.warn("Displaying results at http://localhost:8888")
|
||||
loop = tornado.ioloop.IOLoop.current()
|
||||
loop.start()
|
||||
|
||||
def write_results(self):
|
||||
if self._table is None:
|
||||
return
|
||||
|
||||
logging.debug("Writing results to `benchmark-python.arrow`")
|
||||
if not os.path.exists(os.path.dirname(ARROW_PATH)):
|
||||
os.makedirs(os.path.dirname(ARROW_PATH))
|
||||
|
||||
with open(ARROW_PATH, "wb") as file:
|
||||
arrow = self._table.view().to_arrow(compression=None)
|
||||
file.write(arrow)
|
||||
|
||||
html_path = os.path.join(os.path.dirname(__file__), "benchmark-python.html")
|
||||
out_path = os.path.join(os.path.dirname(ARROW_PATH), "benchmark-python.html")
|
||||
with open(html_path, "r") as input:
|
||||
with open(out_path, "w") as output:
|
||||
output.write(input.read())
|
||||
|
||||
self._WROTE_RESULTS = True
|
||||
|
||||
def run_method(self, func, *args, **kwargs):
|
||||
"""Wrap the benchmark `func` with timing code and run for n
|
||||
`ITERATIONS`, returning a result row that can be fed into Perspective.
|
||||
"""
|
||||
overall_result = {k.replace("__BENCH__", ""): v for (k, v) in func.__dict__.items() if "__BENCH__" in k}
|
||||
|
||||
result = timeit(func, number=Runner.ITERATIONS) / Runner.ITERATIONS
|
||||
overall_result["__TIME__"] = result
|
||||
overall_result["baseline"] = BASELINE["time"][BASELINE["name"].index(overall_result["name"])]
|
||||
return overall_result
|
||||
|
||||
def print_result(self, result):
|
||||
logging.info(
|
||||
"{:<9} {:<14} {:<7} {:>2.4f}s".format(
|
||||
result["group"],
|
||||
result["name"],
|
||||
result["version"],
|
||||
result["__TIME__"],
|
||||
)
|
||||
)
|
||||
|
||||
def run(self, version):
|
||||
"""Runs each benchmark function from the suite for n `ITERATIONS`,
|
||||
timing each function and writing the results to a `perspective.Table`.
|
||||
"""
|
||||
logging.info("Running benchmark suite...")
|
||||
for benchmark in self._benchmarks:
|
||||
result = self.run_method(benchmark)
|
||||
result["version"] = version
|
||||
self.print_result(result)
|
||||
if self._table is None:
|
||||
if os.path.exists(ARROW_PATH):
|
||||
# if arrow exists, append to it
|
||||
with open(ARROW_PATH, "rb") as arr:
|
||||
print("Reading table from pre-existing benchmark-python.arrow")
|
||||
self._table = Table(arr.read())
|
||||
self._table.update([result])
|
||||
else:
|
||||
print("Creating new table")
|
||||
self._table = Table([result])
|
||||
else:
|
||||
self._table.update([result])
|
||||
@@ -0,0 +1,39 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||
|
||||
<script type="module" src="node_modules/@perspective-dev/viewer/dist/cdn/perspective-viewer.js"></script>
|
||||
<script type="module" src="node_modules/@perspective-dev/viewer-datagrid/dist/cdn/perspective-viewer-datagrid.js"></script>
|
||||
<script type="module" src="node_modules/@perspective-dev/viewer-charts/dist/cdn/perspective-viewer-charts.js"></script>
|
||||
|
||||
<link rel="stylesheet" crossorigin="anonymous" href="/node_modules/@perspective-dev/viewer/dist/css/themes.css" />
|
||||
|
||||
<style>
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<perspective-viewer></perspective-viewer>
|
||||
|
||||
<script type="module">
|
||||
import perspective from "/node_modules/@perspective-dev/client/dist/cdn/perspective.js";
|
||||
const el = document.getElementsByTagName("perspective-viewer")[0];
|
||||
el.addEventListener("perspective-config-update", async () => localStorage.setItem("layout", JSON.stringify(await el.save())));
|
||||
const req = await fetch("benchmark-python.arrow");
|
||||
const arrow = await req.arrayBuffer();
|
||||
const worker = perspective.worker();
|
||||
const table = await worker.table(arrow);
|
||||
el.load(table);
|
||||
el.restore(JSON.parse(localStorage.getItem("layout")));
|
||||
el.toggleConfig();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!--
|
||||
|
||||
Copyright (c) 2019, the Perspective Authors.
|
||||
|
||||
This file is part of the Perspective library, distributed under the terms of
|
||||
the Apache License 2.0. The full license can be found in the LICENSE file.
|
||||
|
||||
-->
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
||||
|
||||
<script src="http://localhost:8080/perspective-viewer"></script>
|
||||
<script src="http://localhost:8080/perspective-viewer-datagrid"></script>
|
||||
<script src="http://localhost:8080/perspective-viewer-charts"></script>
|
||||
<script src="http://localhost:8080/perspective"></script>
|
||||
|
||||
<link rel="stylesheet" href="http://localhost:8080/pro.dark.css" />
|
||||
|
||||
<style>
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<perspective-viewer id="viewer" editable plugin="Y Bar" columns='["__TIME__"]' row-pivots='["group", "name"]' column-pivots='["group", "name"]' aggregates="{'__TIME__':'avg'}">
|
||||
</perspective-viewer>
|
||||
|
||||
<script>
|
||||
const websocket = perspective.websocket("ws://localhost:8888/websocket");
|
||||
const table = websocket.open_table("benchmark_results");
|
||||
document.getElementById("viewer").load(table);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,242 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import os
|
||||
from functools import partial
|
||||
from bench import Benchmark, Suite, Runner
|
||||
from perspective import Table
|
||||
import perspective
|
||||
import logging
|
||||
|
||||
SUPERSTORE_ARROW = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"..",
|
||||
"node_modules",
|
||||
"superstore-arrow",
|
||||
"superstore.arrow",
|
||||
)
|
||||
|
||||
with open(SUPERSTORE_ARROW, "rb") as f:
|
||||
SUPERSTORE_ARROW_DATA = f.read()
|
||||
|
||||
VERSIONS = ["master", "0.4.1", "0.4.0rc6"]
|
||||
|
||||
|
||||
def make_meta(group, name):
|
||||
return {"group": group, "name": name}
|
||||
|
||||
|
||||
def empty_callback(port_id):
|
||||
pass
|
||||
|
||||
|
||||
class PerspectiveBenchmark(Suite):
|
||||
AGG_OPTIONS = [
|
||||
[{"column": "Sales", "op": "sum"}],
|
||||
[{"column": "State", "op": "dominant"}],
|
||||
[{"column": "Order Date", "op": "dominant"}],
|
||||
]
|
||||
split_by_OPTIONS = [[], ["Sub-Category"], ["Category", "Sub-Category"]]
|
||||
group_by_OPTIONS = [[], ["State"], ["State", "City"]]
|
||||
|
||||
def __init__(self):
|
||||
"""Create a benchmark suite for the `perspective-python` runtime."""
|
||||
tbl = Table(SUPERSTORE_ARROW_DATA)
|
||||
for x in range(19):
|
||||
tbl.update(SUPERSTORE_ARROW_DATA)
|
||||
self._schema = tbl.schema()
|
||||
self._df_schema = tbl.schema()
|
||||
# mutate schema to have some integer columns, so as to force numpy
|
||||
# float-to-int demotion
|
||||
self._df_schema["Sales"] = int
|
||||
self._df_schema["Profit"] = int
|
||||
self._df_schema["Quantity"] = int
|
||||
self._view = tbl.view()
|
||||
self._table = tbl
|
||||
|
||||
def _get_update_data(self, n=30):
|
||||
"""Retrieve n rows from self.records to be used as update data."""
|
||||
return SUPERSTORE_ARROW_DATA
|
||||
|
||||
def register_benchmarks(self):
|
||||
"""Register all the benchmark methods - each method creates a number of
|
||||
lambdas, and then calls `setattr` on the Suite itself so that the
|
||||
`Runner` can find the tests at runtime."""
|
||||
self.benchmark_table_arrow()
|
||||
self.benchmark_table_csv()
|
||||
self.benchmark_view_zero()
|
||||
self.benchmark_view_one()
|
||||
self.benchmark_view_two()
|
||||
self.benchmark_view_zero_updates()
|
||||
self.benchmark_view_one_updates()
|
||||
self.benchmark_view_two_updates()
|
||||
self.benchmark_to_format_zero()
|
||||
self.benchmark_to_format_one()
|
||||
self.benchmark_to_format_two()
|
||||
|
||||
def benchmark_table_arrow(self):
|
||||
"""Benchmark table from arrow separately as it requires opening the
|
||||
Arrow file from the filesystem."""
|
||||
test_meta = make_meta("table", "arrow")
|
||||
func = Benchmark(lambda: Table(SUPERSTORE_ARROW_DATA), meta=test_meta)
|
||||
setattr(self, "table_arrow", func)
|
||||
|
||||
def benchmark_table_csv(self):
|
||||
"""Benchmark table from csv separately as it requires opening the
|
||||
Arrow file from the filesystem."""
|
||||
csv = self._view.to_csv()
|
||||
test_meta = make_meta("table", "csv")
|
||||
func = Benchmark(lambda: Table(csv), meta=test_meta)
|
||||
setattr(self, "table_csv", func)
|
||||
|
||||
def benchmark_view_zero(self):
|
||||
"""Benchmark view creation with zero pivots."""
|
||||
func = Benchmark(lambda: self._table.view(), meta=make_meta("view", "zero"))
|
||||
setattr(self, "view_zero", func)
|
||||
|
||||
def benchmark_view_zero_updates(self):
|
||||
"""Benchmark how long it takes for each update to resolve fully, using
|
||||
the on update callback that forces resolution of updates across
|
||||
10 views."""
|
||||
table = Table(self._schema)
|
||||
views = [table.view() for i in range(25)]
|
||||
for v in views:
|
||||
v.on_update(empty_callback)
|
||||
update_data = self._get_update_data(1000)
|
||||
|
||||
def resolve_update():
|
||||
table.update(update_data)
|
||||
table.size()
|
||||
|
||||
func = Benchmark(resolve_update, meta=make_meta("update", "zero"))
|
||||
setattr(self, "update_zero", func)
|
||||
|
||||
def benchmark_view_one(self):
|
||||
"""Benchmark view creation with different pivots."""
|
||||
for pivot in PerspectiveBenchmark.group_by_OPTIONS:
|
||||
if len(pivot) == 0:
|
||||
continue
|
||||
test_meta = make_meta("view", "one_{0}_pivot".format(len(pivot)))
|
||||
view_constructor = partial(self._table.view, group_by=pivot)
|
||||
func = Benchmark(lambda: view_constructor(), meta=test_meta)
|
||||
setattr(self, "view_{0}".format(test_meta["name"]), func)
|
||||
|
||||
def benchmark_view_one_updates(self):
|
||||
"""Benchmark how long it takes for each update to resolve fully, using
|
||||
the on update callback that forces resolution of updates across
|
||||
25 views."""
|
||||
table = Table(self._schema)
|
||||
views = [table.view(group_by=["State", "City"]) for i in range(25)]
|
||||
for v in views:
|
||||
v.on_update(empty_callback)
|
||||
update_data = self._get_update_data(1000)
|
||||
|
||||
def resolve_update():
|
||||
table.update(update_data)
|
||||
table.size()
|
||||
|
||||
func = Benchmark(resolve_update, meta=make_meta("update", "one"))
|
||||
setattr(self, "update_one", func)
|
||||
|
||||
def benchmark_view_two(self):
|
||||
"""Benchmark view creation with row and Split By."""
|
||||
for i in range(len(PerspectiveBenchmark.group_by_OPTIONS)):
|
||||
RP = PerspectiveBenchmark.group_by_OPTIONS[i]
|
||||
CP = PerspectiveBenchmark.split_by_OPTIONS[i]
|
||||
if len(RP) == 0 and len(CP) == 0:
|
||||
continue
|
||||
test_meta = make_meta("view", "two_{0}x{1}_pivot".format(len(RP), len(CP)))
|
||||
view_constructor = partial(self._table.view, group_by=RP, split_by=CP)
|
||||
func = Benchmark(lambda: view_constructor(), meta=test_meta)
|
||||
setattr(self, "view_{0}".format(test_meta["name"]), func)
|
||||
|
||||
def benchmark_view_two_updates(self):
|
||||
"""Benchmark how long it takes for each update to resolve fully, using
|
||||
the on update callback that forces resolution of updates across
|
||||
25 views."""
|
||||
table = Table(self._schema)
|
||||
views = [table.view(group_by=["State", "City"], split_by=["Category", "Sub-Category"]) for i in range(25)]
|
||||
for v in views:
|
||||
v.on_update(empty_callback)
|
||||
update_data = self._get_update_data(1000)
|
||||
|
||||
def resolve_update():
|
||||
table.update(update_data)
|
||||
table.size()
|
||||
|
||||
func = Benchmark(resolve_update, meta=make_meta("update", "two"))
|
||||
setattr(self, "update_two", func)
|
||||
|
||||
def benchmark_to_format_zero(self):
|
||||
"""Benchmark each `to_format` method."""
|
||||
for name in (
|
||||
"arrow",
|
||||
"csv",
|
||||
"columns",
|
||||
"records",
|
||||
):
|
||||
method = "to_{0}".format(name)
|
||||
test_meta = make_meta("to_format", method)
|
||||
func = Benchmark(getattr(self._view, method), meta=test_meta)
|
||||
setattr(self, "to_format_{0}".format(name), func)
|
||||
|
||||
def benchmark_to_format_one(self):
|
||||
"""Benchmark each `to_format` method for one-sided contexts."""
|
||||
for name in (
|
||||
"arrow",
|
||||
"csv",
|
||||
"columns",
|
||||
"records",
|
||||
):
|
||||
for pivot in PerspectiveBenchmark.group_by_OPTIONS:
|
||||
if len(pivot) == 0:
|
||||
continue
|
||||
test_meta = make_meta("to_format", "to_{0}_r{1}".format(name, len(pivot)))
|
||||
view = self._table.view(group_by=pivot)
|
||||
method = "to_{0}".format(name)
|
||||
func = Benchmark(getattr(view, method), meta=test_meta)
|
||||
setattr(self, "to_format_{0}".format(test_meta["name"]), func)
|
||||
|
||||
def benchmark_to_format_two(self):
|
||||
"""Benchmark each `to_format` method for two-sided contexts."""
|
||||
for name in (
|
||||
"arrow",
|
||||
"csv",
|
||||
"columns",
|
||||
"records",
|
||||
):
|
||||
for i in range(len(PerspectiveBenchmark.group_by_OPTIONS)):
|
||||
RP = PerspectiveBenchmark.group_by_OPTIONS[i]
|
||||
CP = PerspectiveBenchmark.split_by_OPTIONS[i]
|
||||
if len(RP) == 0 and len(CP) == 0:
|
||||
continue
|
||||
test_meta = make_meta("to_format", "to_{0}_r{1}_c{2}".format(name, len(RP), len(CP)))
|
||||
view = self._table.view(group_by=RP, split_by=CP)
|
||||
method = "to_{0}".format(name)
|
||||
func = Benchmark(getattr(view, method), meta=test_meta)
|
||||
setattr(self, "to_format_{0}".format(test_meta["name"]), func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
VERSION = os.environ.get("PSP_VERSION", "Unknown Version")
|
||||
|
||||
# Initialize a suite and runner, then call `.run()`
|
||||
suite = PerspectiveBenchmark()
|
||||
runner = Runner(suite)
|
||||
|
||||
logging.info("Benchmarking perspective-python=={}".format(VERSION))
|
||||
logging.info("Detected {} {}".format(perspective.__version__, perspective.__file__))
|
||||
runner.run(VERSION)
|
||||
runner.write_results()
|
||||
@@ -0,0 +1,80 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from bench import VirtualEnvHandler
|
||||
import logging
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Benchmark the `perspective-python` runtime locally."""
|
||||
VERSIONS = [
|
||||
"master",
|
||||
"2.7.0",
|
||||
"2.6.0",
|
||||
"2.5.0",
|
||||
"2.4.0",
|
||||
"2.3.2",
|
||||
"2.3.1",
|
||||
# "2.3.0",
|
||||
"2.2.1",
|
||||
"2.2.0",
|
||||
]
|
||||
|
||||
# Access the benchmark virtualenv
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
VIRTUALENV_NAME = "benchmark_venv"
|
||||
VIRTUALENV_PATH = os.path.join(HERE, "..", "..", "..", "..", VIRTUALENV_NAME)
|
||||
venv_handler = VirtualEnvHandler(VIRTUALENV_PATH)
|
||||
|
||||
print("Benchmarking perspective-python==master")
|
||||
logging.info(venv_handler.activate_virtualenv())
|
||||
env = {k: v for (k, v) in os.environ.items()}
|
||||
env["PSP_VERSION"] = "master"
|
||||
|
||||
subprocess.check_output(
|
||||
"python3 {}/perspective_benchmark.py master".format(HERE),
|
||||
shell=True,
|
||||
env=env,
|
||||
)
|
||||
|
||||
# Run previous versions in virtualenv
|
||||
if not venv_handler.virtualenv_exists():
|
||||
venv_handler.create_virtualenv()
|
||||
|
||||
last_version = "master"
|
||||
for version in VERSIONS[1:]:
|
||||
env["PSP_VERSION"] = version
|
||||
env["PYTHONPATH"] = ""
|
||||
logging.info("Uninstalling perspective-python=={}".format(last_version))
|
||||
last_version = version
|
||||
logging.debug(
|
||||
subprocess.check_output(
|
||||
"{} && yes | python3 -m pip uninstall perspective-python".format(venv_handler.activate_virtualenv()),
|
||||
shell=True,
|
||||
env=env,
|
||||
)
|
||||
)
|
||||
logging.info("Installing perspective-python=={}".format(version))
|
||||
logging.debug(
|
||||
subprocess.check_output(
|
||||
"{} && yes | python3 -m pip install perspective-python[dev]=={}".format(venv_handler.activate_virtualenv(), version),
|
||||
shell=True,
|
||||
env=env,
|
||||
)
|
||||
)
|
||||
logging.info("Benchmarking perspective-python=={}".format(version))
|
||||
subprocess.check_output(
|
||||
"{} && python3 {}/perspective_benchmark.py {}".format(venv_handler.activate_virtualenv(), HERE, version),
|
||||
shell=True,
|
||||
env=env,
|
||||
)
|
||||
Reference in New Issue
Block a user