blob: 62d2db39f77d1213aff1c733f5897d7248a26fa2 [file]
#
# 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.
#
"""
Microbenchmarks for ``ArrowToPandasConversion.to_pandas``, the hot path of pandas
UDF inputs: every pandas UDF eval type calls it once per batch to build the
Series it passes to the user's function.
Part of the per-batch cost is fixed per COLUMN and does not scale with row
count, so ``n_cols`` is swept alongside ``n_rows``: wide batches and small
batches are the shapes where that fixed cost dominates.
``ArrowToPandasConversion._convert_array`` converts each Arrow array by Spark type. ``long`` and
``timestamp`` are in the ``_prefer_convert_numpy`` allowlist and take
``_convert_array_numpy``; ``string`` is not, and takes ``_convert_array_legacy``. The two
allowlist types differ by an order of magnitude in conversion cost -- a timestamp
column is localized by pyarrow compute kernels -- so sweeping both shows how much
of a change is fixed per-column cost rather than per-row work.
"""
import numpy as np
import pyarrow as pa
class ArrowBatchToPandasBenchmark:
"""Benchmark ``ArrowToPandasConversion.to_pandas`` over a whole RecordBatch."""
params = [
[128, 10000],
[1, 50],
["long", "timestamp", "string"],
]
param_names = ["n_rows", "n_cols", "col_type"]
def setup(self, n_rows, n_cols, col_type):
from pyspark.sql.conversion import ArrowToPandasConversion
from pyspark.sql.types import (
LongType,
StringType,
StructField,
StructType,
TimestampType,
)
if col_type == "long":
column = pa.array(np.arange(n_rows, dtype=np.int64))
spark_type = LongType()
elif col_type == "timestamp":
base = np.datetime64("2020-01-01T00:00:00", "us")
column = pa.array(base + np.arange(n_rows) * np.timedelta64(1, "s")).cast(
pa.timestamp("us", tz="UTC")
)
spark_type = TimestampType()
elif col_type == "string":
column = pa.array([f"s{i:07d}" for i in range(n_rows)], type=pa.string())
spark_type = StringType()
else:
raise ValueError(f"unknown col_type: {col_type}")
names = [f"c{i}" for i in range(n_cols)]
self.batch = pa.RecordBatch.from_arrays([column] * n_cols, names)
self.schema = StructType([StructField(name, spark_type) for name in names])
self.to_pandas = ArrowToPandasConversion.to_pandas
def time_batch_to_pandas(self, n_rows, n_cols, col_type):
self.to_pandas(self.batch, timezone="UTC", schema=self.schema)