blob: 55f3100311ce2d432087c4b39041b0b6aa3142f2 [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 ``LocalDataToArrowConversion.convert``, the hot path of
Spark Connect ``createDataFrame`` and Arrow-optimized Python UDF output.
``string`` / ``binary`` columns are converted element by element: the per-element
converter is fast when the element is already the target type (``str`` /
``bytes``) and slower when it needs coercion. Each benchmark sweeps the fraction
of non-target ("other") values from 0% (all fast path) to 100% (all slow path),
for a scalar column, a one-level ``array`` column, and a two-level
``array<array<...>>`` column (which drives the inlined array fast path hardest).
"""
def _build(convert, is_string, leaf, other_val, other_frac, n_rows):
import random
from pyspark.sql.types import ArrayType, BinaryType, StringType, StructField, StructType
leaf_type = StringType() if is_string else BinaryType()
def target(i):
return f"s{i}" if is_string else b"s%d" % i
rnd = random.Random(0)
def elem(i):
return other_val(i) if rnd.random() * 100 < other_frac else target(i)
if leaf == "array2":
schema = StructType([StructField("c", ArrayType(ArrayType(leaf_type)), True)])
data = [([[elem(i), elem(i + 1)], [elem(i + 2), elem(i + 3)]],) for i in range(n_rows)]
elif leaf == "array":
schema = StructType([StructField("c", ArrayType(leaf_type), True)])
data = [([elem(i), elem(i + 1), elem(i + 2)],) for i in range(n_rows)]
else: # scalar
schema = StructType([StructField("c", leaf_type, True)])
data = [(elem(i),) for i in range(n_rows)]
return data, schema
class LocalDataToArrowStringBenchmark:
"""
Benchmark ``convert`` on a ``string`` column, sweeping the kind and fraction
of non-target values.
- ``leaf``: ``scalar``, one-level ``array``, or two-level ``array2``
(``array<array<string>>``).
- ``other``: the non-target values -- ``none`` (null), ``int`` or ``bool``
(a single non-str type, isolating one coercion branch), or ``mix`` (a
rotation of int / float / bool / Decimal / date / datetime).
- ``other_frac``: fraction of elements that are non-target, ``0`` to ``100``.
"""
params = [
[1000000],
["scalar", "array", "array2"],
["none", "int", "bool", "mix"],
[0, 30, 70, 100],
]
param_names = ["n_rows", "leaf", "other", "other_frac"]
def setup(self, n_rows, leaf, other, other_frac):
import datetime
import decimal
from pyspark.sql.conversion import LocalDataToArrowConversion
self.convert = LocalDataToArrowConversion.convert
if other == "none":
def other_val(i):
return None
elif other == "int":
def other_val(i):
return i # int coerced to string via str()
elif other == "bool":
def other_val(i):
return i % 2 == 0 # bool coerced to "true" / "false"
else: # mix: a rotation of non-str types, each coerced to string
_pool = [
123,
4.5,
True,
False,
decimal.Decimal("1.50"),
datetime.date(2020, 1, 1),
datetime.datetime(2020, 1, 1, 3, 4, 5),
]
def other_val(i):
return _pool[i % len(_pool)]
self.data, self.schema = _build(self.convert, True, leaf, other_val, other_frac, n_rows)
def time_convert(self, n_rows, leaf, other, other_frac):
self.convert(self.data, self.schema, False)
def peakmem_convert(self, n_rows, leaf, other, other_frac):
self.convert(self.data, self.schema, False)
class LocalDataToArrowBinaryBenchmark:
"""
Benchmark ``convert`` on a ``binary`` column, sweeping the fraction of
non-target values.
- ``leaf``: ``scalar``, one-level ``array``, or two-level ``array2``
(``array<array<binary>>``).
- ``other``: ``none`` (null) or ``bytearray`` (the only non-bytes input
binary accepts, copied to immutable bytes).
- ``other_frac``: fraction of elements that are non-target, ``0`` to ``100``.
"""
params = [
[1000000],
["scalar", "array", "array2"],
["none", "bytearray"],
[0, 30, 70, 100],
]
param_names = ["n_rows", "leaf", "other", "other_frac"]
def setup(self, n_rows, leaf, other, other_frac):
from pyspark.sql.conversion import LocalDataToArrowConversion
self.convert = LocalDataToArrowConversion.convert
if other == "none":
def other_val(i):
return None
else: # bytearray coerced to immutable bytes
def other_val(i):
return bytearray(b"s%d" % i)
self.data, self.schema = _build(self.convert, False, leaf, other_val, other_frac, n_rows)
def time_convert(self, n_rows, leaf, other, other_frac):
self.convert(self.data, self.schema, False)
def peakmem_convert(self, n_rows, leaf, other, other_frac):
self.convert(self.data, self.schema, False)