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

Reduce execution time of Python ORC tests #14776

Merged
merged 8 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
22 changes: 16 additions & 6 deletions python/cudf/cudf/_lib/orc.pyx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2020-2023, NVIDIA CORPORATION.
# Copyright (c) 2020-2024, NVIDIA CORPORATION.

import cudf
from cudf.core.buffer import acquire_spill_lock
Expand Down Expand Up @@ -376,20 +376,26 @@ cdef class ORCWriter:
cdef object index
cdef table_input_metadata tbl_meta
cdef object cols_as_map_type
cdef object stripe_size_bytes
cdef object stripe_size_rows

def __cinit__(self,
object path,
object index=None,
object compression="snappy",
object statistics="ROWGROUP",
object cols_as_map_type=None):
object cols_as_map_type=None,
object stripe_size_bytes=None,
object stripe_size_rows=None):

self.sink = make_sink_info(path, self._data_sink)
self.stat_freq = _get_orc_stat_freq(statistics)
self.comp_type = _get_comp_type(compression)
self.index = index
self.cols_as_map_type = cols_as_map_type \
if cols_as_map_type is None else set(cols_as_map_type)
self.stripe_size_bytes = stripe_size_bytes
self.stripe_size_rows = stripe_size_rows
self.initialized = False

def write_table(self, table):
Expand Down Expand Up @@ -457,17 +463,21 @@ cdef class ORCWriter:
pandas_metadata = generate_pandas_metadata(table, self.index)
user_data[str.encode("pandas")] = str.encode(pandas_metadata)

cdef chunked_orc_writer_options args
with nogil:
args = move(
cdef chunked_orc_writer_options c_opts = move(
chunked_orc_writer_options.builder(self.sink)
.metadata(self.tbl_meta)
.key_value_metadata(move(user_data))
.compression(self.comp_type)
.enable_statistics(self.stat_freq)
.build()
)
self.writer.reset(new orc_chunked_writer(args))
if self.stripe_size_bytes is not None:
c_opts.set_stripe_size_bytes(self.stripe_size_bytes)
if self.stripe_size_rows is not None:
c_opts.set_stripe_size_rows(self.stripe_size_rows)

with nogil:
self.writer.reset(new orc_chunked_writer(c_opts))

self.initialized = True

Expand Down
43 changes: 22 additions & 21 deletions python/cudf/cudf/tests/test_orc.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,13 +604,13 @@ def normalized_equals(value1, value2):


@pytest.mark.parametrize("stats_freq", ["STRIPE", "ROWGROUP"])
@pytest.mark.parametrize("nrows", [1, 100, 6000000])
@pytest.mark.parametrize("nrows", [1, 100, 100000])
def test_orc_write_statistics(tmpdir, datadir, nrows, stats_freq):
from pyarrow import orc

supported_stat_types = supported_numpy_dtypes + ["str"]
# Can't write random bool columns until issue #6763 is fixed
Copy link
Contributor

Choose a reason for hiding this comment

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

@vuule Do you think we should consider putting #6763 back on the queue of things to do? Seems like a bug worth fixing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's sad that we don't fully support bool columns, but we haven't had any users ask for this (that I know of).
If there's demand, I'll gladly add it to the pilebacklog. Not sure if the issue conveys this, but it's not a trivial feature.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can we explicitly disable writing bool columns? This seems like we're writing bad data, and silent corruption isn't something I feel comfortable waiting for users to discover and report.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Already done #7261 ;)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ahhhhh. That totally changes my perspective. Can you update the comments to say something like "Writing bool columns exceeding one row group are disabled in libcudf until #6763 is fixed"?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also we should update this test to check that an error is raised in this case, rather than removing the column!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test is for writing statistics, so we really want it to write a table with multiple stripes and verify the written statistics. Letting a test case throw does not contribute to this.
We do have a separate test for throwing with bool columns (as opposed to silent corruption).

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks. Apologies, I should have looked first. I have no other concerns.

if nrows == 6000000:
if nrows == 100000:
Copy link
Contributor

@bdice bdice Feb 7, 2024

Choose a reason for hiding this comment

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

Why does this work for nrows=1 or nrows=100?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can write a single row group of random bools, just not multiple (at least not in the way that does not cause issues with other readers). So anything below 10k rows is fine. I know this is very hacky :(

supported_stat_types.remove("bool")

# Make a dataframe
Expand All @@ -623,7 +623,7 @@ def test_orc_write_statistics(tmpdir, datadir, nrows, stats_freq):
fname = tmpdir.join("gdf.orc")

# Write said dataframe to ORC with cuDF
gdf.to_orc(fname.strpath, statistics=stats_freq)
gdf.to_orc(fname.strpath, statistics=stats_freq, stripe_size_rows=30000)

# Read back written ORC's statistics
orc_file = orc.ORCFile(fname)
Expand Down Expand Up @@ -678,28 +678,30 @@ def test_orc_write_statistics(tmpdir, datadir, nrows, stats_freq):


@pytest.mark.parametrize("stats_freq", ["STRIPE", "ROWGROUP"])
@pytest.mark.parametrize("nrows", [2, 100, 6000000])
@pytest.mark.parametrize("nrows", [2, 100, 200000])
def test_orc_chunked_write_statistics(tmpdir, datadir, nrows, stats_freq):
from pyarrow import orc

np.random.seed(0)
supported_stat_types = supported_numpy_dtypes + ["str"]
# Can't write random bool columns until issue #6763 is fixed
vuule marked this conversation as resolved.
Show resolved Hide resolved
if nrows == 6000000:
if nrows == 200000:
supported_stat_types.remove("bool")

gdf_fname = tmpdir.join("chunked_stats.orc")
writer = ORCWriter(gdf_fname)
writer = ORCWriter(
gdf_fname, statistics=stats_freq, stripe_size_rows=30000
)

max_char_length = 1000 if nrows < 10000 else 100
max_char_length = 100 if nrows < 10000 else 10

# Make a dataframe
gdf = cudf.DataFrame(
{
"col_"
+ str(dtype): gen_rand_series(
dtype,
int(nrows / 2),
nrows // 2,
has_nulls=True,
low=0,
high=max_char_length,
Expand All @@ -718,7 +720,7 @@ def test_orc_chunked_write_statistics(tmpdir, datadir, nrows, stats_freq):
"col_"
+ str(dtype): gen_rand_series(
dtype,
int(nrows / 2),
nrows // 2,
has_nulls=True,
low=0,
high=max_char_length,
Expand Down Expand Up @@ -785,7 +787,7 @@ def test_orc_chunked_write_statistics(tmpdir, datadir, nrows, stats_freq):
assert stats_num_vals == actual_num_vals


@pytest.mark.parametrize("nrows", [1, 100, 6000000])
@pytest.mark.parametrize("nrows", [1, 100, 100000])
def test_orc_write_bool_statistics(tmpdir, datadir, nrows):
from pyarrow import orc

Expand All @@ -794,7 +796,7 @@ def test_orc_write_bool_statistics(tmpdir, datadir, nrows):
fname = tmpdir.join("gdf.orc")

# Write said dataframe to ORC with cuDF
gdf.to_orc(fname.strpath)
gdf.to_orc(fname.strpath, stripe_size_rows=30000)

# Read back written ORC's statistics
orc_file = orc.ORCFile(fname)
Expand Down Expand Up @@ -848,21 +850,20 @@ def test_orc_bool_encode_fail():
np.random.seed(0)
buffer = BytesIO()

# Generate a boolean column longer than a single stripe
fail_df = cudf.DataFrame({"col": gen_rand_series("bool", 600000)})
# Invalidate the first row in the second stripe to break encoding
fail_df["col"][500000] = None
# Generate a boolean column longer than a single row group
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Modified this test based on the actual checks we perform on bool columns - all row groups except for the last one in each stripe need to have the number of valid elements divisible by 8. The row group size is 10k, so a single null fails this check and the writer should throw.
I have no idea what I meant with the original comments, they don't match the code at all 🤷‍♂️

fail_df = cudf.DataFrame({"col": gen_rand_series("bool", 20000)})
# Invalidate a row in the first row group
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 old comment was incorrect, the test file had a single stripe

fail_df["col"][5000] = None

# Should throw instead of generating a file that is incompatible
# with other readers (see issue #6763)
with pytest.raises(RuntimeError):
fail_df.to_orc(buffer)

# Generate a boolean column that fits into a single stripe
okay_df = cudf.DataFrame({"col": gen_rand_series("bool", 500000)})
okay_df["col"][500000 - 1] = None
# Invalid row is in the last row group of the stripe;
# encoding is assumed to be correct
# Generate a boolean column longer than a single row group
okay_df = cudf.DataFrame({"col": gen_rand_series("bool", 20000)})
okay_df["col"][15000] = None
# Invalid row is in the last row group; encoding is assumed to be correct
okay_df.to_orc(buffer)

# Also validate data
Expand Down Expand Up @@ -1130,7 +1131,7 @@ def test_pyspark_struct(datadir):
assert_eq(pdf, gdf)


def gen_map_buff(size=10000):
def gen_map_buff(size):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

default value was unused

from string import ascii_letters as al

from pyarrow import orc
Expand Down
Loading