#!/usr/bin/env fbpython # Copyright (c) Meta Platforms, Inc. and affiliates. # # Licensed 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. import timeit from collections import defaultdict import click import convertible.thrift_types as python_types # noqa: F401 import convertible.types as py3_types # noqa: F401 from tabulate import tabulate from thrift.py3.converter import to_py3_struct # noqa: F401 from thrift.python.converter import to_python_struct # noqa: F401 cost_in_ms = defaultdict(list) @click.pass_context def benchmark(ctx, klass_name: str, setup: str, qualifier: str = "") -> None: title = f"{klass_name} {qualifier}" if qualifier else klass_name to_ctrl = f"to_python_struct(python_types.{klass_name}, PY3)" to_test = "PY3._to_python()" from_ctrl = f"to_py3_struct(py3_types.{klass_name}, PYTHON)" from_test = f"py3_types.{klass_name}.from_python(PYTHON)" setup_full = "\n".join([setup, "PYTHON=PY3._to_python()"]) for stmt in (to_ctrl, to_test, from_ctrl, from_test): timer = timeit.Timer( stmt=stmt, setup=setup_full, globals={ "python_types": python_types, "py3_types": py3_types, "to_python_struct": to_python_struct, "to_py3_struct": to_py3_struct, }, ) costs = [] for _ in range(ctx.params["repeat"]): (n, total) = timer.autorange() costs.append(total * 1000.0 / n) cost_in_ms[title].append(min(costs)) @click.command() @click.option( "--repeat", default=3, help="Repeat each benchmark N times and take the minimum.", ) def main(repeat: int) -> None: benchmark( "Simple", setup="PY3=py3_types.Simple()", ) benchmark( "Simple", qualifier="with large list", setup="PY3=py3_types.Simple(intList=[1] * 10_000)", ) benchmark( "Simple", qualifier="with long string", setup="PY3=py3_types.Simple(strField='a' * 10_000_000)", ) benchmark( "Nested", setup="PY3=py3_types.Nested()", ) benchmark( "Nested", qualifier="with large list", setup="PY3=py3_types.Nested(simpleList=[py3_types.Simple()] * 1_000)", ) benchmark( "Union", setup="PY3=py3_types.Union(intField=42)", ) headers = [ "thrift type", "to_python_struct", "capi Constructor", "to_py3_struct", "capi Extractor", ] table = [ ([name] + [f"{cost:.3f} ms" for cost in costs]) for (name, costs) in cost_in_ms.items() ] print( tabulate( table, headers=headers, tablefmt="github", ) ) if __name__ == "__main__": main()