Files
2026-07-13 12:25:07 +08:00

243 lines
10 KiB
Python

# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
# ┃ 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()