Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/python benchmarking #11125

Merged
merged 27 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
acdbebb
Add all benchmark files.
vyasr Jun 17, 2022
28ed6a8
Fix most linter issues.
vyasr Jun 17, 2022
7e3db91
Add copyrights.
vyasr Jun 17, 2022
af6882f
Add missing module docstrings.
vyasr Jun 17, 2022
97c601c
Fix long lines for flake8.
vyasr Jun 17, 2022
55434af
Remove unnecessary README.
vyasr Jun 17, 2022
97bbe3e
Run benchmarks as tests in CI.
vyasr Jun 17, 2022
0dd018b
Update environment.
vyasr Jun 17, 2022
7c370f4
Enable usage of accepts_cudf_fixture with cases.
vyasr Jun 21, 2022
83ee6b7
Use makefun to create the wrapper more thoroughly.
vyasr Jun 21, 2022
c61ba9f
Address a couple minor parts of @isVoid review.
vyasr Jun 21, 2022
690577f
Rename CUDF_BENCHMARKS_TEST_ONLY to CUDF_BENCHMARKS_DEBUG_ONLY.
vyasr Jun 21, 2022
1025be4
Address most review comments.
vyasr Jun 23, 2022
5db4589
Convert the benchmarks into a package so that we can use relative imp…
vyasr Jun 23, 2022
6d57c10
Rename accepts_cudf_fixture to benchmark_with_object.
vyasr Jun 23, 2022
0521038
Remove the name parameter to benchmark_with_object.
vyasr Jun 23, 2022
0d69a1f
Run tests from inside the benchmarks directory to find the correct cudf.
vyasr Jun 23, 2022
b060ec7
Address PR comments.
vyasr Jun 23, 2022
ec4eaa7
Try running from the root.
vyasr Jun 23, 2022
fe2a3a0
Fix path to benchmarks.
vyasr Jun 24, 2022
dd01dd4
Switch to absolute imports.
vyasr Jun 24, 2022
0183a51
Add gpuci_logger line.
vyasr Jun 24, 2022
28a6ea6
Try using py.test instead of pytest
vyasr Jun 25, 2022
a9ecdf3
Try using identical commands as for testing.
vyasr Jun 25, 2022
5e5e3d4
Revert "Convert the benchmarks into a package so that we can use rela…
vyasr Jun 25, 2022
b57eee2
Fix style.
vyasr Jun 25, 2022
5f80061
Remove unnecessary coverage parameters for benchmarks.
vyasr Jun 25, 2022
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
5 changes: 5 additions & 0 deletions ci/gpu/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ cd "$WORKSPACE/python/cudf/cudf"
gpuci_logger "Python py.test for cuDF"
py.test -n 8 --cache-clear --basetemp="$WORKSPACE/cudf-cuda-tmp" --ignore="$WORKSPACE/python/cudf/cudf/benchmarks" --junitxml="$WORKSPACE/junit-cudf.xml" -v --cov-config="$WORKSPACE/python/cudf/.coveragerc" --cov=cudf --cov-report=xml:"$WORKSPACE/python/cudf/cudf-coverage.xml" --cov-report term --dist=loadscope tests

# Run benchmarks with both cudf and pandas to ensure compatibility is maintained.
vyasr marked this conversation as resolved.
Show resolved Hide resolved
cd "$WORKSPACE/python/cudf"
CUDF_BENCHMARKS_TEST_ONLY=ON pytest -n 8 benchmarks
CUDF_BENCHMARKS_USE_PANDAS=ON CUDF_BENCHMARKS_TEST_ONLY=ON pytest -n 8 benchmarks

cd "$WORKSPACE/python/dask_cudf"
gpuci_logger "Python py.test for dask-cudf"
py.test -n 8 --cache-clear --basetemp="$WORKSPACE/dask-cudf-cuda-tmp" --junitxml="$WORKSPACE/junit-dask-cudf.xml" -v --cov-config=.coveragerc --cov=dask_cudf --cov-report=xml:"$WORKSPACE/python/dask_cudf/dask-cudf-coverage.xml" --cov-report term dask_cudf
Expand Down
1 change: 1 addition & 0 deletions conda/environments/cudf_dev_cuda11.5.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ dependencies:
- cython>=0.29,<0.30
- fsspec>=0.6.0
- pytest
- pytest-cases
- pytest-benchmark
vyasr marked this conversation as resolved.
Show resolved Hide resolved
- pytest-xdist
- sphinx
Expand Down
115 changes: 115 additions & 0 deletions python/cudf/benchmarks/API/bench_dataframe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright (c) 2022, NVIDIA CORPORATION.

"""Benchmarks of DataFrame methods."""

import string

import numpy
import pytest
from config import cudf, cupy
from utils import accepts_cudf_fixture


@pytest.mark.parametrize("N", [100, 1_000_000])
def bench_construction(benchmark, N):
benchmark(cudf.DataFrame, {None: cupy.random.rand(N)})


@accepts_cudf_fixture(cls="dataframe", dtype="float", cols=6)
@pytest.mark.parametrize(
"expr", ["a+b", "a+b+c+d+e", "a / (sin(a) + cos(b)) * tanh(d*e*f)"]
)
def bench_eval_func(benchmark, expr, dataframe):
benchmark(dataframe.eval, expr)


@accepts_cudf_fixture(
cls="dataframe", dtype="int", nulls=False, cols=6, name="df"
)
@pytest.mark.parametrize(
"nkey_cols",
vyasr marked this conversation as resolved.
Show resolved Hide resolved
[2, 3, 4],
)
def bench_merge(benchmark, df, nkey_cols):
benchmark(df.merge, df, on=list(df.columns[:nkey_cols]))


# TODO: Some of these cases could be generalized to an IndexedFrame benchmark
# instead of a DataFrame benchmark.
@accepts_cudf_fixture(cls="dataframe", dtype="int")
@pytest.mark.parametrize(
"values",
[
range(1000),
{f"key{i}": range(1000) for i in range(10)},
cudf.DataFrame({f"key{i}": range(1000) for i in range(10)}),
cudf.Series(range(1000)),
],
)
def bench_isin(benchmark, dataframe, values):
benchmark(dataframe.isin, values)


@pytest.fixture(
params=[0, numpy.random.RandomState, cupy.random.RandomState],
ids=["Seed", "NumpyRandomState", "CupyRandomState"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the type of random state used affect performance? If not, do we need all three benchmarks? Or is this really about test coverage (in which case it should be a test, not a benchmark)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance does matter for a couple of reasons, mostly around the fact that our sampling implementation is different for sampling rows vs columns and you actually can't use cupy arrays for column sampling (because it needs to happen on the host) but you could use either for sampling rows and there is a meaningful perf difference.

)
def random_state(request):
rs = request.param
return rs if isinstance(rs, int) else rs(seed=42)


@accepts_cudf_fixture(cls="dataframe", dtype="int")
@pytest.mark.parametrize("frac", [0.5])
def bench_sample(benchmark, dataframe, axis, frac, random_state):
if axis == 1 and isinstance(random_state, cupy.random.RandomState):
pytest.skip("Unsupported params.")
benchmark(
dataframe.sample, frac=frac, axis=axis, random_state=random_state
)


@accepts_cudf_fixture(cls="dataframe", dtype="int", nulls=False, cols=6)
@pytest.mark.parametrize(
"nkey_cols",
[2, 3, 4],
)
def bench_groupby(benchmark, dataframe, nkey_cols):
benchmark(dataframe.groupby, by=list(dataframe.columns[:nkey_cols]))


@accepts_cudf_fixture(cls="dataframe", dtype="int", nulls=False, cols=6)
@pytest.mark.parametrize(
"agg",
[
"sum",
["sum", "mean"],
{
f"{string.ascii_lowercase[i]}": ["sum", "mean", "count"]
for i in range(6)
Comment on lines +88 to +89
Copy link
Contributor

@bdice bdice Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to be explicit here, especially since the value 6 is not parameterized by cols=6.

Suggested change
f"{string.ascii_lowercase[i]}": ["sum", "mean", "count"]
for i in range(6)
col: ["sum", "mean", "count"]
for col in ["a", "b", "c", "d", "e", "f"]

},
],
)
@pytest.mark.parametrize(
"nkey_cols",
[2, 3, 4],
)
@pytest.mark.parametrize("as_index", [True, False])
@pytest.mark.parametrize("sort", [True, False])
def bench_groupby_agg(benchmark, dataframe, agg, nkey_cols, as_index, sort):
by = list(dataframe.columns[:nkey_cols])
benchmark(dataframe.groupby(by=by, as_index=as_index, sort=sort).agg, agg)


@accepts_cudf_fixture(cls="dataframe", dtype="int")
@pytest.mark.parametrize("ncol_sort", [1])
vyasr marked this conversation as resolved.
Show resolved Hide resolved
def bench_sort_values(benchmark, dataframe, ncol_sort):
benchmark(dataframe.sort_values, list(dataframe.columns[:ncol_sort]))


@accepts_cudf_fixture(cls="dataframe", dtype="int")
@pytest.mark.parametrize("ncol_sort", [1])
@pytest.mark.parametrize("n", [10])
def bench_nsmallest(benchmark, dataframe, ncol_sort, n):
by = list(dataframe.columns[:ncol_sort])
benchmark(dataframe.nsmallest, n, by)
88 changes: 88 additions & 0 deletions python/cudf/benchmarks/API/bench_frame_or_index.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Copyright (c) 2022, NVIDIA CORPORATION.

"""Benchmarks of methods that exist for both Frame and BaseIndex."""

import operator

import numpy as np
import pytest
from utils import accepts_cudf_fixture, make_gather_map


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
@pytest.mark.parametrize("gather_how", ["sequence", "reverse", "random"])
@pytest.mark.parametrize("fraction", [0.4])
def bench_take(benchmark, gather_how, fraction, frame_or_index):
nr = len(frame_or_index)
gather_map = make_gather_map(nr * fraction, nr, gather_how)
benchmark(frame_or_index.take, gather_map)


@pytest.mark.pandas_incompatible # Series/Index work, but not DataFrame
@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_argsort(benchmark, frame_or_index):
benchmark(frame_or_index.argsort)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_min(benchmark, frame_or_index):
benchmark(frame_or_index.min)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_where(benchmark, frame_or_index):
cond = frame_or_index % 2 == 0
benchmark(frame_or_index.where, cond, 0)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int", nulls=False)
@pytest.mark.pandas_incompatible
def bench_values_host(benchmark, frame_or_index):
benchmark(lambda: frame_or_index.values_host)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int", nulls=False)
def bench_values(benchmark, frame_or_index):
benchmark(lambda: frame_or_index.values)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_nunique(benchmark, frame_or_index):
benchmark(frame_or_index.nunique)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int", nulls=False)
def bench_to_numpy(benchmark, frame_or_index):
benchmark(frame_or_index.to_numpy)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int", nulls=False)
@pytest.mark.pandas_incompatible
def bench_to_cupy(benchmark, frame_or_index):
benchmark(frame_or_index.to_cupy)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
@pytest.mark.pandas_incompatible
def bench_to_arrow(benchmark, frame_or_index):
benchmark(frame_or_index.to_arrow)


@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_astype(benchmark, frame_or_index):
benchmark(frame_or_index.astype, float)


@pytest.mark.parametrize("ufunc", [np.add, np.logical_and])
@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_ufunc_series_binary(benchmark, frame_or_index, ufunc):
benchmark(ufunc, frame_or_index, frame_or_index)


@pytest.mark.parametrize(
"op",
[operator.add, operator.mul, operator.eq],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a comparator? Less than? It takes a different code path in libcudf so might be worth adding.

)
@accepts_cudf_fixture(cls="frame_or_index", dtype="int")
def bench_binops(benchmark, op, frame_or_index):
benchmark(lambda: op(frame_or_index, frame_or_index))
56 changes: 56 additions & 0 deletions python/cudf/benchmarks/API/bench_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) 2022, NVIDIA CORPORATION.

"""Benchmarks of free functions that accept cudf objects."""

import pytest
import pytest_cases
from config import cudf, cupy


@pytest_cases.parametrize_with_cases("objs", prefix="concat")
@pytest.mark.parametrize(
"axis",
[
1,
],
)
@pytest.mark.parametrize("join", ["inner", "outer"])
@pytest.mark.parametrize("ignore_index", [True, False])
def bench_concat_axis_1(benchmark, objs, axis, join, ignore_index):
benchmark(
cudf.concat, objs=objs, axis=axis, join=join, ignore_index=ignore_index
)


@pytest.mark.parametrize("size", [10_000, 100_000])
@pytest.mark.parametrize("cardinality", [10, 100, 1000])
@pytest.mark.parametrize("dtype", [cupy.bool_, cupy.float64])
def bench_get_dummies_high_cardinality(benchmark, size, cardinality, dtype):
"""This test is mean to test the performance of get_dummies given the
cardinality of column to encode is high.
vyasr marked this conversation as resolved.
Show resolved Hide resolved
"""
df = cudf.DataFrame(
{
"col": cudf.Series(
cupy.random.randint(low=0, high=cardinality, size=size)
).astype("category")
}
)
benchmark(cudf.get_dummies, df, columns=["col"], dtype=dtype)


@pytest.mark.parametrize("prefix", [None, "pre"])
def bench_get_dummies_simple(benchmark, prefix):
"""This test provides a small input to get_dummies to test the efficiency
of the API itself.
"""
vyasr marked this conversation as resolved.
Show resolved Hide resolved
df = cudf.DataFrame(
{
"col1": list(range(10)),
"col2": list("abcdefghij"),
"col3": cudf.Series(list(range(100, 110)), dtype="category"),
}
)
benchmark(
cudf.get_dummies, df, columns=["col1", "col2", "col3"], prefix=prefix
)
Loading