diff --git a/benchmarks/cpp/benchmark_report.py b/benchmarks/cpp/benchmark_report.py index ebb79d8d7f..581c564892 100644 --- a/benchmarks/cpp/benchmark_report.py +++ b/benchmarks/cpp/benchmark_report.py @@ -19,14 +19,30 @@ import os import platform import argparse -import shutil -import subprocess +import sys +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter from collections import defaultdict from datetime import datetime +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + apply_benchmark_style, + add_compact_legend, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + try: import psutil @@ -226,80 +242,16 @@ def load_benchmark_data(json_file): system_info["CPU Cores (from benchmark)"] = context["num_cpus"] -# === Plotting === -def format_tps_label(tps): - if tps >= 1e9: - return f"{tps / 1e9:.2f}G" - if tps >= 1e6: - return f"{tps / 1e6:.2f}M" - if tps >= 1e3: - return f"{tps / 1e3:.2f}K" - return f"{tps:.0f}" - - def format_tps_tick(tps, _position): - return format_tps_label(tps) - - -def plot_datatype(ax, datatype, operation): - """Plot a single datatype/operation throughput comparison.""" - if datatype not in data or operation not in data[datatype]: - ax.set_title(f"{datatype} {operation} - No Data") - ax.axis("off") - return - - libs = set(data[datatype][operation].keys()) - lib_order = [lib for lib in SERIALIZER_ORDER if lib in libs] - - times = [data[datatype][operation].get(lib, 0) for lib in lib_order] - throughput = [1e9 / t if t > 0 else 0 for t in times] - colors = [COLORS.get(lib, "#888888") for lib in lib_order] - - x = np.arange(len(lib_order)) - bars = ax.bar(x, throughput, color=colors, width=0.6) - - ax.set_title(f"{operation.capitalize()} Throughput (higher is better)") - ax.set_xticks(x) - ax.set_xticklabels([SERIALIZER_LABELS.get(lib, lib) for lib in lib_order]) - ax.set_ylabel("Throughput (ops/sec)") - ax.grid(True, axis="y", linestyle="--", alpha=0.5) - ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0)) - - # Add value labels on bars - for bar, tps_val in zip(bars, throughput): - height = bar.get_height() - ax.annotate( - format_tps_label(tps_val), - xy=(bar.get_x() + bar.get_width() / 2, height), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) + return format_throughput_tick(tps, _position) # === Create plots === -plot_images = [] datatypes = ordered_datatypes(data.keys()) operations = ["serialize", "deserialize"] -for datatype in datatypes: - fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - for i, op in enumerate(operations): - plot_datatype(axes[i], datatype, op) - fig.suptitle(f"{datatype.capitalize()} Throughput", fontsize=14) - fig.tight_layout(rect=[0, 0, 1, 0.95]) - plot_path = os.path.join(output_dir, f"{datatype}.png") - plt.savefig(plot_path, dpi=150) - plot_images.append((datatype, plot_path)) - plt.close() # === Create combined TPS comparison plot === -non_list_datatypes = [dt for dt in datatypes if not dt.endswith("list")] -list_datatypes = [dt for dt in datatypes if dt.endswith("list")] - - def plot_throughput_grid_subplot(ax, datatype): if datatype not in data: ax.set_title(f"{format_datatype_table_label(datatype)}\nNo Data") @@ -320,41 +272,44 @@ def plot_throughput_grid_subplot(ax, datatype): return operations = ["serialize", "deserialize"] - x = np.arange(len(operations)) - width = 0.8 / len(available_libs) + x = GROUP_X for idx, lib in enumerate(available_libs): times = [data[datatype][operation].get(lib, 0) for operation in operations] tps = [1e9 / t if t > 0 else 0 for t in times] - offset = (idx - (len(available_libs) - 1) / 2) * width + offset = serializer_offset(idx, len(available_libs)) ax.bar( x + offset, tps, - width, + GROUP_BAR_WIDTH, label=SERIALIZER_LABELS.get(lib, lib), color=COLORS.get(lib, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(format_datatype_table_label(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_tps = max( + 1e9 / data[datatype][operation][lib] + for operation in operations + for lib in available_libs + if data[datatype][operation].get(lib, 0) > 0 + ) + ax.set_ylim(0, max_tps * 1.12) + ax.set_title(format_datatype_table_label(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) -fig, axes = plt.subplots(2, 3, figsize=(18, 10)) +fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): plot_throughput_grid_subplot(ax, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) -fig.suptitle("C++ Serialization Throughput", fontsize=14) -fig.tight_layout() + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) +fig.suptitle("C++ Serialization Throughput", fontsize=15, fontweight="normal", y=0.955) +fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) combined_plot_path = os.path.join(output_dir, "throughput.png") -plt.savefig(combined_plot_path, dpi=150) -plot_images.append(("throughput", combined_plot_path)) +save_benchmark_figure(fig, combined_plot_path) plt.close() # === Markdown report === @@ -368,6 +323,9 @@ def plot_throughput_grid_subplot(ax, datatype): "cd ..\n", "python benchmark_report.py --json-file build/benchmark_results.json --output-dir report\n", "```\n\n", + "## Benchmark Plot\n\n", + "The plot shows throughput (ops/sec); higher is better.\n\n", + f"![Throughput]({args.plot_prefix}throughput.png)\n\n", "## Hardware & OS Info\n\n", "| Key | Value |\n", "|-----|-------|\n", @@ -375,28 +333,6 @@ def plot_throughput_grid_subplot(ax, datatype): for k, v in system_info.items(): md_report.append(f"| {k} | {v} |\n") -# Plots section -md_report.append("\n## Benchmark Plots\n") -md_report.append("\nAll class-level plots below show throughput (ops/sec).\n") -plot_images_sorted = sorted( - plot_images, - key=lambda item: ( - 0 if item[0] == "throughput" else 1, - DATATYPE_ORDER_INDEX.get(item[0], len(DATATYPE_ORDER)), - item[0], - ), -) -for datatype, img in plot_images_sorted: - img_filename = os.path.basename(img) - img_path_report = args.plot_prefix + img_filename - plot_title = ( - "Throughput" - if datatype == "throughput" - else format_datatype_table_label(datatype) - ) - md_report.append(f"\n### {plot_title}\n\n") - md_report.append(f"![{plot_title}]({img_path_report})\n") - # Results table md_report.append("\n## Benchmark Results\n\n") md_report.append("### Timing Results (nanoseconds)\n\n") @@ -492,9 +428,7 @@ def plot_throughput_grid_subplot(ax, datatype): with open(report_path, "w", encoding="utf-8") as f: f.writelines(md_report) -prettier = shutil.which("prettier") -if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) +format_markdown_with_prettier(report_path) print(f"✅ Plots saved in: {output_dir}") print(f"📄 Markdown report generated at: {report_path}") diff --git a/benchmarks/cpp/run.sh b/benchmarks/cpp/run.sh index 51f2dcc7b3..ac2f2af8b4 100755 --- a/benchmarks/cpp/run.sh +++ b/benchmarks/cpp/run.sh @@ -20,6 +20,7 @@ set -e export ENABLE_FORY_DEBUG_OUTPUT=0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/cpp" # Colors for output RED='\033[0;31m' @@ -33,6 +34,7 @@ DATA="" SERIALIZER="" DEBUG_BUILD=false DURATION="" +COPY_DOCS=true # Parse arguments usage() { @@ -47,6 +49,7 @@ usage() { echo " Filter benchmark by serializer" echo " --duration Minimum time to run each benchmark (e.g., 10, 30)" echo " --debug Build with debug symbols and low optimization for profiling" + echo " --no-copy-docs Skip copying report/plots into docs/benchmarks/cpp" echo " --help Show this help message" echo "" echo "Examples:" @@ -80,6 +83,10 @@ while [[ $# -gt 0 ]]; do DEBUG_BUILD=true shift ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; --help|-h) usage ;; @@ -205,10 +212,19 @@ if ! python3 -c "import matplotlib" 2>/dev/null; then fi python3 benchmark_report.py --json-file build/benchmark_results.json --output-dir report +if [[ "$COPY_DOCS" == true ]]; then + mkdir -p "$DOCS_DIR" + cp report/README.md "$DOCS_DIR/README.md" + cp report/throughput.png "$DOCS_DIR/throughput.png" + echo -e "${GREEN}Copied report and throughput plot to: ${DOCS_DIR}${NC}" +fi echo "" echo -e "${GREEN}=== All done! ===${NC}" -echo -e "Report generated at: ${SCRIPT_DIR}/report/REPORT.md" +echo -e "Report generated at: ${SCRIPT_DIR}/report/README.md" echo -e "Plots saved in: ${SCRIPT_DIR}/report/" +if [[ "$COPY_DOCS" == true ]]; then + echo -e "Docs sync: ${DOCS_DIR}" +fi echo "" echo -e "For profiling/flamegraph, run: ${YELLOW}./profile.sh --help${NC}" diff --git a/benchmarks/csharp/README.md b/benchmarks/csharp/README.md index 04e72dc3a7..33df7def87 100644 --- a/benchmarks/csharp/README.md +++ b/benchmarks/csharp/README.md @@ -24,7 +24,7 @@ This runs all benchmark cases and generates: - `build/benchmark_results.json` - `report/README.md` -- `report/throughput.png` and per-datatype throughput plots (for example `report/struct.png`) +- `report/throughput.png` ## Run Options @@ -79,4 +79,4 @@ Per-case winners vary by payload and operation. The full breakdown is generated - `benchmarks/csharp/build/benchmark_results.json` - `benchmarks/csharp/report/README.md` -- `benchmarks/csharp/report/*.png` +- `benchmarks/csharp/report/throughput.png` diff --git a/benchmarks/csharp/benchmark_report.py b/benchmarks/csharp/benchmark_report.py index dc11f61118..c21c079413 100755 --- a/benchmarks/csharp/benchmark_report.py +++ b/benchmarks/csharp/benchmark_report.py @@ -20,15 +20,31 @@ import json import os import platform -import shutil -import subprocess +import sys from collections import defaultdict from datetime import datetime +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + apply_benchmark_style, + add_compact_legend, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + try: import psutil @@ -138,18 +154,8 @@ def format_datatype_table_label(datatype: str) -> str: return datatype.capitalize() -def format_tps_label(tps: float) -> str: - if tps >= 1e9: - return f"{tps / 1e9:.2f}G" - if tps >= 1e6: - return f"{tps / 1e6:.2f}M" - if tps >= 1e3: - return f"{tps / 1e3:.2f}K" - return f"{tps:.0f}" - - def format_tps_tick(tps: float, _position) -> str: - return format_tps_label(tps) + return format_throughput_tick(tps, _position) def preferred_ordered_values(values, preferred): @@ -247,44 +253,6 @@ def build_coverage(rows): } -def plot_datatype(ax, throughputs: dict, datatype: str, operation: str) -> None: - if datatype not in throughputs or operation not in throughputs[datatype]: - ax.set_title(f"{datatype} {operation} - No Data") - ax.axis("off") - return - - libs = set(throughputs[datatype][operation].keys()) - lib_order = [lib for lib in SERIALIZER_ORDER if lib in libs] - if not lib_order: - ax.set_title(f"{datatype} {operation} - No Supported Serializer Data") - ax.axis("off") - return - throughput = [throughputs[datatype][operation].get(lib, 0) for lib in lib_order] - colors = [COLORS.get(lib, "#888888") for lib in lib_order] - - x = np.arange(len(lib_order)) - bars = ax.bar(x, throughput, color=colors, width=0.6) - - ax.set_title(f"{operation.capitalize()} Throughput (higher is better)") - ax.set_xticks(x) - ax.set_xticklabels([SERIALIZER_LABELS.get(lib, lib) for lib in lib_order]) - ax.set_ylabel("Throughput (ops/sec)") - ax.grid(True, axis="y", linestyle="--", alpha=0.5) - ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0)) - - for bar, tps_value in zip(bars, throughput): - height = bar.get_height() - ax.annotate( - format_tps_label(tps_value), - xy=(bar.get_x() + bar.get_width() / 2, height), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) - - def plot_throughput_grid_subplot(ax, throughputs, datatype): if datatype not in throughputs: ax.set_title(f"{format_datatype_table_label(datatype)}\nNo Data") @@ -304,28 +272,35 @@ def plot_throughput_grid_subplot(ax, throughputs, datatype): ax.axis("off") return - x = np.arange(len(PREFERRED_OPERATION_ORDER)) - width = 0.8 / len(available_libs) + x = GROUP_X for idx, lib in enumerate(available_libs): tps = [ throughputs[datatype][operation].get(lib, 0) for operation in PREFERRED_OPERATION_ORDER ] - offset = (idx - (len(available_libs) - 1) / 2) * width + offset = serializer_offset(idx, len(available_libs)) ax.bar( x + offset, tps, - width, + GROUP_BAR_WIDTH, label=SERIALIZER_LABELS.get(lib, lib), color=COLORS.get(lib, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(format_datatype_table_label(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_tps = max( + throughputs[datatype][operation][lib] + for operation in PREFERRED_OPERATION_ORDER + for lib in available_libs + if throughputs[datatype][operation].get(lib, 0) > 0 + ) + ax.set_ylim(0, max_tps * 1.12) + ax.set_title(format_datatype_table_label(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def build_markdown( @@ -337,7 +312,6 @@ def build_markdown( sizes, datatypes, operations, - plot_images, ): report_lines = [ "# C# Benchmark Performance Report\n\n", @@ -348,6 +322,9 @@ def build_markdown( "dotnet run -c Release --project ./Fory.CSharpBenchmark.csproj -- --output build/benchmark_results.json\n", "python3 benchmark_report.py --json-file build/benchmark_results.json --output-dir report\n", "```\n\n", + "## Benchmark Plot\n\n", + "The plot shows throughput (ops/sec); higher is better.\n\n", + f"![Throughput]({args.plot_prefix}throughput.png)\n\n", "## Hardware & OS Info\n\n", "| Key | Value |\n", "|-----|-------|\n", @@ -376,22 +353,6 @@ def build_markdown( "\n> Warning: benchmark input is partial; plots/tables only show available cases.\n" ) - report_lines.append("\n## Benchmark Plots\n") - report_lines.append("\nAll class-level plots below show throughput (ops/sec).\n") - plot_images_sorted = sorted( - plot_images, key=lambda item: (0 if item[0] == "throughput" else 1, item[0]) - ) - for datatype, img in plot_images_sorted: - img_filename = os.path.basename(img) - img_path_report = args.plot_prefix + img_filename - plot_title = ( - "Throughput" - if datatype == "throughput" - else format_datatype_table_label(datatype) - ) - report_lines.append(f"\n### {plot_title}\n\n") - report_lines.append(f"![{plot_title}]({img_path_report})\n") - report_lines.append("\n## Benchmark Results\n\n") report_lines.append("### Timing Results (nanoseconds)\n\n") report_lines.append( @@ -503,31 +464,17 @@ def main() -> None: list(operations_present), PREFERRED_OPERATION_ORDER ) - plot_images = [] - for datatype in datatypes: - fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - for index, operation in enumerate(PREFERRED_OPERATION_ORDER): - plot_datatype(axes[index], throughputs, datatype, operation) - fig.suptitle(f"{format_datatype_table_label(datatype)} Throughput", fontsize=14) - fig.tight_layout(rect=[0, 0, 1, 0.95]) - plot_path = os.path.join(output_dir, f"{datatype}.png") - plt.savefig(plot_path, dpi=150) - plot_images.append((datatype, plot_path)) - plt.close() - - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) for index, (ax, datatype) in enumerate(zip(axes.flat, PREFERRED_DATATYPE_ORDER)): plot_throughput_grid_subplot(ax, throughputs, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) - fig.suptitle("C# Serialization Throughput", fontsize=14) - fig.tight_layout() + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) + fig.suptitle( + "C# Serialization Throughput", fontsize=15, fontweight="normal", y=0.955 + ) + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) throughput_path = os.path.join(output_dir, "throughput.png") - plt.savefig(throughput_path, dpi=150) - plot_images.append(("throughput", throughput_path)) + save_benchmark_figure(fig, throughput_path) plt.close() report = build_markdown( @@ -539,7 +486,6 @@ def main() -> None: sizes=sizes, datatypes=datatypes, operations=operations, - plot_images=plot_images, ) legacy_report_path = os.path.join(output_dir, "REPORT.md") if os.path.exists(legacy_report_path): @@ -548,9 +494,7 @@ def main() -> None: with open(report_path, "w", encoding="utf-8") as f: f.write(report) - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) + format_markdown_with_prettier(report_path) if coverage["is_partial"]: print( diff --git a/benchmarks/csharp/run.sh b/benchmarks/csharp/run.sh index fa436553ab..2c078ca711 100755 --- a/benchmarks/csharp/run.sh +++ b/benchmarks/csharp/run.sh @@ -21,6 +21,7 @@ export ENABLE_FORY_DEBUG_OUTPUT=0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/csharp" RED='\033[0;31m' GREEN='\033[0;32m' @@ -32,6 +33,7 @@ SERIALIZER="" DURATION="3" WARMUP="1" OUTPUT_DIR="" +COPY_DOCS=true usage() { cat < Measure duration per benchmark (default: 3) --warmup Warmup duration per benchmark (default: 1) --output-dir Base directory for benchmark outputs + --no-copy-docs Skip copying report/plots into docs/benchmarks/csharp --help Show this help USAGE exit 0 @@ -74,6 +77,10 @@ while [[ $# -gt 0 ]]; do OUTPUT_DIR="$2" shift 2 ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; --help|-h) usage ;; @@ -126,6 +133,12 @@ if ! python3 -c "import matplotlib" 2>/dev/null; then fi python3 benchmark_report.py --json-file "$RESULT_JSON" --output-dir "$REPORT_DIR" +if [[ "$COPY_DOCS" == true ]]; then + mkdir -p "$DOCS_DIR" + cp "$REPORT_DIR/README.md" "$DOCS_DIR/README.md" + cp "$REPORT_DIR/throughput.png" "$DOCS_DIR/throughput.png" + echo -e "${GREEN}Copied report and throughput plot to: ${DOCS_DIR}${NC}" +fi echo "" echo -e "${GREEN}=== All done! ===${NC}" @@ -138,3 +151,6 @@ else fi echo "Report generated at: $REPORT_PATH" echo "Plots saved in: $REPORT_PLOTS_DIR/" +if [[ "$COPY_DOCS" == true ]]; then + echo "Docs sync: $DOCS_DIR" +fi diff --git a/benchmarks/dart/benchmark_report.py b/benchmarks/dart/benchmark_report.py index 4332a68256..fb4bdd3e90 100644 --- a/benchmarks/dart/benchmark_report.py +++ b/benchmarks/dart/benchmark_report.py @@ -19,15 +19,31 @@ import json import math import os -import shutil import socket import subprocess +import sys from collections import defaultdict +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + apply_benchmark_style, + add_compact_legend, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) COLORS = { "fory": "#FF6F01", @@ -76,36 +92,14 @@ def collect_results(payload: dict) -> dict: return results -def format_label(value: float) -> str: - if value >= 1e6: - return f"{value / 1e6:.2f}M" - if value >= 1e3: - return f"{value / 1e3:.2f}K" - return f"{value:.2f}" - - def format_tick(value: float, _position) -> str: - return format_label(value) + return format_throughput_tick(value, _position) def format_int(value: float) -> str: return f"{int(round(value)):,}" -def datatype_plot_label(data_type: str) -> str: - if data_type == "struct": - return "NumericStruct" - if data_type == "structlist": - return "NumericStruct\nList" - if data_type == "mediacontent": - return "MediaContent" - if data_type == "mediacontentlist": - return "MediaContent\nList" - if data_type.endswith("list"): - return f"{data_type[:-4].capitalize()}\nList" - return data_type.capitalize() - - def fastest_entry(values: dict[str, float]) -> str: positive = {serializer: value for serializer, value in values.items() if value > 0} if not positive: @@ -165,93 +159,56 @@ def plot_summary_group(ax, results: dict, data_type: str) -> None: return operations = ["serialize", "deserialize"] - x_positions = np.arange(len(operations)) - bar_width = 0.8 / len(serializers) + x_positions = GROUP_X for index, serializer in enumerate(serializers): values = [ results.get(data_type, {}).get(operation, {}).get(serializer, 0.0) for operation in operations ] - offset = (index - (len(serializers) - 1) / 2) * bar_width + offset = serializer_offset(index, len(serializers)) ax.bar( x_positions + offset, values, - width=bar_width, + width=GROUP_BAR_WIDTH, label=serializer, color=COLORS[serializer], + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(DISPLAY_NAMES[data_type]) - ax.set_xticks(x_positions) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_value = max( + results.get(data_type, {}).get(operation, {}).get(serializer, 0.0) + for operation in operations + for serializer in serializers + ) + ax.set_ylim(0, max_value * 1.12) + ax.set_title(DISPLAY_NAMES[data_type], pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def save_summary_plot(results: dict, output_dir: str) -> str: - figure, axes = plt.subplots(2, 3, figsize=(18, 10)) - figure.suptitle("Dart Serialization Throughput", fontsize=14) + figure, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) + figure.suptitle( + "Dart Serialization Throughput", fontsize=15, fontweight="normal", y=0.955 + ) for index, (ax, data_type) in enumerate(zip(axes.flat, DATA_TYPES)): plot_summary_group(ax, results, data_type) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) - figure.tight_layout() + figure.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) path = os.path.join(output_dir, "throughput.png") - figure.savefig(path, dpi=150) + save_benchmark_figure(figure, path) plt.close(figure) return path -def save_per_type_plots(results: dict, output_dir: str) -> list[tuple[str, str]]: - plot_paths = [] - for data_type in DATA_TYPES: - operations = results.get(data_type, {}) - if not operations: - continue - figure, axes = plt.subplots(1, 2, figsize=(12, 5)) - for index, operation in enumerate(["serialize", "deserialize"]): - serializers = SERIALIZERS - values = [ - operations.get(operation, {}).get(serializer, 0.0) - for serializer in serializers - ] - bars = axes[index].bar( - serializers, - values, - color=[COLORS[serializer] for serializer in serializers], - ) - axes[index].set_title(f"{operation.capitalize()} throughput") - axes[index].set_ylabel("ops/s") - axes[index].grid(True, axis="y", linestyle="--", alpha=0.4) - for bar, value in zip(bars, values): - axes[index].annotate( - format_label(value), - xy=(bar.get_x() + bar.get_width() / 2, value), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) - figure.suptitle(DISPLAY_NAMES[data_type]) - figure.tight_layout(rect=[0, 0, 1, 0.95]) - path = os.path.join(output_dir, f"{data_type}.png") - figure.savefig(path, dpi=150) - plt.close(figure) - plot_paths.append((DISPLAY_NAMES[data_type], path)) - return plot_paths - - -def write_report( - payload: dict, results: dict, output_dir: str, plot_paths: list[tuple[str, str]] -): +def write_report(payload: dict, results: dict, output_dir: str): metadata = payload["metadata"] report_path = os.path.join(output_dir, "README.md") with open(report_path, "w", encoding="utf-8") as handle: @@ -260,6 +217,8 @@ def write_report( "This benchmark compares serialization and deserialization throughput for " "Apache Fory, Protocol Buffers, and JSON in Dart.\n\n" ) + handle.write("## Throughput Plot\n\n") + handle.write("![Throughput](throughput.png)\n\n") handle.write("## Hardware and Runtime Info\n\n") handle.write("| Key | Value |\n") handle.write("| --- | --- |\n") @@ -267,7 +226,6 @@ def write_report( handle.write(f"| {key} | {value} |\n") handle.write("\n## Throughput Results\n\n") - handle.write("![Throughput](throughput.png)\n\n") handle.write( "| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest |\n" ) @@ -300,15 +258,7 @@ def write_report( f"| {DISPLAY_NAMES[data_type]} | {sizes['fory']} | {sizes['protobuf']} | {sizes['json']} |\n" ) - if plot_paths: - handle.write("\n## Per-workload Plots\n\n") - for display_name, path in plot_paths: - handle.write(f"### {display_name}\n\n") - handle.write(f"![{display_name}]({os.path.basename(path)})\n\n") - - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) + format_markdown_with_prettier(report_path) def main() -> None: @@ -317,8 +267,7 @@ def main() -> None: payload = load_payload(args.json_file) results = collect_results(payload) save_summary_plot(results, args.output_dir) - plot_paths = save_per_type_plots(results, args.output_dir) - write_report(payload, results, args.output_dir, plot_paths) + write_report(payload, results, args.output_dir) if __name__ == "__main__": diff --git a/benchmarks/dart/run.sh b/benchmarks/dart/run.sh index ff3f1301ac..11f5f176a8 100644 --- a/benchmarks/dart/run.sh +++ b/benchmarks/dart/run.sh @@ -22,9 +22,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" OUTPUT_DIR="$SCRIPT_DIR/results" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/dart" BUILD_DIR="$SCRIPT_DIR/build" RUNNER="$BUILD_DIR/benchmark_runner" GENERATE_REPORT=true +COPY_DOCS=true DATA="" SERIALIZER="" OPERATION="" @@ -43,6 +45,7 @@ usage() { echo " --duration Seconds per sample (default: 1.5)." echo " --warmup Warmup seconds per case (default: 1.0)." echo " --no-report Skip report generation." + echo " --no-copy-docs Skip copying report/plots into docs/benchmarks/dart." echo " -h, --help Show this help." } @@ -76,6 +79,10 @@ while [[ $# -gt 0 ]]; do GENERATE_REPORT=false shift ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; -h|--help) usage exit 0 @@ -135,6 +142,12 @@ if $GENERATE_REPORT; then python3 "$SCRIPT_DIR/benchmark_report.py" \ --json-file "$OUTPUT_DIR/benchmark_results.json" \ --output-dir "$OUTPUT_DIR" + if [[ "$COPY_DOCS" == true ]]; then + mkdir -p "$DOCS_DIR" + cp "$OUTPUT_DIR/README.md" "$DOCS_DIR/README.md" + cp "$OUTPUT_DIR/throughput.png" "$DOCS_DIR/throughput.png" + echo "Copied report and throughput plot to: $DOCS_DIR" + fi fi echo "" @@ -142,3 +155,6 @@ echo "============================================" echo "Benchmark complete!" echo "============================================" echo "Results saved to: $OUTPUT_DIR" +if [[ "$GENERATE_REPORT" == true && "$COPY_DOCS" == true ]]; then + echo "Docs sync: $DOCS_DIR" +fi diff --git a/benchmarks/go/README.md b/benchmarks/go/README.md index ddfcf39bda..699c1263de 100644 --- a/benchmarks/go/README.md +++ b/benchmarks/go/README.md @@ -93,7 +93,7 @@ go test -bench=BenchmarkFory_NumericStruct -benchmem Example results on Apple M1 Pro: -![Combined Benchmark](../../docs/benchmarks/go/benchmark_combined.png) +![Combined Benchmark](../../docs/benchmarks/go/throughput.png) | Data Type | Operation | Fory (ops/s) | Protobuf (ops/s) | Msgpack (ops/s) | Fory vs PB | Fory vs MP | | ----------------- | ----------- | ------------ | ---------------- | --------------- | ---------- | ---------- | @@ -148,7 +148,7 @@ After running `./run.sh`: - `benchmark_results.txt` - Human-readable benchmark output - `benchmark_results.json` - JSON format for programmatic analysis - `benchmark_report.md` - Generated markdown report -- `benchmark_*.png` - Performance comparison charts (requires matplotlib) +- `throughput.png` - Combined throughput chart (requires matplotlib) ## Directory Structure diff --git a/benchmarks/go/benchmark_report.py b/benchmarks/go/benchmark_report.py index 17c9857a31..cf62cb7bb7 100755 --- a/benchmarks/go/benchmark_report.py +++ b/benchmarks/go/benchmark_report.py @@ -25,8 +25,6 @@ import os import platform import re -import shutil -import subprocess import sys from collections import defaultdict from datetime import datetime @@ -41,6 +39,25 @@ HAS_MATPLOTLIB = False print("Warning: matplotlib not installed. Skipping plot generation.") +if HAS_MATPLOTLIB: + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + add_compact_legend, + apply_benchmark_style, + format_markdown_with_prettier, + format_throughput_label, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, + ) + + apply_benchmark_style(plt) + # Color scheme (matching C++ benchmark) COLORS = { @@ -69,12 +86,16 @@ def format_ops_per_sec(value): + if HAS_MATPLOTLIB: + return format_throughput_label(value) if value >= 1e6: return f"{value / 1e6:.2f}M" return f"{value / 1e3:.0f}K" def format_ops_tick(value, _position): + if HAS_MATPLOTLIB: + return format_throughput_tick(value, _position) return format_ops_per_sec(value) @@ -187,96 +208,6 @@ def load_serialized_sizes(output_dir): return {} -def generate_plots(results, output_dir): - """Generate comparison plots for each data type.""" - if not HAS_MATPLOTLIB: - return - - for datatype in DATATYPES: - if datatype not in results: - continue - - fig, axes = plt.subplots(1, 2, figsize=(14, 6)) - fig.suptitle( - f"{display_name(datatype)} Serialization Benchmark", - fontsize=14, - ) - - for idx, op in enumerate(OPERATIONS): - ax = axes[idx] - - if op not in results[datatype]: - continue - - data = results[datatype][op] - available_serializers = [s for s in SERIALIZERS if s in data] - - if not available_serializers: - continue - - # Convert ns to ops/sec - ops_per_sec = [ - 1e9 / data[s] if s in data else 0 for s in available_serializers - ] - colors = [COLORS.get(s, "#888888") for s in available_serializers] - - bars = ax.bar(available_serializers, ops_per_sec, color=colors) - ax.set_ylabel("Operations/sec") - ax.set_title(f"{op.title()}") - - # Add value labels on bars - for bar, val in zip(bars, ops_per_sec): - height = bar.get_height() - ax.annotate( - format_ops_per_sec(val), - xy=(bar.get_x() + bar.get_width() / 2, height), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) - - # Add speedup annotations - if "fory" in data: - fory_val = 1e9 / data["fory"] - speedup_lines = [] - for s in available_serializers: - if s != "fory" and s in data: - other_val = 1e9 / data[s] - speedup = fory_val / other_val - if speedup > 1: - speedup_lines.append( - f"Fory {speedup:.1f}x faster than {s.title()}" - ) - - if speedup_lines: - ax.text( - 0.98, - 0.98, - "\n".join(speedup_lines), - transform=ax.transAxes, - ha="right", - va="top", - fontsize=9, - color="green", - bbox=dict( - boxstyle="round,pad=0.25", - facecolor="white", - edgecolor="none", - alpha=0.85, - ), - ) - - plt.tight_layout() - plt.savefig( - os.path.join(output_dir, f"benchmark_{datatype}.png"), - dpi=150, - bbox_inches="tight", - ) - plt.close() - - def plot_throughput_grid_subplot(ax, results, datatype): """Plot one datatype with Serialize/Deserialize operation groups.""" if datatype not in results: @@ -294,10 +225,9 @@ def plot_throughput_grid_subplot(ax, results, datatype): ax.set_title(display_name(datatype)) return - x = range(len(OPERATIONS)) - width = min(0.8 / len(available_serializers), 0.25) + x = GROUP_X offsets = [ - (idx - (len(available_serializers) - 1) / 2) * width + serializer_offset(idx, len(available_serializers)) for idx in range(len(available_serializers)) ] @@ -309,17 +239,25 @@ def plot_throughput_grid_subplot(ax, results, datatype): ax.bar( [position + offset for position in x], values, - width, + GROUP_BAR_WIDTH, label=serializer.title(), color=COLORS.get(serializer, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(display_name(datatype)) - ax.set_xticks(list(x)) - ax.set_xticklabels([op.title() for op in OPERATIONS]) - ax.grid(True, axis="y", alpha=0.25) + max_value = max( + 1e9 / results[datatype][operation][serializer] + for operation in OPERATIONS + for serializer in available_serializers + if results[datatype].get(operation, {}).get(serializer) + ) + ax.set_ylim(0, max_value * 1.12) + ax.set_title(display_name(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_ops_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def generate_combined_plot(results, output_dir): @@ -327,24 +265,21 @@ def generate_combined_plot(results, output_dir): if not HAS_MATPLOTLIB: return - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) fig.suptitle( "Go Serialization Throughput", - fontsize=16, + fontsize=15, + fontweight="normal", + y=0.955, ) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPES)): plot_throughput_grid_subplot(ax, results, datatype) if index % 3 == 0: - ax.set_ylabel("ops/sec") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) - - plt.tight_layout() - plt.savefig( - os.path.join(output_dir, "throughput.png"), dpi=150, bbox_inches="tight" - ) + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) + + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) + save_benchmark_figure(fig, os.path.join(output_dir, "throughput.png")) plt.close() @@ -357,6 +292,11 @@ def generate_markdown_report(results, output_dir): report.append("# Go Serialization Benchmark Report\n") report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + # Plot section + if HAS_MATPLOTLIB: + report.append("## Performance Chart\n") + report.append("![Throughput](throughput.png)\n") + # System info report.append("## System Information\n") report.append(f"- **OS**: {platform.system()} {platform.release()}") @@ -459,18 +399,6 @@ def generate_markdown_report(results, output_dir): else: report.append("No serialized size data found.\n") - # Plots section - if HAS_MATPLOTLIB: - report.append("## Performance Charts\n") - report.append("### Throughput") - report.append("![Throughput](throughput.png)\n") - for datatype in datatypes: - if datatype in results: - report.append(f"### {display_name(datatype)}") - report.append( - f"![{display_name(datatype)} Benchmark](benchmark_{datatype}.png)\n" - ) - # Write report report_paths = [ os.path.join(output_dir, "README.md"), @@ -480,9 +408,7 @@ def generate_markdown_report(results, output_dir): with open(report_path, "w") as f: f.write("\n".join(report)) - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", *report_paths], check=True) + format_markdown_with_prettier(*report_paths) print(f"Report generated: {report_paths[0]}") @@ -518,7 +444,6 @@ def main(): print("Parsed results for data types:", list(results.keys())) # Generate outputs - generate_plots(results, output_dir) generate_combined_plot(results, output_dir) generate_markdown_report(results, output_dir) diff --git a/benchmarks/go/run.sh b/benchmarks/go/run.sh index 547d74192b..98b1089ca7 100755 --- a/benchmarks/go/run.sh +++ b/benchmarks/go/run.sh @@ -23,6 +23,7 @@ cd "$SCRIPT_DIR" # Output directory for results OUTPUT_DIR="$SCRIPT_DIR/results" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/go" mkdir -p "$OUTPUT_DIR" # Default values @@ -31,6 +32,7 @@ SERIALIZER="" COUNT=5 BENCHTIME="1s" GENERATE_REPORT=true +COPY_DOCS=true # Parse arguments while [[ $# -gt 0 ]]; do @@ -55,6 +57,10 @@ while [[ $# -gt 0 ]]; do GENERATE_REPORT=false shift ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; -h|--help) echo "Usage: $0 [options]" echo "" @@ -64,6 +70,7 @@ while [[ $# -gt 0 ]]; do echo " --count Number of benchmark runs (default: 5)" echo " --benchtime Time for each benchmark (default: 1s)" echo " --no-report Skip report generation" + echo " --no-copy-docs Skip copying report/plots into docs/benchmarks/go" echo " -h, --help Show this help message" echo "" echo "Examples:" @@ -226,6 +233,14 @@ if $GENERATE_REPORT; then else echo "Warning: Python not found. Skipping report generation." fi + if [[ "$COPY_DOCS" == true && -f "$OUTPUT_DIR/README.md" ]]; then + mkdir -p "$DOCS_DIR" + cp "$OUTPUT_DIR/README.md" "$DOCS_DIR/README.md" + cp "$OUTPUT_DIR/throughput.png" "$DOCS_DIR/throughput.png" + cp "$OUTPUT_DIR/benchmark_results.txt" "$DOCS_DIR/benchmark_results.txt" + cp "$OUTPUT_DIR/serialized_sizes.txt" "$DOCS_DIR/serialized_sizes.txt" + echo "Copied report, throughput plot, and text outputs to: $DOCS_DIR" + fi fi echo "" @@ -236,6 +251,9 @@ echo "Results saved to: $OUTPUT_DIR/" echo " - benchmark_results.txt (human-readable)" echo " - benchmark_results.json (JSON format)" if $GENERATE_REPORT; then - echo " - benchmark_report.md (report)" - echo " - benchmark_*.png (plots)" + echo " - README.md and benchmark_report.md (report)" + echo " - throughput.png (plot)" + if [[ "$COPY_DOCS" == true ]]; then + echo "Docs sync: $DOCS_DIR" + fi fi diff --git a/benchmarks/java/analyze.py b/benchmarks/java/analyze.py index 5e3cedb90e..20cba40689 100644 --- a/benchmarks/java/analyze.py +++ b/benchmarks/java/analyze.py @@ -20,10 +20,13 @@ """ import matplotlib.pyplot as plt +import numpy as np import os import pandas as pd from pathlib import Path import re +import shutil +import subprocess dir_path = os.path.dirname(os.path.realpath(__file__)) repo_root = Path(dir_path).parent.parent @@ -229,9 +232,16 @@ def _replace_table_section(content: str, heading: str, table_markdown: str): break if start_index is None: raise ValueError(f"Failed to find section {heading}") + heading_level = len(heading) - len(heading.lstrip("#")) end_index = len(lines) for index in range(start_index + 1, len(lines)): - if lines[index].startswith("### "): + line = lines[index] + if line.startswith("#"): + line_level = len(line) - len(line.lstrip("#")) + if line_level <= heading_level: + end_index = index + break + if line.startswith("### ") and heading_level <= 3: end_index = index break updated_lines = lines[: start_index + 1] + ["", table_markdown, ""] @@ -240,6 +250,13 @@ def _replace_table_section(content: str, heading: str, table_markdown: str): return "\n".join(updated_lines).rstrip() + "\n" +def _format_markdown_with_prettier(path: Path): + prettier = shutil.which("prettier") + if prettier is None: + return + subprocess.run([prettier, "--write", str(path)], check=True) + + def _parse_chart_spec(source_path: str): name = Path(source_path).name benchmark_match = re.match( @@ -303,7 +320,7 @@ def _build_single_plot_frame(spec, benchmark_data, zero_copy_data): f"{spec['benchmark']} {spec['objectType']} from {spec['bufferType']}" ) xlabel = "enable_references" - width = 0.7 * bar_width_scale + width = benchmark_group_width else: sub_df = zero_copy_data[ (zero_copy_data["Benchmark"] == spec["benchmark"]) @@ -320,10 +337,66 @@ def _build_single_plot_frame(spec, benchmark_data, zero_copy_data): else: title = f"{spec['benchmark']} {spec['dataType']} from {spec['bufferType']}" xlabel = "array_size" - width = 0.8 * bar_width_scale + width = zero_copy_group_width return final_df, title, xlabel, width +def _lib_columns(frame: pd.DataFrame): + if isinstance(frame.columns, pd.MultiIndex): + return frame.columns.get_level_values("Lib").tolist() + return frame.columns.tolist() + + +def _plot_colors(libs): + colors = color_map.copy() + if "Fory" in libs and "ForyMetaShared" in libs: + colors["Fory"] = color_map["ForyMetaShared"] + colors["ForyMetaShared"] = color_map["Fory"] + return [colors[lib] for lib in libs] + + +def _bar_label(value): + if pd.isna(value): + return "" + return f"{float(value):g}" + + +def _plot_grouped_bar_frame(axis, frame: pd.DataFrame, title: str, width: float): + libs = _lib_columns(frame) + values = frame.to_numpy(dtype=float) + group_count = len(frame.index) + bar_count = len(libs) + x = np.arange(group_count) + gap = intra_bar_gap if bar_count > 1 else 0.0 + bar_width = (width - gap * (bar_count - 1)) / bar_count + if bar_width <= 0: + raise ValueError(f"Invalid grouped bar width for {bar_count} bars") + total_width = bar_width * bar_count + gap * (bar_count - 1) + start = -total_width / 2 + bar_width / 2 + + for index, lib in enumerate(libs): + positions = x + start + index * (bar_width + gap) + container = axis.bar( + positions, + values[:, index], + width=bar_width, + label=lib, + color=_plot_colors(libs)[index], + ) + axis.bar_label( + container, + labels=[_bar_label(value) for value in values[:, index]], + fontsize=8, + padding=1, + ) + + axis.set_title(title) + axis.set_xticks(x) + axis.set_xticklabels([str(value) for value in frame.index]) + axis.margins(x=0.04, y=0.08) + return libs + + def _plot_combined_group(group, benchmark_data, zero_copy_data, output_path: Path): fig, axes = plt.subplots(1, 4, figsize=(22, 6), gridspec_kw={"wspace": 0.15}) for axis_index, source_path in enumerate(group["sources"]): @@ -332,11 +405,7 @@ def _plot_combined_group(group, benchmark_data, zero_copy_data, output_path: Pat final_df, title, xlabel, width = _build_single_plot_frame( spec, benchmark_data, zero_copy_data ) - libs = final_df.columns.to_frame()["Lib"] - color = [color_map[lib] for lib in libs] - final_df.plot.bar(title=title, color=color, ax=axis, width=width) - for container in axis.containers: - axis.bar_label(container, fontsize=8) + libs = _plot_grouped_bar_frame(axis, final_df, title, width) axis.set_xlabel(xlabel) if axis_index == 0: axis.set_ylabel(f"Tps/{scaler}") @@ -416,12 +485,13 @@ def _update_java_benchmark_readme(data_dir: Path, readme_path: Path): readme_content = readme_path.read_text() readme_content = _replace_table_section( - readme_content, "### Java Serialization", _to_markdown(benchmark_table) + readme_content, "#### Java Serialization", _to_markdown(benchmark_table) ) readme_content = _replace_table_section( - readme_content, "### Java Zero-copy", _to_markdown(zero_copy_table) + readme_content, "#### Java Zero-copy", _to_markdown(zero_copy_table) ) readme_path.write_text(readme_content) + _format_markdown_with_prettier(readme_path) def process_data(filepath: str): @@ -460,8 +530,8 @@ def process_df(bench_df): color_map = { - "Fory": "#FF6f01", # Orange - "ForyMetaShared": "#FFB266", # Shallow orange + "Fory": "#FF6f01", # Primary orange + "ForyMetaShared": "#FF8A24", # Secondary orange # "Kryo": (1, 0.5, 1), # "Kryo": (1, 0.84, 0.25), "Kryo": "#55BCC2", @@ -476,7 +546,9 @@ def process_df(bench_df): scaler = 10000 -bar_width_scale = 1.2 +benchmark_group_width = 0.9 +zero_copy_group_width = 0.9 +intra_bar_gap = 0.012 def format_scaler(x): @@ -495,9 +567,14 @@ def add_upper_right_legend(ax, labels): loc="upper right", bbox_to_anchor=(0.98, 0.98), borderaxespad=0.2, - prop={"size": 10}, frameon=True, - framealpha=0.9, + framealpha=0.95, + edgecolor="#D6DAE0", + borderpad=0.3, + labelspacing=0.3, + handlelength=1.4, + handletextpad=0.45, + prop={"size": 8}, ) diff --git a/benchmarks/java/benchmark_report.py b/benchmarks/java/benchmark_report.py index 774a449703..7bf472b00c 100755 --- a/benchmarks/java/benchmark_report.py +++ b/benchmarks/java/benchmark_report.py @@ -25,14 +25,31 @@ import os import re import shutil +import sys from collections import defaultdict from pathlib import Path from typing import Any import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + add_compact_legend, + apply_benchmark_style, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + SERIALIZER_ORDER = ["fory", "protobuf", "flatbuffer"] COLORS = { "fory": "#FF6f01", @@ -136,13 +153,7 @@ def collect_results(payload: Any) -> dict: def format_tps(value: float, _position) -> str: - if value >= 1e9: - return f"{value / 1e9:.2f}G" - if value >= 1e6: - return f"{value / 1e6:.2f}M" - if value >= 1e3: - return f"{value / 1e3:.2f}K" - return f"{value:.0f}" + return format_throughput_tick(value, _position) def plot_group(ax, results: dict, datatype: str) -> None: @@ -164,57 +175,144 @@ def plot_group(ax, results: dict, datatype: str) -> None: ax.axis("off") return - x = np.arange(len(OPERATIONS)) - width = 0.8 / len(serializers) + x = GROUP_X for index, serializer in enumerate(serializers): values = [ results.get(datatype, {}).get(operation, {}).get(serializer, 0.0) for operation in OPERATIONS ] - offset = (index - (len(serializers) - 1) / 2) * width + offset = serializer_offset(index, len(serializers)) ax.bar( x + offset, values, - width=width, + width=GROUP_BAR_WIDTH, label=serializer, color=COLORS.get(serializer, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(datatype_title(datatype), fontweight="normal") - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_value = max( + results.get(datatype, {}).get(operation, {}).get(serializer, 0.0) + for operation in OPERATIONS + for serializer in serializers + ) + ax.set_ylim(0, max_value * 1.12) + ax.set_title(datatype_title(datatype), fontweight="normal", pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def render_plot(results: dict, output_dir: str) -> str: - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) fig.suptitle( - "Java Xlang Serialization Throughput", fontsize=14, fontweight="normal" + "Java Xlang Serialization Throughput", + fontsize=15, + fontweight="normal", + y=0.955, ) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): plot_group(ax, results, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) - fig.tight_layout() + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) output_path = os.path.join(output_dir, "throughput.png") - plt.savefig(output_path, dpi=150) + save_benchmark_figure(fig, output_path) plt.close(fig) return output_path +def format_table_value(value: float) -> str: + return f"{value:,.0f}" if value > 0 else "N/A" + + +def winner_cell(values: dict) -> str: + positive = {name: value for name, value in values.items() if value > 0} + if not positive: + return "N/A" + winner = max(positive, key=positive.get) + return winner.capitalize() + + +def build_xlang_section(results: dict, image_name: str) -> str: + lines = [ + "## Xlang Benchmark\n\n", + "Run from `benchmarks/java/run.sh`. Raw JMH JSON stays under the ignored local " + "`benchmarks/java/reports/` directory; `throughput.png` and this xlang " + "section are synced into `docs/benchmarks/java/`.\n\n", + "```bash\n", + "cd benchmarks/java\n", + "./run.sh\n", + "```\n\n", + "JMH parameters: `-f 1 -wi 3 -i 3 -t 1 -w 3s -r 3s -bm thrpt -tu s`. " + "Higher throughput is better.\n\n", + f"![Java Xlang Serialization Throughput]({image_name})\n\n", + "| Data type | Operation | Fory ops/sec | Protobuf ops/sec | Flatbuffer ops/sec | Fastest |\n", + "|-----------|-----------|--------------|------------------|--------------------|---------|\n", + ] + + for datatype in DATATYPE_ORDER: + for operation in OPERATIONS: + values = { + serializer: results.get(datatype, {}) + .get(operation, {}) + .get(serializer, 0.0) + for serializer in SERIALIZER_ORDER + } + lines.append( + f"| {datatype_title(datatype)} | {operation.capitalize()} | " + + " | ".join( + format_table_value(values[serializer]) + for serializer in SERIALIZER_ORDER + ) + + f" | {winner_cell(values)} |\n" + ) + return "".join(lines) + + +def write_local_readme(output_dir: Path, section: str) -> Path: + report_path = output_dir / "README.md" + report_path.write_text( + "# Java Xlang Benchmark Report\n\n" + section, encoding="utf-8" + ) + run_prettier(report_path) + return report_path + + +def update_docs_readme(docs_output_dir: Path, section: str) -> Path: + docs_readme = docs_output_dir / "README.md" + if docs_readme.exists(): + content = docs_readme.read_text(encoding="utf-8").rstrip() + marker = "\n## Xlang Benchmark\n" + if marker in content: + prefix = content.split(marker, 1)[0].rstrip() + content = prefix + "\n\n" + section + else: + content = content + "\n\n" + section + else: + content = "# Java Benchmarks\n\n" + section + docs_readme.write_text(content.rstrip() + "\n", encoding="utf-8") + run_prettier(docs_readme) + return docs_readme + + +def run_prettier(path: Path) -> None: + format_markdown_with_prettier(path) + + def main() -> None: args = parse_args() output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) results = collect_results(load_json(args.json_file)) output_path = render_plot(results, str(output_dir)) + section = build_xlang_section(results, os.path.basename(output_path)) + report_path = write_local_readme(output_dir, section) + print(f"Generated {report_path}") print(f"Generated {output_path}") if args.docs_output_dir is not None: docs_output_dir = Path(args.docs_output_dir) @@ -222,6 +320,8 @@ def main() -> None: docs_output_path = docs_output_dir / "throughput.png" shutil.copy2(output_path, docs_output_path) print(f"Copied {docs_output_path}") + docs_readme = update_docs_readme(docs_output_dir, section) + print(f"Updated {docs_readme}") if __name__ == "__main__": diff --git a/benchmarks/java/run.sh b/benchmarks/java/run.sh index ba8d8509e9..ea22143ba4 100755 --- a/benchmarks/java/run.sh +++ b/benchmarks/java/run.sh @@ -24,7 +24,7 @@ DATA_FILTER="" SERIALIZER_FILTER="" DURATION_SECONDS="3" REPORT_DIR="${SCRIPT_DIR}/reports" -DOCS_OUTPUT_DIR="" +DOCS_OUTPUT_DIR="${SCRIPT_DIR}/../../docs/benchmarks/java" SKIP_BUILD="false" GENERATE_REPORT="true" @@ -40,7 +40,8 @@ Options: --duration JMH warmup and measurement duration per iteration --reports-dir Local directory for benchmark_results.json and throughput.png (default: reports) - --output-dir Optional docs directory for copied throughput.png only + --output-dir Docs directory for copied throughput.png and README xlang section + --no-copy-docs Skip copying xlang report artifacts into docs/benchmarks/java --skip-build Reuse an existing target/benchmarks.jar --no-report Do not generate throughput.png --help Show this help @@ -141,6 +142,10 @@ while [[ $# -gt 0 ]]; do DOCS_OUTPUT_DIR="${2:-}" shift 2 ;; + --no-copy-docs) + DOCS_OUTPUT_DIR="" + shift + ;; --reports-dir) REPORT_DIR="${2:-}" shift 2 diff --git a/benchmarks/javascript/README.md b/benchmarks/javascript/README.md index 3ec7c88b6d..ac64bd36ea 100644 --- a/benchmarks/javascript/README.md +++ b/benchmarks/javascript/README.md @@ -48,7 +48,7 @@ Examples: Running the pipeline writes: - raw benchmark JSON to `benchmarks/javascript/benchmark_results.json` -- plots to `docs/benchmarks/javascript/*.png` +- throughput plot to `docs/benchmarks/javascript/throughput.png` - Markdown report to `docs/benchmarks/javascript/README.md` ## Notes diff --git a/benchmarks/javascript/benchmark_report.py b/benchmarks/javascript/benchmark_report.py index 1b117dba9c..a79b1156f2 100644 --- a/benchmarks/javascript/benchmark_report.py +++ b/benchmarks/javascript/benchmark_report.py @@ -19,15 +19,31 @@ import json import os import platform -import shutil -import subprocess +import sys from collections import defaultdict from datetime import datetime +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + add_compact_legend, + apply_benchmark_style, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + try: import psutil @@ -162,72 +178,13 @@ def format_datatype_table_label(datatype): system_info["V8"] = context["v8_version"] -def format_tps_label(tps): - if tps >= 1e9: - return f"{tps / 1e9:.2f}G" - if tps >= 1e6: - return f"{tps / 1e6:.2f}M" - if tps >= 1e3: - return f"{tps / 1e3:.2f}K" - return f"{tps:.0f}" - - def format_tps_tick(tps, _position): - return format_tps_label(tps) - - -def plot_datatype(ax, datatype, operation): - if datatype not in data or operation not in data[datatype]: - ax.set_title(f"{datatype} {operation} - No Data") - ax.axis("off") - return - - libs = [lib for lib in SERIALIZER_ORDER if lib in data[datatype][operation]] - if not libs: - ax.set_title(f"{datatype} {operation} - No Data") - ax.axis("off") - return + return format_throughput_tick(tps, _position) - times = [data[datatype][operation].get(lib, 0) for lib in libs] - throughput = [1e9 / value if value > 0 else 0 for value in times] - colors = [COLORS[lib] for lib in libs] - - x = np.arange(len(libs)) - bars = ax.bar(x, throughput, color=colors, width=0.6) - ax.set_title(f"{operation.capitalize()} Throughput (higher is better)") - ax.set_xticks(x) - ax.set_xticklabels([SERIALIZER_LABELS[lib] for lib in libs]) - ax.set_ylabel("Throughput (ops/sec)") - ax.grid(True, axis="y", linestyle="--", alpha=0.5) - ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0)) - - for bar, tps in zip(bars, throughput): - ax.annotate( - format_tps_label(tps), - xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) - -plot_images = [] datatypes = [datatype for datatype in DATATYPE_ORDER if datatype in data] operations = ["serialize", "deserialize"] -for datatype in datatypes: - fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - for i, operation in enumerate(operations): - plot_datatype(axes[i], datatype, operation) - fig.suptitle(f"{format_datatype_table_label(datatype)} Throughput", fontsize=14) - fig.tight_layout(rect=[0, 0, 1, 0.95]) - plot_path = os.path.join(output_dir, f"{datatype}.png") - plt.savefig(plot_path, dpi=150) - plot_images.append((datatype, plot_path)) - plt.close() - def plot_throughput_grid_subplot(ax, datatype): if datatype not in data: @@ -249,41 +206,46 @@ def plot_throughput_grid_subplot(ax, datatype): return operations = ["serialize", "deserialize"] - x = np.arange(len(operations)) - width = 0.8 / len(available_libs) + x = GROUP_X for idx, lib in enumerate(available_libs): times = [data[datatype][operation].get(lib, 0) for operation in operations] throughput = [1e9 / value if value > 0 else 0 for value in times] - offset = (idx - (len(available_libs) - 1) / 2) * width + offset = serializer_offset(idx, len(available_libs)) ax.bar( x + offset, throughput, - width, + GROUP_BAR_WIDTH, label=SERIALIZER_LABELS[lib], color=COLORS[lib], + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(format_datatype_table_label(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_tps = max( + 1e9 / data[datatype][operation][lib] + for operation in operations + for lib in available_libs + if data[datatype][operation].get(lib, 0) > 0 + ) + ax.set_ylim(0, max_tps * 1.12) + ax.set_title(format_datatype_table_label(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) -fig, axes = plt.subplots(2, 3, figsize=(18, 10)) +fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): plot_throughput_grid_subplot(ax, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) -fig.suptitle("JavaScript Serialization Throughput", fontsize=14) -fig.tight_layout() + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) +fig.suptitle( + "JavaScript Serialization Throughput", fontsize=15, fontweight="normal", y=0.955 +) +fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) combined_plot_path = os.path.join(output_dir, "throughput.png") -plt.savefig(combined_plot_path, dpi=150) -plot_images.append(("throughput", combined_plot_path)) +save_benchmark_figure(fig, combined_plot_path) plt.close() md_report = [ @@ -300,6 +262,9 @@ def plot_throughput_grid_subplot(ax, datatype): "prebuilt protobuf-shaped value, and JSON receives the benchmark JavaScript " "object. Protobuf timings do not include `toProto`, `fromProto`, " "`protobufjs.create`, or `toObject` conversion work.\n\n", + "## Benchmark Plot\n\n", + "The plot shows throughput (ops/sec); higher is better.\n\n", + f"![Throughput]({args.plot_prefix}throughput.png)\n\n", "## Hardware & OS Info\n\n", "| Key | Value |\n", "|-----|-------|\n", @@ -308,21 +273,6 @@ def plot_throughput_grid_subplot(ax, datatype): for key, value in system_info.items(): md_report.append(f"| {key} | {value} |\n") -md_report.append("\n## Benchmark Plots\n") -md_report.append("\nAll class-level plots below show throughput (ops/sec).\n") -for datatype, image in sorted( - plot_images, key=lambda item: (0 if item[0] == "throughput" else 1, item[0]) -): - image_name = os.path.basename(image) - image_path = args.plot_prefix + image_name - title = ( - "Throughput" - if datatype == "throughput" - else format_datatype_table_label(datatype) - ) - md_report.append(f"\n### {title}\n\n") - md_report.append(f"![{title}]({image_path})\n") - md_report.append("\n## Benchmark Results\n\n") md_report.append("### Timing Results (nanoseconds)\n\n") md_report.append( @@ -400,9 +350,7 @@ def plot_throughput_grid_subplot(ax, datatype): with open(report_path, "w", encoding="utf-8") as handle: handle.writelines(md_report) -prettier = shutil.which("prettier") -if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) +format_markdown_with_prettier(report_path) print(f"Plots saved in: {output_dir}") print(f"Markdown report generated at: {report_path}") diff --git a/benchmarks/plot_style.py b/benchmarks/plot_style.py new file mode 100644 index 0000000000..bfabe638dc --- /dev/null +++ b/benchmarks/plot_style.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# 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. + +"""Shared plotting style for language benchmark reports.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path + +import numpy as np +from matplotlib.ticker import MaxNLocator + +PLOT_RC_PARAMS = { + "figure.facecolor": "white", + "axes.facecolor": "white", + "axes.titleweight": "normal", + "axes.labelcolor": "#222222", + "xtick.color": "#222222", + "ytick.color": "#222222", + "font.size": 10, + "axes.titlesize": 11, + "axes.labelsize": 10, + "xtick.labelsize": 9, + "ytick.labelsize": 9, + "legend.fontsize": 8, +} + +GRID_COLOR = "#D9DEE7" +SPINE_COLOR = "#8A939E" +BAR_EDGE_COLOR = "white" +GROUP_X = np.array([0.0, 0.68]) +GROUP_BAR_WIDTH = 0.15 +GROUP_OFFSET_STEP = 0.165 +GROUP_X_LIMITS = (-0.39, 1.07) + + +def apply_benchmark_style(plt) -> None: + plt.rcParams.update(PLOT_RC_PARAMS) + + +def format_throughput_label(value: float) -> str: + def format_scaled(scaled: float, suffix: str) -> str: + return f"{scaled:.2f}".rstrip("0").rstrip(".") + suffix + + if value >= 1e9: + return format_scaled(value / 1e9, "G") + if value >= 1e6: + return format_scaled(value / 1e6, "M") + if value >= 1e3: + return format_scaled(value / 1e3, "K") + return f"{value:.0f}" + + +def format_throughput_tick(value: float, _position) -> str: + return format_throughput_label(value) + + +def style_throughput_axis(ax) -> None: + ax.set_axisbelow(True) + ax.grid(True, axis="y", color=GRID_COLOR, linestyle="-", linewidth=0.7) + ax.grid(False, axis="x") + ax.yaxis.set_major_locator(MaxNLocator(nbins=5, min_n_ticks=3)) + ax.tick_params(axis="both", width=0.8, length=3) + for spine in ax.spines.values(): + spine.set_color(SPINE_COLOR) + spine.set_linewidth(0.8) + + +def serializer_offset(index: int, count: int) -> float: + return (index - (count - 1) / 2) * GROUP_OFFSET_STEP + + +def set_grouped_operation_axis(ax, labels=("Serialize", "Deserialize")) -> None: + ax.set_xticks(GROUP_X) + ax.set_xticklabels(list(labels)) + ax.set_xlim(*GROUP_X_LIMITS) + + +def add_compact_legend(ax) -> None: + ax.legend( + loc="upper right", + frameon=True, + framealpha=0.95, + edgecolor="#D6DAE0", + borderpad=0.3, + labelspacing=0.3, + handlelength=1.4, + handletextpad=0.45, + ) + + +def save_benchmark_figure(fig, path: str | Path) -> None: + fig.savefig(path, dpi=170, bbox_inches="tight", pad_inches=0.12) + + +def format_markdown_with_prettier(*paths: str | Path) -> None: + prettier = shutil.which("prettier") + if prettier is None or not paths: + return + subprocess.run( + [ + prettier, + "--write", + "--ignore-path", + os.devnull, + *(str(path) for path in paths), + ], + check=True, + ) diff --git a/benchmarks/python/benchmark_report.py b/benchmarks/python/benchmark_report.py index 50116e16b5..66f0a845da 100755 --- a/benchmarks/python/benchmark_report.py +++ b/benchmarks/python/benchmark_report.py @@ -22,19 +22,31 @@ import argparse import json -import os import platform -import shutil -import subprocess +import sys from collections import defaultdict from datetime import datetime from pathlib import Path from typing import Dict import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + PLOT_RC_PARAMS, + add_compact_legend, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + try: import psutil @@ -131,18 +143,8 @@ def format_datatype_table_label(datatype: str) -> str: return mapping.get(datatype, datatype) -def format_tps_label(tps: float) -> str: - if tps >= 1e9: - return f"{tps / 1e9:.2f}G" - if tps >= 1e6: - return f"{tps / 1e6:.2f}M" - if tps >= 1e3: - return f"{tps / 1e3:.2f}K" - return f"{tps:.0f}" - - def format_tps_tick(tps: float, _position) -> str: - return format_tps_label(tps) + return format_throughput_tick(tps, _position) def build_benchmark_matrix(benchmarks): @@ -155,52 +157,6 @@ def build_benchmark_matrix(benchmarks): return data -def plot_datatype(ax, data, datatype: str, operation: str): - if datatype not in data or operation not in data[datatype]: - ax.set_title(f"{format_datatype_table_label(datatype)} {operation}: no data") - ax.axis("off") - return - - libs = [ - lib - for lib in SERIALIZER_ORDER - if data[datatype][operation].get(lib, 0) and data[datatype][operation][lib] > 0 - ] - if not libs: - ax.set_title(f"{format_datatype_table_label(datatype)} {operation}: no data") - ax.axis("off") - return - - times = [data[datatype][operation][lib] for lib in libs] - throughput = [1e9 / t if t > 0 else 0 for t in times] - - x = np.arange(len(libs)) - bars = ax.bar( - x, - throughput, - color=[COLORS.get(lib, "#999999") for lib in libs], - width=0.6, - ) - - ax.set_xticks(x) - ax.set_xticklabels([SERIALIZER_LABELS.get(lib, lib) for lib in libs]) - ax.set_ylabel("Throughput (ops/sec)") - ax.set_title(f"{operation.capitalize()} Throughput (higher is better)") - ax.grid(True, axis="y", linestyle="--", alpha=0.45) - ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0)) - - for bar, val in zip(bars, throughput): - ax.annotate( - format_tps_label(val), - xy=(bar.get_x() + bar.get_width() / 2, bar.get_height()), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) - - def plot_throughput_grid_subplot(ax, data, datatype: str): if datatype not in data: ax.set_title(f"{format_datatype_table_label(datatype)}\nNo Data") @@ -221,70 +177,61 @@ def plot_throughput_grid_subplot(ax, data, datatype: str): return operations = ["serialize", "deserialize"] - x = np.arange(len(operations)) - width = 0.8 / len(available_libs) + x = GROUP_X for idx, lib in enumerate(available_libs): times = [ data.get(datatype, {}).get(operation, {}).get(lib, 0) for operation in operations ] tps = [1e9 / val if val > 0 else 0 for val in times] - offset = (idx - (len(available_libs) - 1) / 2) * width + offset = serializer_offset(idx, len(available_libs)) ax.bar( x + offset, tps, - width, + GROUP_BAR_WIDTH, label=SERIALIZER_LABELS.get(lib, lib), color=COLORS.get(lib, "#999999"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(format_datatype_table_label(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.45) + max_tps = max( + 1e9 / data[datatype][operation][lib] + for operation in operations + for lib in available_libs + if data.get(datatype, {}).get(operation, {}).get(lib, 0) > 0 + ) + ax.set_ylim(0, max_tps * 1.12) + ax.set_title(format_datatype_table_label(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def generate_plots(data, output_dir: Path): - plot_images = [] - operations = ["serialize", "deserialize"] - - datatypes = [dt for dt in DATATYPE_ORDER if dt in data] - for datatype in datatypes: - fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - for idx, operation in enumerate(operations): - plot_datatype(axes[idx], data, datatype, operation) - fig.suptitle(f"{format_datatype_table_label(datatype)} Throughput", fontsize=14) - fig.tight_layout(rect=[0, 0, 1, 0.95]) - - path = output_dir / f"{datatype}.png" - plt.savefig(path, dpi=150) + with plt.rc_context(PLOT_RC_PARAMS): + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) + for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): + plot_throughput_grid_subplot(ax, data, datatype) + if index % 3 == 0: + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) + + fig.suptitle( + "Python Serialization Throughput", + fontsize=15, + fontweight="normal", + y=0.955, + ) + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) + throughput_path = output_dir / "throughput.png" + save_benchmark_figure(fig, throughput_path) plt.close() - plot_images.append((datatype, path)) - - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) - for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): - plot_throughput_grid_subplot(ax, data, datatype) - if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) - - fig.suptitle("Python Serialization Throughput", fontsize=14) - fig.tight_layout() - throughput_path = output_dir / "throughput.png" - plt.savefig(throughput_path, dpi=150) - plt.close() - plot_images.append(("throughput", throughput_path)) - - return plot_images - - -def generate_markdown_report( - raw, data, sizes, plot_images, output_dir: Path, plot_prefix: str -): + + return throughput_path + + +def generate_markdown_report(raw, data, sizes, output_dir: Path, plot_prefix: str): context = raw.get("context", {}) system_info = get_system_info() @@ -304,6 +251,9 @@ def generate_markdown_report( "cd benchmarks/python\n", "./run.sh\n", "```\n\n", + "## Benchmark Plot\n\n", + "The plot shows throughput (ops/sec); higher is better.\n\n", + f"![Throughput]({plot_prefix}throughput.png)\n\n", "## Hardware & OS Info\n\n", "| Key | Value |\n", "|-----|-------|\n", @@ -319,23 +269,6 @@ def generate_markdown_report( if key in context: md.append(f"| {key} | {context[key]} |\n") - md.append("\n## Benchmark Plots\n") - md.append("\nAll plots show throughput (ops/sec); higher is better.\n") - - plot_images_sorted = sorted( - plot_images, key=lambda item: (0 if item[0] == "throughput" else 1, item[0]) - ) - for datatype, image_path in plot_images_sorted: - image_name = os.path.basename(image_path) - image_ref = f"{plot_prefix}{image_name}" - plot_title = ( - "Throughput" - if datatype == "throughput" - else format_datatype_table_label(datatype) - ) - md.append(f"\n### {plot_title}\n\n") - md.append(f"![{plot_title}]({image_ref})\n") - md.append("\n## Benchmark Results\n\n") md.append("### Timing Results (nanoseconds)\n\n") timing_headers = [ @@ -425,9 +358,7 @@ def generate_markdown_report( report_path = output_dir / "README.md" report_path.write_text("".join(md), encoding="utf-8") - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", str(report_path)], check=True) + format_markdown_with_prettier(report_path) return report_path @@ -444,13 +375,12 @@ def main() -> int: sizes = raw.get("sizes", {}) data = build_benchmark_matrix(benchmarks) - plot_images = generate_plots(data, output_dir) + generate_plots(data, output_dir) report_path = generate_markdown_report( raw, data, sizes, - plot_images, output_dir, args.plot_prefix, ) diff --git a/benchmarks/python/run.sh b/benchmarks/python/run.sh index ff28f81ce6..ef0bb1b482 100755 --- a/benchmarks/python/run.sh +++ b/benchmarks/python/run.sh @@ -183,8 +183,8 @@ echo "Generating report..." if [[ "$COPY_DOCS" == true ]]; then mkdir -p "$DOCS_DIR" cp "$REPORT_DIR/README.md" "$DOCS_DIR/README.md" - cp "$REPORT_DIR"/*.png "$DOCS_DIR/" 2>/dev/null || true - echo "Copied report and plots to: $DOCS_DIR" + cp "$REPORT_DIR/throughput.png" "$DOCS_DIR/throughput.png" + echo "Copied report and throughput plot to: $DOCS_DIR" fi echo "" diff --git a/benchmarks/rust/README.md b/benchmarks/rust/README.md index c946c9b344..ca2ef95f90 100644 --- a/benchmarks/rust/README.md +++ b/benchmarks/rust/README.md @@ -84,4 +84,3 @@ The report generator writes: - `results/README.md` - `results/throughput.png` -- per-datatype plots such as `results/struct.png` and `results/mediacontent.png` diff --git a/benchmarks/rust/benchmark_report.py b/benchmarks/rust/benchmark_report.py index d3302b7d62..1400f30f41 100644 --- a/benchmarks/rust/benchmark_report.py +++ b/benchmarks/rust/benchmark_report.py @@ -20,15 +20,31 @@ import os import platform import re -import shutil -import subprocess +import sys from collections import defaultdict from datetime import datetime +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + add_compact_legend, + apply_benchmark_style, + format_markdown_with_prettier, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + try: import psutil @@ -107,20 +123,6 @@ def datatype_title(datatype): return datatype.capitalize() -def datatype_plot_label(datatype): - if datatype == "struct": - return "NumericStruct" - if datatype == "structlist": - return "NumericStruct\nList" - if datatype == "mediacontent": - return "MediaContent" - if datatype == "mediacontentlist": - return "MediaContent\nList" - if datatype.endswith("list"): - return f"{datatype[:-4].capitalize()}\nList" - return datatype.capitalize() - - def get_system_info(log_file): info = { "OS": f"{platform.system()} {platform.release()}", @@ -192,57 +194,8 @@ def load_serialized_sizes(size_file): return sizes -def format_tps_label(tps): - if tps >= 1e9: - return f"{tps / 1e9:.2f}G" - if tps >= 1e6: - return f"{tps / 1e6:.2f}M" - if tps >= 1e3: - return f"{tps / 1e3:.2f}K" - return f"{tps:.0f}" - - def format_tps_tick(tps, _position): - return format_tps_label(tps) - - -def plot_datatype(ax, results, datatype, operation): - if datatype not in results or operation not in results[datatype]: - ax.set_title(f"{datatype} {operation} - No Data") - ax.axis("off") - return - - libs = [ - serializer - for serializer in SERIALIZER_ORDER - if results[datatype][operation].get(serializer, 0) > 0 - ] - throughput = [1e9 / results[datatype][operation][serializer] for serializer in libs] - x = np.arange(len(libs)) - bars = ax.bar( - x, - throughput, - color=[COLORS.get(serializer, "#888888") for serializer in libs], - width=0.6, - ) - - ax.set_title(f"{operation.capitalize()} Throughput (higher is better)") - ax.set_xticks(x) - ax.set_xticklabels([SERIALIZER_LABELS[serializer] for serializer in libs]) - ax.set_ylabel("Throughput (ops/sec)") - ax.grid(True, axis="y", linestyle="--", alpha=0.5) - ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0)) - - for bar, value in zip(bars, throughput): - ax.annotate( - format_tps_label(value), - xy=(bar.get_x() + bar.get_width() / 2, value), - xytext=(0, 3), - textcoords="offset points", - ha="center", - va="bottom", - fontsize=9, - ) + return format_throughput_tick(tps, _position) def plot_throughput_grid_subplot(ax, results, datatype): @@ -264,66 +217,57 @@ def plot_throughput_grid_subplot(ax, results, datatype): ax.axis("off") return - x = np.arange(len(OPERATIONS)) - width = 0.8 / len(available) + x = GROUP_X for index, serializer in enumerate(available): throughput = [] for operation in OPERATIONS: time_ns = results[datatype][operation].get(serializer, 0) throughput.append(1e9 / time_ns if time_ns > 0 else 0) - offset = (index - (len(available) - 1) / 2) * width + offset = serializer_offset(index, len(available)) ax.bar( x + offset, throughput, - width, + GROUP_BAR_WIDTH, label=SERIALIZER_LABELS[serializer], color=COLORS.get(serializer, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(datatype_title(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_tps = max( + 1e9 / results[datatype][operation][serializer] + for operation in OPERATIONS + for serializer in available + if results[datatype][operation].get(serializer, 0) > 0 + ) + ax.set_ylim(0, max_tps * 1.12) + ax.set_title(datatype_title(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def generate_plots(results, output_dir): os.makedirs(output_dir, exist_ok=True) - plot_images = [] - for datatype in DATATYPE_ORDER: - if datatype not in results: - continue - fig, axes = plt.subplots(1, 2, figsize=(12, 5)) - for index, operation in enumerate(OPERATIONS): - plot_datatype(axes[index], results, datatype, operation) - fig.suptitle(f"{datatype_title(datatype)} Throughput", fontsize=14) - fig.tight_layout(rect=[0, 0, 1, 0.95]) - plot_path = os.path.join(output_dir, f"{datatype}.png") - plt.savefig(plot_path, dpi=150) - plt.close(fig) - plot_images.append((datatype, plot_path)) - - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): plot_throughput_grid_subplot(ax, results, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) - fig.suptitle("Rust Serialization Throughput", fontsize=14) - fig.tight_layout() + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) + fig.suptitle( + "Rust Serialization Throughput", fontsize=15, fontweight="normal", y=0.955 + ) + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) throughput_path = os.path.join(output_dir, "throughput.png") - plt.savefig(throughput_path, dpi=150) + save_benchmark_figure(fig, throughput_path) plt.close(fig) - plot_images.append(("throughput", throughput_path)) - return plot_images + return throughput_path -def write_report(system_info, results, sizes, plot_images, output_dir, plot_prefix): +def write_report(system_info, results, sizes, output_dir, plot_prefix): report = [ "# Rust Benchmark Performance Report\n\n", f"_Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}_\n\n", @@ -334,6 +278,9 @@ def write_report(system_info, results, sizes, plot_images, output_dir, plot_pref "cargo run --release --bin fory_profiler -- --print-all-serialized-sizes | tee results/serialized_sizes.txt\n", "python benchmark_report.py --log-file results/cargo_bench.log --size-file results/serialized_sizes.txt --output-dir results\n", "```\n\n", + "## Benchmark Plot\n\n", + "The plot shows throughput (ops/sec); higher is better.\n\n", + f"![Throughput]({plot_prefix}throughput.png)\n\n", "## Hardware & OS Info\n\n", "| Key | Value |\n", "|-----|-------|\n", @@ -342,17 +289,6 @@ def write_report(system_info, results, sizes, plot_images, output_dir, plot_pref for key, value in system_info.items(): report.append(f"| {key} | {value} |\n") - report.append("\n## Benchmark Plots\n") - report.append("\nAll class-level plots below show throughput (ops/sec).\n") - - sorted_plots = sorted( - plot_images, key=lambda item: (0 if item[0] == "throughput" else 1, item[0]) - ) - for datatype, image_path in sorted_plots: - plot_title = datatype_title(datatype) - report.append(f"\n### {plot_title}\n\n") - report.append(f"![{plot_title}]({plot_prefix}{os.path.basename(image_path)})\n") - report.append("\n## Benchmark Results\n\n") report.append("### Timing Results (nanoseconds)\n\n") report.append( @@ -429,9 +365,7 @@ def write_report(system_info, results, sizes, plot_images, output_dir, plot_pref with open(report_path, "w", encoding="utf-8") as file: file.writelines(report) - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) + format_markdown_with_prettier(report_path) return report_path @@ -441,9 +375,9 @@ def main(): results = load_benchmark_results(args.log_file) sizes = load_serialized_sizes(args.size_file) system_info = get_system_info(args.log_file) - plot_images = generate_plots(results, args.output_dir) + generate_plots(results, args.output_dir) report_path = write_report( - system_info, results, sizes, plot_images, args.output_dir, args.plot_prefix + system_info, results, sizes, args.output_dir, args.plot_prefix ) print(f"✅ Plots saved in: {args.output_dir}") print(f"📄 Markdown report generated at: {report_path}") diff --git a/benchmarks/rust/run.sh b/benchmarks/rust/run.sh index dbc79fe1b7..109d6d10eb 100755 --- a/benchmarks/rust/run.sh +++ b/benchmarks/rust/run.sh @@ -23,9 +23,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" OUTPUT_DIR="$SCRIPT_DIR/results" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/rust" LOG_FILE="$OUTPUT_DIR/cargo_bench.log" SIZE_FILE="$OUTPUT_DIR/serialized_sizes.txt" GENERATE_REPORT=true +COPY_DOCS=true DATA_FILTER="" SERIALIZER_FILTER="" CUSTOM_FILTER="" @@ -41,6 +43,7 @@ usage() { echo " --serializer Filter by serializer: fory, protobuf, msgpack" echo " --filter Custom criterion filter regex (overrides --data/--serializer)" echo " --no-report Skip report generation" + echo " --no-copy-docs Skip copying report/plots into docs/benchmarks/rust" echo " -h, --help Show this help message" echo "" echo "Examples:" @@ -70,6 +73,10 @@ while [[ $# -gt 0 ]]; do GENERATE_REPORT=false shift ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; -h|--help) usage ;; @@ -194,6 +201,12 @@ if $GENERATE_REPORT; then else echo "Warning: Python not found. Skipping report generation." fi + if [[ "$COPY_DOCS" == true && -f "$OUTPUT_DIR/README.md" ]]; then + mkdir -p "$DOCS_DIR" + cp "$OUTPUT_DIR/README.md" "$DOCS_DIR/README.md" + cp "$OUTPUT_DIR/throughput.png" "$DOCS_DIR/throughput.png" + echo "Copied report and throughput plot to: $DOCS_DIR" + fi fi echo "" @@ -205,4 +218,7 @@ echo " - cargo_bench.log" echo " - serialized_sizes.txt" if $GENERATE_REPORT; then echo " - README.md and plots" + if [[ "$COPY_DOCS" == true ]]; then + echo "Docs sync: $DOCS_DIR" + fi fi diff --git a/benchmarks/swift/Package.swift b/benchmarks/swift/Package.swift index 0341e8b0fd..2f779d4843 100644 --- a/benchmarks/swift/Package.swift +++ b/benchmarks/swift/Package.swift @@ -32,7 +32,6 @@ let package = Package( dependencies: [ .package(name: "fory", path: "../.."), .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.35.0"), - .package(url: "https://github.com/Flight-School/MessagePack.git", from: "1.2.4"), ], targets: [ .target( @@ -48,7 +47,6 @@ let package = Package( dependencies: [ .product(name: "Fory", package: "fory"), "SwiftBenchmarkProto", - .product(name: "MessagePack", package: "MessagePack"), ], path: "Sources/SwiftBenchmark" ), diff --git a/benchmarks/swift/README.md b/benchmarks/swift/README.md index ba0cb4fa52..c9c6e9b126 100644 --- a/benchmarks/swift/README.md +++ b/benchmarks/swift/README.md @@ -1,12 +1,12 @@ # Fory Swift Benchmark -This benchmark compares serialization and deserialization performance between Apache Fory, Protocol Buffers, and MessagePack in Swift. +This benchmark compares serialization and deserialization performance between Apache Fory, Protocol Buffers, and JSON in Swift. ## Serializers Compared - **Fory**: Apache Fory Swift implementation (`swift/Sources/Fory`) - **Protocol Buffers**: `apple/swift-protobuf` -- **MessagePack**: `Flight-School/MessagePack` +- **JSON**: Foundation `JSONEncoder` and `JSONDecoder` ## Benchmarked Data Types @@ -37,9 +37,10 @@ cd benchmarks/swift Supported flags: - `--data ` -- `--serializer ` +- `--serializer ` - `--duration ` - `--no-report` +- `--no-copy-docs` Examples: @@ -51,7 +52,7 @@ Examples: ./run.sh --serializer protobuf # Run a single serializer and datatype -./run.sh --data sample --serializer msgpack --duration 5 +./run.sh --data sample --serializer json --duration 5 # Skip report generation ./run.sh --no-report @@ -76,7 +77,7 @@ After running `./run.sh`, the following files are generated in `benchmarks/swift - `benchmark_results.json`: raw benchmark metrics and serialized-size comparison - `throughput.png`: throughput comparison plot -- `REPORT.md`: markdown report with hardware/runtime info and result tables +- `README.md` and `REPORT.md`: markdown report with hardware/runtime info and result tables ## Notes diff --git a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift index 5dbd02a2bd..9887003eeb 100644 --- a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift +++ b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift @@ -47,7 +47,7 @@ enum DataKind: String, CaseIterable { enum SerializerKind: String, CaseIterable { case fory case protobuf - case msgpack + case json var title: String { rawValue.capitalized diff --git a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift index 106f858651..f488a109c2 100644 --- a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift +++ b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift @@ -17,7 +17,6 @@ import Fory import Foundation -import MessagePack import SwiftProtobuf struct BenchmarkConfig { @@ -42,7 +41,7 @@ struct SizeEntry: Codable { let dataType: String let fory: Int let protobuf: Int - let msgpack: Int + let json: Int } struct BenchmarkContext: Codable { @@ -63,8 +62,8 @@ struct BenchmarkOutput: Codable { final class BenchmarkSuite { private let config: BenchmarkConfig private let fory: Fory - private let msgpackEncoder = MessagePackEncoder() - private let msgpackDecoder = MessagePackDecoder() + private let jsonEncoder = JSONEncoder() + private let jsonDecoder = JSONDecoder() init(config: BenchmarkConfig) { self.config = config @@ -156,14 +155,14 @@ final class BenchmarkSuite { let foryBytes = try fory.serialize(value) let protobufBytes = try value.toProtobuf().serializedData() - let msgpackBytes = try msgpackEncoder.encode(value) + let jsonBytes = try jsonEncoder.encode(value) sizeEntries.append( SizeEntry( dataType: dataKind.title, fory: foryBytes.count, protobuf: protobufBytes.count, - msgpack: msgpackBytes.count + json: jsonBytes.count ) ) @@ -218,27 +217,27 @@ final class BenchmarkSuite { ) } - if shouldRun(dataKind, .msgpack) { + if shouldRun(dataKind, .json) { entries.append( try runSingleCase( - serializer: .msgpack, + serializer: .json, dataKind: dataKind, operation: .serialize, - bytes: msgpackBytes.count + bytes: jsonBytes.count ) { - try self.msgpackEncoder.encode(value).count + try self.jsonEncoder.encode(value).count } ) entries.append( try runSingleCase( - serializer: .msgpack, + serializer: .json, dataKind: dataKind, operation: .deserialize, - bytes: msgpackBytes.count + bytes: jsonBytes.count ) { - let decoded: T = try self.msgpackDecoder.decode(T.self, from: msgpackBytes) + let decoded: T = try self.jsonDecoder.decode(T.self, from: jsonBytes) withExtendedLifetime(decoded) {} - return msgpackBytes.count + return jsonBytes.count } ) } diff --git a/benchmarks/swift/Sources/SwiftBenchmark/main.swift b/benchmarks/swift/Sources/SwiftBenchmark/main.swift index b61c75ed79..d1c946dc88 100644 --- a/benchmarks/swift/Sources/SwiftBenchmark/main.swift +++ b/benchmarks/swift/Sources/SwiftBenchmark/main.swift @@ -26,7 +26,7 @@ private struct CLI { Options: --data Filter benchmark by data type - --serializer + --serializer Filter benchmark by serializer --duration Minimum time to run each benchmark (default: 3) --output JSON output file (default: results/benchmark_results.json) @@ -119,7 +119,7 @@ private func runMain() throws { print("\nSerialized sizes (bytes):") for entry in output.serializedSizes { - print(" \(entry.dataType): fory=\(entry.fory), protobuf=\(entry.protobuf), msgpack=\(entry.msgpack)") + print(" \(entry.dataType): fory=\(entry.fory), protobuf=\(entry.protobuf), json=\(entry.json)") } } diff --git a/benchmarks/swift/benchmark_report.py b/benchmarks/swift/benchmark_report.py index 1e96036352..ca733fba0d 100755 --- a/benchmarks/swift/benchmark_report.py +++ b/benchmarks/swift/benchmark_report.py @@ -23,20 +23,36 @@ import argparse import json import os -import shutil -import subprocess +import sys from collections import defaultdict from pathlib import Path import matplotlib.pyplot as plt -import numpy as np from matplotlib.ticker import FuncFormatter -SERIALIZER_ORDER = ["fory", "protobuf", "msgpack"] +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +from plot_style import ( # noqa: E402 + BAR_EDGE_COLOR, + GROUP_BAR_WIDTH, + GROUP_X, + add_compact_legend, + apply_benchmark_style, + format_markdown_with_prettier, + format_throughput_label, + format_throughput_tick, + save_benchmark_figure, + serializer_offset, + set_grouped_operation_axis, + style_throughput_axis, +) + +apply_benchmark_style(plt) + +SERIALIZER_ORDER = ["fory", "protobuf", "json"] COLORS = { "fory": "#FF6f01", "protobuf": "#55BCC2", - "msgpack": "#8C6F6D", + "json": "#8C6F6D", } DATATYPE_ORDER = [ "struct", @@ -120,17 +136,11 @@ def format_tps(value: float) -> str: def format_tps_label(value: float) -> str: - if value >= 1e9: - return f"{value / 1e9:.2f}G" - if value >= 1e6: - return f"{value / 1e6:.2f}M" - if value >= 1e3: - return f"{value / 1e3:.2f}K" - return f"{value:.0f}" + return format_throughput_label(value) def format_tps_tick(value: float, _position) -> str: - return format_tps_label(value) + return format_throughput_tick(value, _position) def collect_results(payload: dict) -> dict: @@ -164,45 +174,50 @@ def plot_group(ax, results: dict, datatype: str) -> None: ax.axis("off") return - x = np.arange(len(OPERATIONS)) - width = 0.8 / len(available_serializers) + x = GROUP_X for index, serializer in enumerate(available_serializers): values = [ results.get(datatype, {}).get(operation, {}).get(serializer, 0.0) for operation in OPERATIONS ] - offset = (index - (len(available_serializers) - 1) / 2) * width + offset = serializer_offset(index, len(available_serializers)) ax.bar( x + offset, values, - width=width, + width=GROUP_BAR_WIDTH, label=serializer, color=COLORS.get(serializer, "#888888"), + edgecolor=BAR_EDGE_COLOR, + linewidth=0.8, ) - ax.set_title(datatype_title(datatype)) - ax.set_xticks(x) - ax.set_xticklabels(["Serialize", "Deserialize"]) - ax.grid(True, axis="y", linestyle="--", alpha=0.5) + max_value = max( + results.get(datatype, {}).get(operation, {}).get(serializer, 0.0) + for operation in OPERATIONS + for serializer in available_serializers + ) + ax.set_ylim(0, max_value * 1.12) + ax.set_title(datatype_title(datatype), pad=8) + set_grouped_operation_axis(ax) + style_throughput_axis(ax) ax.yaxis.set_major_formatter(FuncFormatter(format_tps_tick)) - ax.legend(loc="upper right", fontsize=8, framealpha=0.9) + add_compact_legend(ax) def render_plot(results: dict, output_dir: str) -> str: - fig, axes = plt.subplots(2, 3, figsize=(18, 10)) - fig.suptitle("Swift Serialization Throughput", fontsize=14) + fig, axes = plt.subplots(2, 3, figsize=(16.5, 9.0)) + fig.suptitle( + "Swift Serialization Throughput", fontsize=15, fontweight="normal", y=0.955 + ) for index, (ax, datatype) in enumerate(zip(axes.flat, DATATYPE_ORDER)): plot_group(ax, results, datatype) if index % 3 == 0: - ax.set_ylabel("Throughput (ops/sec)") - else: - ax.tick_params(axis="y", labelleft=False) - ax.yaxis.get_offset_text().set_visible(False) + ax.set_ylabel("Throughput (ops/sec)", labelpad=10) - fig.tight_layout() + fig.tight_layout(rect=[0.02, 0.02, 0.995, 0.965], w_pad=1.2, h_pad=1.25) output_path = os.path.join(output_dir, "throughput.png") - plt.savefig(output_path, dpi=150) + save_benchmark_figure(fig, output_path) plt.close(fig) return output_path @@ -236,9 +251,18 @@ def write_report( lines.append("") lines.append( "This benchmark compares serialization and deserialization throughput for " - "Apache Fory, Protocol Buffers, and MessagePack in Swift." + "Apache Fory, Protocol Buffers, and JSON in Swift." ) lines.append("") + lines.append("## Throughput Plot") + lines.append("") + plot_name = os.path.basename(throughput_plot) + if plot_prefix: + image_path = f"{plot_prefix.rstrip('/')}/{plot_name}" + else: + image_path = plot_name + lines.append(f"![Throughput]({image_path})") + lines.append("") lines.append("## Hardware and Runtime Info") lines.append("") lines.append("| Key | Value |") @@ -254,15 +278,8 @@ def write_report( lines.append("") lines.append("## Throughput Results") lines.append("") - plot_name = os.path.basename(throughput_plot) - if plot_prefix: - image_path = f"{plot_prefix.rstrip('/')}/{plot_name}" - else: - image_path = plot_name - lines.append(f"![Throughput]({image_path})") - lines.append("") lines.append( - "| Datatype | Operation | Fory TPS | Protobuf TPS | Msgpack TPS | Fastest |" + "| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest |" ) lines.append("| --- | --- | ---: | ---: | ---: | --- |") @@ -273,18 +290,18 @@ def write_report( throughputs = results.get(datatype, {}).get(operation, {}) fory = throughputs.get("fory", 0.0) protobuf = throughputs.get("protobuf", 0.0) - msgpack = throughputs.get("msgpack", 0.0) + json_tps = throughputs.get("json", 0.0) lines.append( "| " + f"{datatype_title(datatype)} | {operation.capitalize()} | " - + f"{format_tps(fory)} | {format_tps(protobuf)} | {format_tps(msgpack)} | " + + f"{format_tps(fory)} | {format_tps(protobuf)} | {format_tps(json_tps)} | " + f"{winner_cell(throughputs)} |" ) lines.append("") lines.append("## Serialized Size (bytes)") lines.append("") - lines.append("| Datatype | Fory | Protobuf | Msgpack |") + lines.append("| Datatype | Fory | Protobuf | JSON |") lines.append("| --- | ---: | ---: | ---: |") sizes_by_datatype = { normalize_datatype(str(entry.get("dataType", ""))): entry for entry in sizes @@ -299,16 +316,18 @@ def write_report( + f"{datatype_label} | " + f"{entry.get('fory', '-')} | " + f"{entry.get('protobuf', '-')} | " - + f"{entry.get('msgpack', '-')} |" + + f"{entry.get('json', '-')} |" ) - report_path = os.path.join(output_dir, "REPORT.md") + report_path = os.path.join(output_dir, "README.md") + legacy_report_path = os.path.join(output_dir, "REPORT.md") + report_text = "\n".join(lines) + "\n" with open(report_path, "w", encoding="utf-8") as f: - f.write("\n".join(lines) + "\n") + f.write(report_text) + with open(legacy_report_path, "w", encoding="utf-8") as f: + f.write(report_text) - prettier = shutil.which("prettier") - if prettier is not None: - subprocess.run([prettier, "--write", report_path], check=True) + format_markdown_with_prettier(report_path, legacy_report_path) return report_path diff --git a/benchmarks/swift/run.sh b/benchmarks/swift/run.sh index 4ed9a6e778..7e72253422 100755 --- a/benchmarks/swift/run.sh +++ b/benchmarks/swift/run.sh @@ -21,6 +21,7 @@ export ENABLE_FORY_DEBUG_OUTPUT=0 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" +DOCS_DIR="$SCRIPT_DIR/../../docs/benchmarks/swift" # Colors RED='\033[0;31m' @@ -34,6 +35,7 @@ SERIALIZER="" DURATION="3" OUTPUT_JSON="results/benchmark_results.json" NO_REPORT=false +COPY_DOCS=true usage() { echo "Usage: $0 [OPTIONS]" @@ -43,17 +45,18 @@ usage() { echo "Options:" echo " --data " echo " Filter benchmark by data type" - echo " --serializer " + echo " --serializer " echo " Filter benchmark by serializer" echo " --duration Minimum time to run each benchmark (default: 3)" echo " --no-report Skip Python report generation" + echo " --no-copy-docs Skip copying report/plots into docs/benchmarks/swift" echo " --help Show this help message" echo "" echo "Examples:" echo " $0" echo " $0 --data struct" echo " $0 --serializer protobuf" - echo " $0 --data sample --serializer msgpack --duration 5" + echo " $0 --data sample --serializer json --duration 5" exit 0 } @@ -75,6 +78,10 @@ while [[ $# -gt 0 ]]; do NO_REPORT=true shift ;; + --no-copy-docs) + COPY_DOCS=false + shift + ;; --help|-h) usage ;; @@ -127,4 +134,13 @@ if ! python3 -c "import matplotlib" >/dev/null 2>&1; then fi python3 benchmark_report.py --json-file "$OUTPUT_JSON" --output-dir results -echo -e "${GREEN}Report generated: ${SCRIPT_DIR}/results/REPORT.md${NC}" +if [[ "$COPY_DOCS" == true ]]; then + mkdir -p "$DOCS_DIR" + cp results/README.md "$DOCS_DIR/README.md" + cp results/throughput.png "$DOCS_DIR/throughput.png" + echo -e "${GREEN}Copied report and throughput plot to: ${DOCS_DIR}${NC}" +fi +echo -e "${GREEN}Report generated: ${SCRIPT_DIR}/results/README.md${NC}" +if [[ "$COPY_DOCS" == true ]]; then + echo -e "Docs sync: ${DOCS_DIR}" +fi diff --git a/docs/benchmarks/cpp/README.md b/docs/benchmarks/cpp/README.md index 156869e3e8..6f878da49b 100644 --- a/docs/benchmarks/cpp/README.md +++ b/docs/benchmarks/cpp/README.md @@ -1,6 +1,6 @@ # C++ Benchmark Performance Report -_Generated on 2026-05-08 03:30:03_ +_Generated on 2026-05-08 17:26:51_ ## How to Generate This Report @@ -11,6 +11,12 @@ cd .. python benchmark_report.py --json-file build/benchmark_results.json --output-dir report ``` +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + ## Hardware & OS Info | Key | Value | @@ -21,76 +27,44 @@ python benchmark_report.py --json-file build/benchmark_results.json --output-dir | CPU Cores (Physical) | 12 | | CPU Cores (Logical) | 12 | | Total RAM (GB) | 48.0 | -| Benchmark Date | 2026-05-08T03:29:26+08:00 | +| Benchmark Date | 2026-05-08T16:29:28+08:00 | | CPU Cores (from benchmark) | 12 | -## Benchmark Plots - -All class-level plots below show throughput (ops/sec). - -### Throughput - -![Throughput](throughput.png) - -### NumericStruct - -![NumericStruct](struct.png) - -### Sample - -![Sample](sample.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### NumericStructList - -![NumericStructList](structlist.png) - -### SampleList - -![SampleList](samplelist.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) - ## Benchmark Results ### Timing Results (nanoseconds) | Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | | ----------------- | ----------- | --------- | ------------- | ------------ | ------- | -| NumericStruct | Serialize | 23.6 | 48.9 | 81.8 | fory | -| NumericStruct | Deserialize | 25.7 | 32.4 | 1171.3 | fory | -| Sample | Serialize | 62.0 | 96.1 | 308.3 | fory | -| Sample | Deserialize | 329.1 | 678.9 | 2712.6 | fory | -| MediaContent | Serialize | 114.2 | 878.1 | 293.4 | fory | -| MediaContent | Deserialize | 417.4 | 1202.5 | 2748.2 | fory | -| NumericStructList | Serialize | 81.7 | 495.5 | 419.7 | fory | -| NumericStructList | Deserialize | 171.5 | 405.8 | 5309.0 | fory | -| SampleList | Serialize | 279.3 | 4992.3 | 1563.2 | fory | -| SampleList | Deserialize | 1784.6 | 5065.2 | 13040.9 | fory | -| MediaContentList | Serialize | 485.3 | 4798.9 | 1479.4 | fory | -| MediaContentList | Deserialize | 2235.5 | 6482.6 | 13797.8 | fory | +| NumericStruct | Serialize | 24.9 | 48.2 | 91.0 | fory | +| NumericStruct | Deserialize | 26.6 | 33.0 | 1194.5 | fory | +| Sample | Serialize | 62.3 | 97.3 | 314.6 | fory | +| Sample | Deserialize | 371.1 | 689.0 | 2649.9 | fory | +| MediaContent | Serialize | 115.0 | 857.2 | 311.7 | fory | +| MediaContent | Deserialize | 406.5 | 1193.1 | 3311.1 | fory | +| NumericStructList | Serialize | 81.7 | 495.0 | 485.6 | fory | +| NumericStructList | Deserialize | 180.9 | 410.6 | 5733.1 | fory | +| SampleList | Serialize | 284.9 | 5004.9 | 1579.6 | fory | +| SampleList | Deserialize | 1928.7 | 5118.1 | 13396.8 | fory | +| MediaContentList | Serialize | 464.8 | 4861.1 | 1671.1 | fory | +| MediaContentList | Deserialize | 2099.8 | 6610.3 | 13963.4 | fory | ### Throughput Results (ops/sec) | Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | | ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | -| NumericStruct | Serialize | 42,340,747 | 20,456,013 | 12,223,421 | fory | -| NumericStruct | Deserialize | 38,921,757 | 30,876,920 | 853,731 | fory | -| Sample | Serialize | 16,120,239 | 10,402,361 | 3,243,915 | fory | -| Sample | Deserialize | 3,038,551 | 1,473,035 | 368,652 | fory | -| MediaContent | Serialize | 8,760,221 | 1,138,812 | 3,408,303 | fory | -| MediaContent | Deserialize | 2,395,697 | 831,626 | 363,876 | fory | -| NumericStructList | Serialize | 12,246,425 | 2,018,049 | 2,382,679 | fory | -| NumericStructList | Deserialize | 5,829,352 | 2,464,295 | 188,358 | fory | -| SampleList | Serialize | 3,579,741 | 200,307 | 639,695 | fory | -| SampleList | Deserialize | 560,346 | 197,425 | 76,682 | fory | -| MediaContentList | Serialize | 2,060,531 | 208,381 | 675,956 | fory | -| MediaContentList | Deserialize | 447,319 | 154,259 | 72,475 | fory | +| NumericStruct | Serialize | 40,087,668 | 20,733,305 | 10,989,907 | fory | +| NumericStruct | Deserialize | 37,606,127 | 30,296,744 | 837,189 | fory | +| Sample | Serialize | 16,041,299 | 10,277,207 | 3,178,983 | fory | +| Sample | Deserialize | 2,694,434 | 1,451,449 | 377,373 | fory | +| MediaContent | Serialize | 8,698,574 | 1,166,539 | 3,208,626 | fory | +| MediaContent | Deserialize | 2,460,094 | 838,185 | 302,013 | fory | +| NumericStructList | Serialize | 12,240,275 | 2,020,102 | 2,059,276 | fory | +| NumericStructList | Deserialize | 5,527,333 | 2,435,246 | 174,427 | fory | +| SampleList | Serialize | 3,510,210 | 199,804 | 633,061 | fory | +| SampleList | Deserialize | 518,490 | 195,386 | 74,645 | fory | +| MediaContentList | Serialize | 2,151,560 | 205,715 | 598,396 | fory | +| MediaContentList | Deserialize | 476,241 | 151,280 | 71,616 | fory | ### Serialized Data Sizes (bytes) diff --git a/docs/benchmarks/cpp/mediacontent.png b/docs/benchmarks/cpp/mediacontent.png deleted file mode 100644 index 5ed5ba3ffc..0000000000 Binary files a/docs/benchmarks/cpp/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/cpp/mediacontentlist.png b/docs/benchmarks/cpp/mediacontentlist.png deleted file mode 100644 index f3a6b47ef1..0000000000 Binary files a/docs/benchmarks/cpp/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/cpp/sample.png b/docs/benchmarks/cpp/sample.png deleted file mode 100644 index 4d8290abbd..0000000000 Binary files a/docs/benchmarks/cpp/sample.png and /dev/null differ diff --git a/docs/benchmarks/cpp/samplelist.png b/docs/benchmarks/cpp/samplelist.png deleted file mode 100644 index 3161372328..0000000000 Binary files a/docs/benchmarks/cpp/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/cpp/struct.png b/docs/benchmarks/cpp/struct.png deleted file mode 100644 index 81d8b838c8..0000000000 Binary files a/docs/benchmarks/cpp/struct.png and /dev/null differ diff --git a/docs/benchmarks/cpp/structlist.png b/docs/benchmarks/cpp/structlist.png deleted file mode 100644 index 1e25666a8e..0000000000 Binary files a/docs/benchmarks/cpp/structlist.png and /dev/null differ diff --git a/docs/benchmarks/cpp/throughput.png b/docs/benchmarks/cpp/throughput.png index ac557797b8..68cf01027f 100644 Binary files a/docs/benchmarks/cpp/throughput.png and b/docs/benchmarks/cpp/throughput.png differ diff --git a/docs/benchmarks/csharp/README.md b/docs/benchmarks/csharp/README.md index 62a8756055..f2c5e640fd 100644 --- a/docs/benchmarks/csharp/README.md +++ b/docs/benchmarks/csharp/README.md @@ -1,6 +1,6 @@ # C# Benchmark Performance Report -_Generated on 2026-05-08 03:33:12_ +_Generated on 2026-05-08 17:26:51_ ## How to Generate This Report @@ -10,6 +10,12 @@ dotnet run -c Release --project ./Fory.CSharpBenchmark.csproj -- --output build/ python3 benchmark_report.py --json-file build/benchmark_results.json --output-dir report ``` +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + ## Hardware & OS Info | Key | Value | @@ -18,7 +24,7 @@ python3 benchmark_report.py --json-file build/benchmark_results.json --output-di | OS Architecture | Arm64 | | Machine | Arm64 | | Runtime Version | 8.0.24 | -| Benchmark Date (UTC) | 2026-05-07T19:33:11.3470280Z | +| Benchmark Date (UTC) | 2026-05-08T08:17:48.7871870Z | | Warmup Seconds | 1 | | Duration Seconds | 3 | | CPU Logical Cores (from benchmark) | 12 | @@ -35,73 +41,41 @@ python3 benchmark_report.py --json-file build/benchmark_results.json --output-di | Datatypes | struct, sample, mediacontent, structlist, samplelist, mediacontentlist | | Operations | serialize, deserialize | -## Benchmark Plots - -All class-level plots below show throughput (ops/sec). - -### Throughput - -![Throughput](throughput.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) - -### Sample - -![Sample](sample.png) - -### SampleList - -![SampleList](samplelist.png) - -### NumericStruct - -![NumericStruct](struct.png) - -### NumericStructList - -![NumericStructList](structlist.png) - ## Benchmark Results ### Timing Results (nanoseconds) | Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | | ----------------- | ----------- | --------- | ------------- | ------------ | ------- | -| NumericStruct | Serialize | 48.1 | 153.4 | 101.8 | fory | -| NumericStruct | Deserialize | 66.6 | 230.5 | 139.2 | fory | -| Sample | Serialize | 264.2 | 587.4 | 357.5 | fory | -| Sample | Deserialize | 173.4 | 1117.6 | 535.4 | fory | -| MediaContent | Serialize | 318.4 | 460.0 | 358.7 | fory | -| MediaContent | Deserialize | 416.4 | 765.3 | 702.2 | fory | -| NumericStructList | Serialize | 177.8 | 627.9 | 418.7 | fory | -| NumericStructList | Deserialize | 276.6 | 921.6 | 673.6 | fory | -| SampleList | Serialize | 1223.2 | 2864.3 | 1733.2 | fory | -| SampleList | Deserialize | 811.7 | 5398.9 | 2663.9 | fory | -| MediaContentList | Serialize | 1464.7 | 2264.3 | 1757.8 | fory | -| MediaContentList | Deserialize | 1805.5 | 3604.5 | 3485.5 | fory | +| NumericStruct | Serialize | 50.3 | 170.8 | 107.8 | fory | +| NumericStruct | Deserialize | 82.4 | 252.0 | 143.4 | fory | +| Sample | Serialize | 263.2 | 607.1 | 377.1 | fory | +| Sample | Deserialize | 199.4 | 1191.7 | 785.6 | fory | +| MediaContent | Serialize | 379.7 | 509.6 | 417.6 | fory | +| MediaContent | Deserialize | 450.3 | 846.6 | 791.4 | fory | +| NumericStructList | Serialize | 183.7 | 641.8 | 447.8 | fory | +| NumericStructList | Deserialize | 288.3 | 974.3 | 702.1 | fory | +| SampleList | Serialize | 1205.7 | 3559.1 | 1864.1 | fory | +| SampleList | Deserialize | 895.1 | 5710.3 | 2757.4 | fory | +| MediaContentList | Serialize | 1495.4 | 2473.6 | 1812.4 | fory | +| MediaContentList | Deserialize | 1946.7 | 3789.3 | 3778.4 | fory | ### Throughput Results (ops/sec) | Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | | ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | -| NumericStruct | Serialize | 20,789,902 | 6,517,124 | 9,824,350 | fory | -| NumericStruct | Deserialize | 15,012,843 | 4,337,643 | 7,182,355 | fory | -| Sample | Serialize | 3,785,458 | 1,702,392 | 2,797,102 | fory | -| Sample | Deserialize | 5,765,682 | 894,796 | 1,867,599 | fory | -| MediaContent | Serialize | 3,141,058 | 2,173,936 | 2,787,836 | fory | -| MediaContent | Deserialize | 2,401,552 | 1,306,689 | 1,424,121 | fory | -| NumericStructList | Serialize | 5,625,375 | 1,592,535 | 2,388,188 | fory | -| NumericStructList | Deserialize | 3,615,288 | 1,085,025 | 1,484,484 | fory | -| SampleList | Serialize | 817,528 | 349,125 | 576,972 | fory | -| SampleList | Deserialize | 1,231,958 | 185,223 | 375,385 | fory | -| MediaContentList | Serialize | 682,734 | 441,639 | 568,884 | fory | -| MediaContentList | Deserialize | 553,871 | 277,432 | 286,900 | fory | +| NumericStruct | Serialize | 19,881,457 | 5,853,473 | 9,276,378 | fory | +| NumericStruct | Deserialize | 12,137,374 | 3,968,585 | 6,973,504 | fory | +| Sample | Serialize | 3,799,418 | 1,647,119 | 2,652,142 | fory | +| Sample | Deserialize | 5,016,006 | 839,129 | 1,272,975 | fory | +| MediaContent | Serialize | 2,633,704 | 1,962,428 | 2,394,549 | fory | +| MediaContent | Deserialize | 2,220,537 | 1,181,222 | 1,263,568 | fory | +| NumericStructList | Serialize | 5,445,002 | 1,558,156 | 2,232,996 | fory | +| NumericStructList | Deserialize | 3,469,207 | 1,026,402 | 1,424,322 | fory | +| SampleList | Serialize | 829,415 | 280,973 | 536,448 | fory | +| SampleList | Deserialize | 1,117,133 | 175,122 | 362,663 | fory | +| MediaContentList | Serialize | 668,732 | 404,272 | 551,755 | fory | +| MediaContentList | Deserialize | 513,699 | 263,899 | 264,664 | fory | ### Serialized Data Sizes (bytes) diff --git a/docs/benchmarks/csharp/mediacontent.png b/docs/benchmarks/csharp/mediacontent.png deleted file mode 100644 index 04ecbc2f0c..0000000000 Binary files a/docs/benchmarks/csharp/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/csharp/mediacontentlist.png b/docs/benchmarks/csharp/mediacontentlist.png deleted file mode 100644 index 5f106c6295..0000000000 Binary files a/docs/benchmarks/csharp/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/csharp/sample.png b/docs/benchmarks/csharp/sample.png deleted file mode 100644 index 18ce39c5cb..0000000000 Binary files a/docs/benchmarks/csharp/sample.png and /dev/null differ diff --git a/docs/benchmarks/csharp/samplelist.png b/docs/benchmarks/csharp/samplelist.png deleted file mode 100644 index 9adb52a37f..0000000000 Binary files a/docs/benchmarks/csharp/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/csharp/struct.png b/docs/benchmarks/csharp/struct.png deleted file mode 100644 index 6d1a2a1ac2..0000000000 Binary files a/docs/benchmarks/csharp/struct.png and /dev/null differ diff --git a/docs/benchmarks/csharp/structlist.png b/docs/benchmarks/csharp/structlist.png deleted file mode 100644 index 137a868acf..0000000000 Binary files a/docs/benchmarks/csharp/structlist.png and /dev/null differ diff --git a/docs/benchmarks/csharp/throughput.png b/docs/benchmarks/csharp/throughput.png index 00b97d0984..926ec88f5a 100644 Binary files a/docs/benchmarks/csharp/throughput.png and b/docs/benchmarks/csharp/throughput.png differ diff --git a/docs/benchmarks/dart/README.md b/docs/benchmarks/dart/README.md index 47773d2f86..b42cedf85c 100644 --- a/docs/benchmarks/dart/README.md +++ b/docs/benchmarks/dart/README.md @@ -2,11 +2,15 @@ This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Dart. +## Throughput Plot + +![Throughput](throughput.png) + ## Hardware and Runtime Info | Key | Value | | --------------------- | ----------------------------------------------------------------- | -| Timestamp | 2026-05-07T19:42:18.589207Z | +| Timestamp | 2026-05-08T08:23:10.201764Z | | OS | Version 15.7.2 (Build 24G325) | | Host | MacBook-Pro.local | | CPU Cores (Logical) | 12 | @@ -18,22 +22,20 @@ This benchmark compares serialization and deserialization throughput for Apache ## Throughput Results -![Throughput](throughput.png) - | Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest | | ----------------- | ----------- | --------: | -----------: | --------: | ------------- | -| NumericStruct | Serialize | 9,315,764 | 1,609,183 | 768,972 | fory (5.79x) | -| NumericStruct | Deserialize | 9,239,326 | 3,304,725 | 1,392,396 | fory (2.80x) | -| Sample | Serialize | 2,513,144 | 553,880 | 137,741 | fory (4.54x) | -| Sample | Deserialize | 2,394,927 | 930,969 | 242,522 | fory (2.57x) | -| MediaContent | Serialize | 1,193,789 | 432,224 | 232,731 | fory (2.76x) | -| MediaContent | Deserialize | 2,015,704 | 774,578 | 252,242 | fory (2.60x) | -| NumericStructList | Serialize | 2,565,386 | 292,207 | 144,065 | fory (8.78x) | -| NumericStructList | Deserialize | 3,079,355 | 542,152 | 268,213 | fory (5.68x) | -| SampleList | Serialize | 587,773 | 50,180 | 26,101 | fory (11.71x) | -| SampleList | Deserialize | 532,619 | 111,270 | 48,355 | fory (4.79x) | -| MediaContentList | Serialize | 275,271 | 81,587 | 43,376 | fory (3.37x) | -| MediaContentList | Deserialize | 459,335 | 150,838 | 50,792 | fory (3.05x) | +| NumericStruct | Serialize | 9,007,809 | 1,582,003 | 774,574 | fory (5.69x) | +| NumericStruct | Deserialize | 9,039,403 | 3,343,459 | 1,391,036 | fory (2.70x) | +| Sample | Serialize | 2,434,800 | 538,385 | 133,800 | fory (4.52x) | +| Sample | Deserialize | 2,362,665 | 909,410 | 239,924 | fory (2.60x) | +| MediaContent | Serialize | 1,167,225 | 423,564 | 223,387 | fory (2.76x) | +| MediaContent | Deserialize | 1,987,141 | 770,107 | 254,156 | fory (2.58x) | +| NumericStructList | Serialize | 2,551,102 | 283,827 | 139,615 | fory (8.99x) | +| NumericStructList | Deserialize | 3,028,068 | 530,360 | 265,058 | fory (5.71x) | +| SampleList | Serialize | 568,937 | 47,426 | 25,386 | fory (12.00x) | +| SampleList | Deserialize | 542,871 | 108,349 | 48,058 | fory (5.01x) | +| MediaContentList | Serialize | 226,507 | 81,828 | 41,780 | fory (2.77x) | +| MediaContentList | Deserialize | 458,667 | 139,395 | 50,183 | fory (3.29x) | ## Serialized Size (bytes) @@ -45,29 +47,3 @@ This benchmark compares serialization and deserialization throughput for Apache | NumericStructList | 255 | 475 | 816 | | SampleList | 1978 | 1900 | 3976 | | MediaContentList | 1531 | 1550 | 3122 | - -## Per-workload Plots - -### NumericStruct - -![NumericStruct](struct.png) - -### Sample - -![Sample](sample.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### NumericStructList - -![NumericStructList](structlist.png) - -### SampleList - -![SampleList](samplelist.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) diff --git a/docs/benchmarks/dart/mediacontent.png b/docs/benchmarks/dart/mediacontent.png deleted file mode 100644 index 55740b867e..0000000000 Binary files a/docs/benchmarks/dart/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/dart/mediacontentlist.png b/docs/benchmarks/dart/mediacontentlist.png deleted file mode 100644 index 74f0952dfd..0000000000 Binary files a/docs/benchmarks/dart/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/dart/sample.png b/docs/benchmarks/dart/sample.png deleted file mode 100644 index 442cec5fff..0000000000 Binary files a/docs/benchmarks/dart/sample.png and /dev/null differ diff --git a/docs/benchmarks/dart/samplelist.png b/docs/benchmarks/dart/samplelist.png deleted file mode 100644 index 874c8e6427..0000000000 Binary files a/docs/benchmarks/dart/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/dart/struct.png b/docs/benchmarks/dart/struct.png deleted file mode 100644 index c2765ca99a..0000000000 Binary files a/docs/benchmarks/dart/struct.png and /dev/null differ diff --git a/docs/benchmarks/dart/structlist.png b/docs/benchmarks/dart/structlist.png deleted file mode 100644 index 8e74ba111c..0000000000 Binary files a/docs/benchmarks/dart/structlist.png and /dev/null differ diff --git a/docs/benchmarks/dart/throughput.png b/docs/benchmarks/dart/throughput.png index 58abab8da0..b97ee73ff3 100644 Binary files a/docs/benchmarks/dart/throughput.png and b/docs/benchmarks/dart/throughput.png differ diff --git a/docs/benchmarks/go/README.md b/docs/benchmarks/go/README.md index 7403afbd0b..0b08038262 100644 --- a/docs/benchmarks/go/README.md +++ b/docs/benchmarks/go/README.md @@ -1,46 +1,50 @@ # Go Serialization Benchmark Report -Generated: 2026-05-08 03:21:36 +Generated: 2026-05-08 17:27:11 + +## Performance Chart + +![Throughput](throughput.png) ## System Information - **OS**: Darwin 24.6.0 - **Architecture**: arm64 -- **Python**: 3.9.6 +- **Python**: 3.10.8 ## Performance Summary | Data Type | Operation | Fory (ops/s) | Protobuf (ops/s) | Msgpack (ops/s) | Fory vs PB | Fory vs MP | | ----------------- | ----------- | ------------ | ---------------- | --------------- | ---------- | ---------- | -| NumericStruct | Serialize | 12.58M | 6.96M | 3.66M | 1.81x | 3.43x | -| NumericStruct | Deserialize | 10.60M | 8.15M | 1.99M | 1.30x | 5.32x | -| Sample | Serialize | 7.09M | 2.55M | 640K | 2.78x | 11.08x | -| Sample | Deserialize | 3.44M | 2.16M | 346K | 1.59x | 9.93x | -| MediaContent | Serialize | 3.83M | 1.90M | 1.14M | 2.02x | 3.36x | -| MediaContent | Deserialize | 2.10M | 1.61M | 661K | 1.30x | 3.18x | -| NumericStructList | Serialize | 1.07M | 376K | 205K | 2.86x | 5.23x | -| NumericStructList | Deserialize | 1.10M | 374K | 102K | 2.94x | 10.74x | -| SampleList | Serialize | 505K | 130K | 37K | 3.88x | 13.73x | -| SampleList | Deserialize | 212K | 101K | 17K | 2.10x | 12.40x | -| MediaContentList | Serialize | 260K | 98K | 65K | 2.66x | 3.97x | -| MediaContentList | Deserialize | 124K | 80K | 33K | 1.56x | 3.82x | +| NumericStruct | Serialize | 12.74M | 7.16M | 3.63M | 1.78x | 3.51x | +| NumericStruct | Deserialize | 10.63M | 8.40M | 1.78M | 1.27x | 5.98x | +| Sample | Serialize | 7.16M | 2.53M | 646K | 2.84x | 11.10x | +| Sample | Deserialize | 3.27M | 2.10M | 343K | 1.56x | 9.54x | +| MediaContent | Serialize | 3.74M | 1.75M | 1.14M | 2.14x | 3.27x | +| MediaContent | Deserialize | 2.03M | 1.23M | 646K | 1.66x | 3.15x | +| NumericStructList | Serialize | 1.10M | 386K | 201K | 2.84x | 5.44x | +| NumericStructList | Deserialize | 1.09M | 368K | 103K | 2.96x | 10.54x | +| SampleList | Serialize | 496K | 126K | 36K | 3.93x | 13.83x | +| SampleList | Deserialize | 195K | 96K | 17K | 2.04x | 11.73x | +| MediaContentList | Serialize | 250K | 91K | 57K | 2.73x | 4.38x | +| MediaContentList | Deserialize | 112K | 74K | 31K | 1.53x | 3.65x | ## Detailed Timing (ns/op) | Data Type | Operation | Fory | Protobuf | Msgpack | | ----------------- | ----------- | ------ | -------- | ------- | -| NumericStruct | Serialize | 79.5 | 143.7 | 273.0 | -| NumericStruct | Deserialize | 94.3 | 122.7 | 501.7 | -| Sample | Serialize | 141.0 | 391.7 | 1562.0 | -| Sample | Deserialize | 290.6 | 462.2 | 2887.0 | -| MediaContent | Serialize | 261.1 | 526.8 | 876.5 | -| MediaContent | Deserialize | 476.0 | 619.4 | 1513.0 | -| NumericStructList | Serialize | 930.6 | 2661.0 | 4868.0 | -| NumericStructList | Deserialize | 909.3 | 2675.0 | 9767.0 | -| SampleList | Serialize | 1979.0 | 7671.0 | 27168.0 | -| SampleList | Deserialize | 4709.0 | 9893.0 | 58414.0 | -| MediaContentList | Serialize | 3846.0 | 10212.0 | 15272.0 | -| MediaContentList | Deserialize | 8055.0 | 12552.0 | 30730.0 | +| NumericStruct | Serialize | 78.5 | 139.6 | 275.5 | +| NumericStruct | Deserialize | 94.0 | 119.0 | 562.5 | +| Sample | Serialize | 139.6 | 395.9 | 1549.0 | +| Sample | Deserialize | 306.0 | 475.9 | 2919.0 | +| MediaContent | Serialize | 267.3 | 571.6 | 875.1 | +| MediaContent | Deserialize | 492.4 | 815.8 | 1549.0 | +| NumericStructList | Serialize | 912.8 | 2594.0 | 4970.0 | +| NumericStructList | Deserialize | 919.9 | 2721.0 | 9698.0 | +| SampleList | Serialize | 2018.0 | 7927.0 | 27909.0 | +| SampleList | Deserialize | 5126.0 | 10460.0 | 60118.0 | +| MediaContentList | Serialize | 4006.0 | 10939.0 | 17553.0 | +| MediaContentList | Deserialize | 8893.0 | 13588.0 | 32439.0 | ### Serialized Data Sizes (bytes) @@ -52,33 +56,3 @@ Generated: 2026-05-08 03:21:36 | NumericStructList | 819 | 1900 | 1766 | | SampleList | 7599 | 7560 | 10486 | | MediaContentList | 5774 | 6080 | 8006 | - -## Performance Charts - -### Throughput - -![Throughput](throughput.png) - -### NumericStruct - -![NumericStruct Benchmark](benchmark_struct.png) - -### Sample - -![Sample Benchmark](benchmark_sample.png) - -### MediaContent - -![MediaContent Benchmark](benchmark_mediacontent.png) - -### NumericStructList - -![NumericStructList Benchmark](benchmark_structlist.png) - -### SampleList - -![SampleList Benchmark](benchmark_samplelist.png) - -### MediaContentList - -![MediaContentList Benchmark](benchmark_mediacontentlist.png) diff --git a/docs/benchmarks/go/benchmark_mediacontent.png b/docs/benchmarks/go/benchmark_mediacontent.png deleted file mode 100644 index 568b0dd590..0000000000 Binary files a/docs/benchmarks/go/benchmark_mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/go/benchmark_mediacontentlist.png b/docs/benchmarks/go/benchmark_mediacontentlist.png deleted file mode 100644 index d068e81447..0000000000 Binary files a/docs/benchmarks/go/benchmark_mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/go/benchmark_results.txt b/docs/benchmarks/go/benchmark_results.txt index 21aa26e639..9f471bc01f 100644 --- a/docs/benchmarks/go/benchmark_results.txt +++ b/docs/benchmarks/go/benchmark_results.txt @@ -2,27 +2,27 @@ Serialized Sizes (bytes): ============================================ NumericStruct: - Fory: 58 bytes - Protobuf: 61 bytes - Msgpack: 57 bytes + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes Sample: - Fory: 446 bytes + Fory: 445 bytes Protobuf: 375 bytes Msgpack: 524 bytes MediaContent: - Fory: 342 bytes + Fory: 340 bytes Protobuf: 301 bytes Msgpack: 400 bytes -StructList: - Fory: 560 bytes - Protobuf: 1260 bytes - Msgpack: 1146 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes SampleList: - Fory: 7600 bytes + Fory: 7599 bytes Protobuf: 7560 bytes Msgpack: 10486 bytes MediaContentList: - Fory: 5776 bytes + Fory: 5774 bytes Protobuf: 6080 bytes Msgpack: 8006 bytes ============================================ @@ -30,27 +30,27 @@ MediaContentList: Serialized Sizes (bytes): ============================================ NumericStruct: - Fory: 58 bytes - Protobuf: 61 bytes - Msgpack: 57 bytes + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes Sample: - Fory: 446 bytes + Fory: 445 bytes Protobuf: 375 bytes Msgpack: 524 bytes MediaContent: - Fory: 342 bytes + Fory: 340 bytes Protobuf: 301 bytes Msgpack: 400 bytes -StructList: - Fory: 560 bytes - Protobuf: 1260 bytes - Msgpack: 1146 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes SampleList: - Fory: 7600 bytes + Fory: 7599 bytes Protobuf: 7560 bytes Msgpack: 10486 bytes MediaContentList: - Fory: 5776 bytes + Fory: 5774 bytes Protobuf: 6080 bytes Msgpack: 8006 bytes ============================================ @@ -58,27 +58,27 @@ MediaContentList: Serialized Sizes (bytes): ============================================ NumericStruct: - Fory: 58 bytes - Protobuf: 61 bytes - Msgpack: 57 bytes + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes Sample: - Fory: 446 bytes + Fory: 445 bytes Protobuf: 375 bytes Msgpack: 524 bytes MediaContent: - Fory: 342 bytes + Fory: 340 bytes Protobuf: 301 bytes Msgpack: 400 bytes -StructList: - Fory: 560 bytes - Protobuf: 1260 bytes - Msgpack: 1146 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes SampleList: - Fory: 7600 bytes + Fory: 7599 bytes Protobuf: 7560 bytes Msgpack: 10486 bytes MediaContentList: - Fory: 5776 bytes + Fory: 5774 bytes Protobuf: 6080 bytes Msgpack: 8006 bytes ============================================ @@ -86,27 +86,27 @@ MediaContentList: Serialized Sizes (bytes): ============================================ NumericStruct: - Fory: 58 bytes - Protobuf: 61 bytes - Msgpack: 57 bytes + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes Sample: - Fory: 446 bytes + Fory: 445 bytes Protobuf: 375 bytes Msgpack: 524 bytes MediaContent: - Fory: 342 bytes + Fory: 340 bytes Protobuf: 301 bytes Msgpack: 400 bytes -StructList: - Fory: 560 bytes - Protobuf: 1260 bytes - Msgpack: 1146 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes SampleList: - Fory: 7600 bytes + Fory: 7599 bytes Protobuf: 7560 bytes Msgpack: 10486 bytes MediaContentList: - Fory: 5776 bytes + Fory: 5774 bytes Protobuf: 6080 bytes Msgpack: 8006 bytes ============================================ @@ -114,27 +114,27 @@ MediaContentList: Serialized Sizes (bytes): ============================================ NumericStruct: - Fory: 58 bytes - Protobuf: 61 bytes - Msgpack: 57 bytes + Fory: 78 bytes + Protobuf: 93 bytes + Msgpack: 88 bytes Sample: - Fory: 446 bytes + Fory: 445 bytes Protobuf: 375 bytes Msgpack: 524 bytes MediaContent: - Fory: 342 bytes + Fory: 340 bytes Protobuf: 301 bytes Msgpack: 400 bytes -StructList: - Fory: 560 bytes - Protobuf: 1260 bytes - Msgpack: 1146 bytes +NumericStructList: + Fory: 819 bytes + Protobuf: 1900 bytes + Msgpack: 1766 bytes SampleList: - Fory: 7600 bytes + Fory: 7599 bytes Protobuf: 7560 bytes Msgpack: 10486 bytes MediaContentList: - Fory: 5776 bytes + Fory: 5774 bytes Protobuf: 6080 bytes Msgpack: 8006 bytes ============================================ @@ -142,185 +142,185 @@ goos: darwin goarch: arm64 pkg: github.com/apache/fory/benchmarks/go cpu: Apple M4 Pro -BenchmarkFory_Struct_Serialize-12 18441836 65.29 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Struct_Serialize-12 18408471 65.41 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Struct_Serialize-12 17736544 65.19 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Struct_Serialize-12 18385110 65.37 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Struct_Serialize-12 18366690 65.95 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_Struct_Serialize-12 11775620 100.3 ns/op 144 B/op 2 allocs/op -BenchmarkProtobuf_Struct_Serialize-12 12272547 97.80 ns/op 144 B/op 2 allocs/op -BenchmarkProtobuf_Struct_Serialize-12 12371797 97.68 ns/op 144 B/op 2 allocs/op -BenchmarkProtobuf_Struct_Serialize-12 12184240 97.62 ns/op 144 B/op 2 allocs/op -BenchmarkProtobuf_Struct_Serialize-12 12216723 97.78 ns/op 144 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Serialize-12 7076667 171.4 ns/op 112 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Serialize-12 6900595 173.6 ns/op 112 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Serialize-12 6820158 184.8 ns/op 112 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Serialize-12 6626666 176.1 ns/op 112 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Serialize-12 6930025 184.9 ns/op 112 B/op 2 allocs/op -BenchmarkFory_Struct_Deserialize-12 12602647 85.48 ns/op 32 B/op 1 allocs/op -BenchmarkFory_Struct_Deserialize-12 14198599 82.69 ns/op 32 B/op 1 allocs/op -BenchmarkFory_Struct_Deserialize-12 12750420 85.79 ns/op 32 B/op 1 allocs/op -BenchmarkFory_Struct_Deserialize-12 13704170 83.80 ns/op 32 B/op 1 allocs/op -BenchmarkFory_Struct_Deserialize-12 14302634 82.66 ns/op 32 B/op 1 allocs/op -BenchmarkProtobuf_Struct_Deserialize-12 13726815 87.92 ns/op 80 B/op 1 allocs/op -BenchmarkProtobuf_Struct_Deserialize-12 13350038 87.81 ns/op 80 B/op 1 allocs/op -BenchmarkProtobuf_Struct_Deserialize-12 13771383 88.75 ns/op 80 B/op 1 allocs/op -BenchmarkProtobuf_Struct_Deserialize-12 13840743 90.58 ns/op 80 B/op 1 allocs/op -BenchmarkProtobuf_Struct_Deserialize-12 13009616 90.86 ns/op 80 B/op 1 allocs/op -BenchmarkMsgpack_Struct_Deserialize-12 3796537 304.3 ns/op 80 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Deserialize-12 3963567 305.9 ns/op 80 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Deserialize-12 3949239 305.3 ns/op 80 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Deserialize-12 3842962 307.7 ns/op 80 B/op 2 allocs/op -BenchmarkMsgpack_Struct_Deserialize-12 3946874 309.6 ns/op 80 B/op 2 allocs/op -BenchmarkFory_StructList_Serialize-12 1887151 654.7 ns/op 0 B/op 0 allocs/op -BenchmarkFory_StructList_Serialize-12 1836488 635.3 ns/op 0 B/op 0 allocs/op -BenchmarkFory_StructList_Serialize-12 1863346 647.0 ns/op 0 B/op 0 allocs/op -BenchmarkFory_StructList_Serialize-12 1913204 627.8 ns/op 0 B/op 0 allocs/op -BenchmarkFory_StructList_Serialize-12 1887578 632.8 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_StructList_Serialize-12 664448 1773 ns/op 3104 B/op 23 allocs/op -BenchmarkProtobuf_StructList_Serialize-12 684133 1794 ns/op 3104 B/op 23 allocs/op -BenchmarkProtobuf_StructList_Serialize-12 642586 1784 ns/op 3104 B/op 23 allocs/op -BenchmarkProtobuf_StructList_Serialize-12 674518 1768 ns/op 3104 B/op 23 allocs/op -BenchmarkProtobuf_StructList_Serialize-12 668338 1783 ns/op 3104 B/op 23 allocs/op -BenchmarkMsgpack_StructList_Serialize-12 350497 3433 ns/op 4106 B/op 8 allocs/op -BenchmarkMsgpack_StructList_Serialize-12 351436 3409 ns/op 4106 B/op 8 allocs/op -BenchmarkMsgpack_StructList_Serialize-12 344049 3398 ns/op 4106 B/op 8 allocs/op -BenchmarkMsgpack_StructList_Serialize-12 357286 3361 ns/op 4106 B/op 8 allocs/op -BenchmarkMsgpack_StructList_Serialize-12 358231 3340 ns/op 4106 B/op 8 allocs/op -BenchmarkFory_StructList_Deserialize-12 1335152 901.9 ns/op 688 B/op 3 allocs/op -BenchmarkFory_StructList_Deserialize-12 1295712 878.2 ns/op 688 B/op 3 allocs/op -BenchmarkFory_StructList_Deserialize-12 1351540 889.7 ns/op 688 B/op 3 allocs/op -BenchmarkFory_StructList_Deserialize-12 1327351 896.7 ns/op 688 B/op 3 allocs/op -BenchmarkFory_StructList_Deserialize-12 1326216 906.4 ns/op 688 B/op 3 allocs/op -BenchmarkProtobuf_StructList_Deserialize-12 618871 1914 ns/op 2808 B/op 28 allocs/op -BenchmarkProtobuf_StructList_Deserialize-12 552850 1867 ns/op 2808 B/op 28 allocs/op -BenchmarkProtobuf_StructList_Deserialize-12 621386 1887 ns/op 2808 B/op 28 allocs/op -BenchmarkProtobuf_StructList_Deserialize-12 626301 1916 ns/op 2808 B/op 28 allocs/op -BenchmarkProtobuf_StructList_Deserialize-12 619466 1891 ns/op 2808 B/op 28 allocs/op -BenchmarkMsgpack_StructList_Deserialize-12 209462 5707 ns/op 1424 B/op 7 allocs/op -BenchmarkMsgpack_StructList_Deserialize-12 205341 5680 ns/op 1424 B/op 7 allocs/op -BenchmarkMsgpack_StructList_Deserialize-12 212827 5715 ns/op 1424 B/op 7 allocs/op -BenchmarkMsgpack_StructList_Deserialize-12 212240 5742 ns/op 1424 B/op 7 allocs/op -BenchmarkMsgpack_StructList_Deserialize-12 214522 5709 ns/op 1424 B/op 7 allocs/op -BenchmarkFory_Sample_Serialize-12 8722086 137.8 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Sample_Serialize-12 8074246 137.2 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Sample_Serialize-12 8734982 137.3 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Sample_Serialize-12 8774318 137.3 ns/op 0 B/op 0 allocs/op -BenchmarkFory_Sample_Serialize-12 8791104 137.3 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_Sample_Serialize-12 3474804 339.3 ns/op 704 B/op 2 allocs/op -BenchmarkProtobuf_Sample_Serialize-12 3353059 350.9 ns/op 704 B/op 2 allocs/op -BenchmarkProtobuf_Sample_Serialize-12 3485572 359.7 ns/op 704 B/op 2 allocs/op -BenchmarkProtobuf_Sample_Serialize-12 3293542 364.4 ns/op 704 B/op 2 allocs/op -BenchmarkProtobuf_Sample_Serialize-12 3304266 367.3 ns/op 704 B/op 2 allocs/op -BenchmarkMsgpack_Sample_Serialize-12 815510 1454 ns/op 2321 B/op 7 allocs/op -BenchmarkMsgpack_Sample_Serialize-12 814988 1474 ns/op 2321 B/op 7 allocs/op -BenchmarkMsgpack_Sample_Serialize-12 819774 1441 ns/op 2321 B/op 7 allocs/op -BenchmarkMsgpack_Sample_Serialize-12 806146 1448 ns/op 2321 B/op 7 allocs/op -BenchmarkMsgpack_Sample_Serialize-12 806767 1492 ns/op 2321 B/op 7 allocs/op -BenchmarkFory_Sample_Deserialize-12 4371154 260.9 ns/op 676 B/op 9 allocs/op -BenchmarkFory_Sample_Deserialize-12 4748262 256.2 ns/op 676 B/op 9 allocs/op -BenchmarkFory_Sample_Deserialize-12 4563691 259.0 ns/op 676 B/op 9 allocs/op -BenchmarkFory_Sample_Deserialize-12 4679144 257.7 ns/op 676 B/op 9 allocs/op -BenchmarkFory_Sample_Deserialize-12 4577006 263.6 ns/op 676 B/op 9 allocs/op -BenchmarkProtobuf_Sample_Deserialize-12 2827587 426.9 ns/op 708 B/op 9 allocs/op -BenchmarkProtobuf_Sample_Deserialize-12 2790972 424.5 ns/op 708 B/op 9 allocs/op -BenchmarkProtobuf_Sample_Deserialize-12 2761682 436.8 ns/op 708 B/op 9 allocs/op -BenchmarkProtobuf_Sample_Deserialize-12 2730283 432.4 ns/op 708 B/op 9 allocs/op -BenchmarkProtobuf_Sample_Deserialize-12 2829603 422.2 ns/op 708 B/op 9 allocs/op -BenchmarkMsgpack_Sample_Deserialize-12 445099 2801 ns/op 1576 B/op 38 allocs/op -BenchmarkMsgpack_Sample_Deserialize-12 437914 2686 ns/op 1576 B/op 38 allocs/op -BenchmarkMsgpack_Sample_Deserialize-12 416235 2660 ns/op 1576 B/op 38 allocs/op -BenchmarkMsgpack_Sample_Deserialize-12 448528 2650 ns/op 1576 B/op 38 allocs/op -BenchmarkMsgpack_Sample_Deserialize-12 447135 2661 ns/op 1576 B/op 38 allocs/op -BenchmarkFory_SampleList_Serialize-12 609176 1948 ns/op 0 B/op 0 allocs/op -BenchmarkFory_SampleList_Serialize-12 612290 1958 ns/op 0 B/op 0 allocs/op -BenchmarkFory_SampleList_Serialize-12 605318 1950 ns/op 0 B/op 0 allocs/op -BenchmarkFory_SampleList_Serialize-12 610993 1945 ns/op 0 B/op 0 allocs/op -BenchmarkFory_SampleList_Serialize-12 612297 1962 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_SampleList_Serialize-12 167353 6748 ns/op 14816 B/op 23 allocs/op -BenchmarkProtobuf_SampleList_Serialize-12 170196 6906 ns/op 14816 B/op 23 allocs/op -BenchmarkProtobuf_SampleList_Serialize-12 170259 7246 ns/op 14816 B/op 23 allocs/op -BenchmarkProtobuf_SampleList_Serialize-12 170742 7127 ns/op 14816 B/op 23 allocs/op -BenchmarkProtobuf_SampleList_Serialize-12 167684 7087 ns/op 14816 B/op 23 allocs/op -BenchmarkMsgpack_SampleList_Serialize-12 46962 25632 ns/op 32793 B/op 11 allocs/op -BenchmarkMsgpack_SampleList_Serialize-12 47451 25432 ns/op 32793 B/op 11 allocs/op -BenchmarkMsgpack_SampleList_Serialize-12 44647 26471 ns/op 32793 B/op 11 allocs/op -BenchmarkMsgpack_SampleList_Serialize-12 46993 25869 ns/op 32793 B/op 11 allocs/op -BenchmarkMsgpack_SampleList_Serialize-12 46513 26169 ns/op 32792 B/op 11 allocs/op -BenchmarkFory_SampleList_Deserialize-12 269730 4251 ns/op 13952 B/op 163 allocs/op -BenchmarkFory_SampleList_Deserialize-12 287401 4202 ns/op 13952 B/op 163 allocs/op -BenchmarkFory_SampleList_Deserialize-12 283461 4176 ns/op 13952 B/op 163 allocs/op -BenchmarkFory_SampleList_Deserialize-12 278598 4257 ns/op 13952 B/op 163 allocs/op -BenchmarkFory_SampleList_Deserialize-12 285021 4234 ns/op 13952 B/op 163 allocs/op -BenchmarkProtobuf_SampleList_Deserialize-12 130734 9285 ns/op 20872 B/op 188 allocs/op -BenchmarkProtobuf_SampleList_Deserialize-12 132328 9157 ns/op 20872 B/op 188 allocs/op -BenchmarkProtobuf_SampleList_Deserialize-12 132595 9258 ns/op 20872 B/op 188 allocs/op -BenchmarkProtobuf_SampleList_Deserialize-12 128911 9332 ns/op 20872 B/op 188 allocs/op -BenchmarkProtobuf_SampleList_Deserialize-12 124810 9321 ns/op 20872 B/op 188 allocs/op -BenchmarkMsgpack_SampleList_Deserialize-12 22225 53384 ns/op 37251 B/op 727 allocs/op -BenchmarkMsgpack_SampleList_Deserialize-12 22498 54968 ns/op 37251 B/op 727 allocs/op -BenchmarkMsgpack_SampleList_Deserialize-12 21262 54571 ns/op 37251 B/op 727 allocs/op -BenchmarkMsgpack_SampleList_Deserialize-12 21877 54363 ns/op 37251 B/op 727 allocs/op -BenchmarkMsgpack_SampleList_Deserialize-12 21978 53615 ns/op 37251 B/op 727 allocs/op -BenchmarkFory_MediaContent_Serialize-12 4530111 265.7 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContent_Serialize-12 4538586 267.6 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContent_Serialize-12 4477569 264.6 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContent_Serialize-12 4504632 270.4 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContent_Serialize-12 4519728 268.8 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_MediaContent_Serialize-12 2446335 486.8 ns/op 1144 B/op 11 allocs/op -BenchmarkProtobuf_MediaContent_Serialize-12 2468845 489.5 ns/op 1144 B/op 11 allocs/op -BenchmarkProtobuf_MediaContent_Serialize-12 2463714 468.9 ns/op 1144 B/op 11 allocs/op -BenchmarkProtobuf_MediaContent_Serialize-12 2605141 467.7 ns/op 1144 B/op 11 allocs/op -BenchmarkProtobuf_MediaContent_Serialize-12 2586032 471.1 ns/op 1144 B/op 11 allocs/op -BenchmarkMsgpack_MediaContent_Serialize-12 1496350 787.2 ns/op 1168 B/op 6 allocs/op -BenchmarkMsgpack_MediaContent_Serialize-12 1533099 794.6 ns/op 1168 B/op 6 allocs/op -BenchmarkMsgpack_MediaContent_Serialize-12 1533223 786.9 ns/op 1168 B/op 6 allocs/op -BenchmarkMsgpack_MediaContent_Serialize-12 1558996 778.9 ns/op 1168 B/op 6 allocs/op -BenchmarkMsgpack_MediaContent_Serialize-12 1535682 773.7 ns/op 1168 B/op 6 allocs/op -BenchmarkFory_MediaContent_Deserialize-12 2840757 421.6 ns/op 656 B/op 13 allocs/op -BenchmarkFory_MediaContent_Deserialize-12 2805544 429.2 ns/op 656 B/op 13 allocs/op -BenchmarkFory_MediaContent_Deserialize-12 2819227 433.5 ns/op 656 B/op 13 allocs/op -BenchmarkFory_MediaContent_Deserialize-12 2805493 426.6 ns/op 656 B/op 13 allocs/op -BenchmarkFory_MediaContent_Deserialize-12 2828582 426.9 ns/op 656 B/op 13 allocs/op -BenchmarkProtobuf_MediaContent_Deserialize-12 2141352 569.0 ns/op 1088 B/op 21 allocs/op -BenchmarkProtobuf_MediaContent_Deserialize-12 2052590 559.8 ns/op 1088 B/op 21 allocs/op -BenchmarkProtobuf_MediaContent_Deserialize-12 2134398 550.8 ns/op 1088 B/op 21 allocs/op -BenchmarkProtobuf_MediaContent_Deserialize-12 2151742 567.4 ns/op 1088 B/op 21 allocs/op -BenchmarkProtobuf_MediaContent_Deserialize-12 2155804 553.1 ns/op 1088 B/op 21 allocs/op -BenchmarkMsgpack_MediaContent_Deserialize-12 838147 1376 ns/op 896 B/op 17 allocs/op -BenchmarkMsgpack_MediaContent_Deserialize-12 824925 1392 ns/op 896 B/op 17 allocs/op -BenchmarkMsgpack_MediaContent_Deserialize-12 814362 1414 ns/op 896 B/op 17 allocs/op -BenchmarkMsgpack_MediaContent_Deserialize-12 837973 1399 ns/op 896 B/op 17 allocs/op -BenchmarkMsgpack_MediaContent_Deserialize-12 850816 1432 ns/op 896 B/op 17 allocs/op -BenchmarkFory_MediaContentList_Serialize-12 320854 3724 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContentList_Serialize-12 323788 3725 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContentList_Serialize-12 323928 3688 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContentList_Serialize-12 320772 3652 ns/op 0 B/op 0 allocs/op -BenchmarkFory_MediaContentList_Serialize-12 325976 3736 ns/op 0 B/op 0 allocs/op -BenchmarkProtobuf_MediaContentList_Serialize-12 128820 9015 ns/op 22848 B/op 203 allocs/op -BenchmarkProtobuf_MediaContentList_Serialize-12 133196 9049 ns/op 22848 B/op 203 allocs/op -BenchmarkProtobuf_MediaContentList_Serialize-12 133237 9152 ns/op 22848 B/op 203 allocs/op -BenchmarkProtobuf_MediaContentList_Serialize-12 131954 8952 ns/op 22848 B/op 203 allocs/op -BenchmarkProtobuf_MediaContentList_Serialize-12 132843 9107 ns/op 22848 B/op 203 allocs/op -BenchmarkMsgpack_MediaContentList_Serialize-12 86242 13984 ns/op 16880 B/op 30 allocs/op -BenchmarkMsgpack_MediaContentList_Serialize-12 84384 14032 ns/op 16880 B/op 30 allocs/op -BenchmarkMsgpack_MediaContentList_Serialize-12 83804 14321 ns/op 16880 B/op 30 allocs/op -BenchmarkMsgpack_MediaContentList_Serialize-12 87357 14185 ns/op 16880 B/op 30 allocs/op -BenchmarkMsgpack_MediaContentList_Serialize-12 85074 13911 ns/op 16880 B/op 30 allocs/op -BenchmarkFory_MediaContentList_Deserialize-12 158547 7564 ns/op 13040 B/op 243 allocs/op -BenchmarkFory_MediaContentList_Deserialize-12 159993 7508 ns/op 13040 B/op 243 allocs/op -BenchmarkFory_MediaContentList_Deserialize-12 159046 7447 ns/op 13040 B/op 243 allocs/op -BenchmarkFory_MediaContentList_Deserialize-12 163632 7268 ns/op 13040 B/op 243 allocs/op -BenchmarkFory_MediaContentList_Deserialize-12 163434 7247 ns/op 13040 B/op 243 allocs/op -BenchmarkProtobuf_MediaContentList_Deserialize-12 92965 11322 ns/op 25400 B/op 428 allocs/op -BenchmarkProtobuf_MediaContentList_Deserialize-12 105206 11297 ns/op 25400 B/op 428 allocs/op -BenchmarkProtobuf_MediaContentList_Deserialize-12 104979 11205 ns/op 25400 B/op 428 allocs/op -BenchmarkProtobuf_MediaContentList_Deserialize-12 105859 11327 ns/op 25400 B/op 428 allocs/op -BenchmarkProtobuf_MediaContentList_Deserialize-12 105524 11435 ns/op 25400 B/op 428 allocs/op -BenchmarkMsgpack_MediaContentList_Deserialize-12 43263 27244 ns/op 20058 B/op 307 allocs/op -BenchmarkMsgpack_MediaContentList_Deserialize-12 44388 27167 ns/op 20058 B/op 307 allocs/op -BenchmarkMsgpack_MediaContentList_Deserialize-12 44185 27271 ns/op 20058 B/op 307 allocs/op -BenchmarkMsgpack_MediaContentList_Deserialize-12 43345 27499 ns/op 20058 B/op 307 allocs/op -BenchmarkMsgpack_MediaContentList_Deserialize-12 43718 27975 ns/op 20058 B/op 307 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15385461 78.49 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 14953550 78.78 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15520209 76.33 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15806354 76.43 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStruct_Serialize-12 15314667 78.51 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8704411 138.0 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8522941 138.8 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8285118 138.9 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8818959 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkProtobuf_NumericStruct_Serialize-12 8463876 139.6 ns/op 192 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4396588 274.9 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4438035 272.7 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4352302 272.0 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4346308 277.3 ns/op 240 B/op 3 allocs/op +BenchmarkMsgpack_NumericStruct_Serialize-12 4361384 275.5 ns/op 240 B/op 3 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12784374 95.10 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12615007 94.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12597256 94.07 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12663748 93.35 ns/op 48 B/op 1 allocs/op +BenchmarkFory_NumericStruct_Deserialize-12 12668444 94.05 ns/op 48 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 9892239 157.3 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6950502 152.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 7466115 210.2 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 6987369 156.1 ns/op 96 B/op 1 allocs/op +BenchmarkProtobuf_NumericStruct_Deserialize-12 10052640 119.0 ns/op 96 B/op 1 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2397518 502.5 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2379376 501.1 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2390596 500.8 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2387790 504.9 ns/op 96 B/op 2 allocs/op +BenchmarkMsgpack_NumericStruct_Deserialize-12 2404051 562.5 ns/op 96 B/op 2 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1081 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1000000 1021 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1293205 947.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1278226 929.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_NumericStructList_Serialize-12 1316396 912.8 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 467838 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 462572 2743 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 453700 2589 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 460408 2607 ns/op 4192 B/op 23 allocs/op +BenchmarkProtobuf_NumericStructList_Serialize-12 452139 2594 ns/op 4192 B/op 23 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 247603 4879 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 249087 4844 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 248340 5142 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 206158 5846 ns/op 4106 B/op 8 allocs/op +BenchmarkMsgpack_NumericStructList_Serialize-12 238311 4970 ns/op 4106 B/op 8 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311939 932.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1313912 937.1 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1315593 915.4 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1311540 927.6 ns/op 1072 B/op 3 allocs/op +BenchmarkFory_NumericStructList_Deserialize-12 1292565 919.9 ns/op 1072 B/op 3 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447052 2745 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 429406 2743 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 438819 2706 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447928 2785 ns/op 3512 B/op 28 allocs/op +BenchmarkProtobuf_NumericStructList_Deserialize-12 447502 2721 ns/op 3512 B/op 28 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 122101 9764 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 123258 9896 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 121612 9738 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 124760 9670 ns/op 2193 B/op 7 allocs/op +BenchmarkMsgpack_NumericStructList_Deserialize-12 125086 9698 ns/op 2193 B/op 7 allocs/op +BenchmarkFory_Sample_Serialize-12 8696760 147.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8567470 181.8 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8293867 139.5 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8486005 140.6 ns/op 0 B/op 0 allocs/op +BenchmarkFory_Sample_Serialize-12 8506568 139.6 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3011733 387.9 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3108796 390.8 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 2547630 393.6 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3026661 392.1 ns/op 704 B/op 2 allocs/op +BenchmarkProtobuf_Sample_Serialize-12 3128582 395.9 ns/op 704 B/op 2 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 777014 1553 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 775314 1520 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 757838 1525 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 802612 1757 ns/op 2321 B/op 7 allocs/op +BenchmarkMsgpack_Sample_Serialize-12 790094 1549 ns/op 2321 B/op 7 allocs/op +BenchmarkFory_Sample_Deserialize-12 4167534 327.9 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3184602 338.0 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3629605 301.8 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 4008093 302.2 ns/op 676 B/op 9 allocs/op +BenchmarkFory_Sample_Deserialize-12 3992972 306.0 ns/op 676 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2527548 477.1 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2476074 474.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2532358 475.8 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2408528 473.0 ns/op 708 B/op 9 allocs/op +BenchmarkProtobuf_Sample_Deserialize-12 2482132 475.9 ns/op 708 B/op 9 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 415698 2893 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417212 2950 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 424497 2879 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 417026 2953 ns/op 1576 B/op 38 allocs/op +BenchmarkMsgpack_Sample_Deserialize-12 418338 2919 ns/op 1576 B/op 38 allocs/op +BenchmarkFory_SampleList_Serialize-12 599605 2041 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 582406 2046 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 563160 2029 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 607074 2067 ns/op 0 B/op 0 allocs/op +BenchmarkFory_SampleList_Serialize-12 613364 2018 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 145861 8291 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 150979 7938 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 144135 8049 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 149202 8469 ns/op 14816 B/op 23 allocs/op +BenchmarkProtobuf_SampleList_Serialize-12 152338 7927 ns/op 14816 B/op 23 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43825 27705 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 43051 29707 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42046 27699 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42596 37501 ns/op 32793 B/op 11 allocs/op +BenchmarkMsgpack_SampleList_Serialize-12 42518 27909 ns/op 32793 B/op 11 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249300 4817 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 245127 5156 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 247790 4943 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 249534 4934 ns/op 13952 B/op 163 allocs/op +BenchmarkFory_SampleList_Deserialize-12 239244 5126 ns/op 13952 B/op 163 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 111327 10797 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 109178 10919 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 92876 12674 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 108414 10816 ns/op 20872 B/op 188 allocs/op +BenchmarkProtobuf_SampleList_Deserialize-12 114196 10460 ns/op 20872 B/op 188 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20566 58400 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20526 65792 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20550 58213 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 19180 58170 ns/op 37251 B/op 727 allocs/op +BenchmarkMsgpack_SampleList_Deserialize-12 20426 60118 ns/op 37251 B/op 727 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4481811 268.2 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4470292 269.4 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4524715 269.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4516789 270.0 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContent_Serialize-12 4391221 267.3 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2135143 544.6 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2160157 561.0 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2221641 545.3 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2214582 565.7 ns/op 1144 B/op 11 allocs/op +BenchmarkProtobuf_MediaContent_Serialize-12 2115079 571.6 ns/op 1144 B/op 11 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380163 869.0 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1380481 874.4 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1369536 873.7 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1376877 881.8 ns/op 1168 B/op 6 allocs/op +BenchmarkMsgpack_MediaContent_Serialize-12 1381296 875.1 ns/op 1168 B/op 6 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2537758 474.9 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2491048 478.2 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2500486 520.6 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2419058 495.7 ns/op 656 B/op 13 allocs/op +BenchmarkFory_MediaContent_Deserialize-12 2404918 492.4 ns/op 656 B/op 13 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1852849 647.5 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1865282 638.1 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1873513 649.3 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1853527 635.6 ns/op 1088 B/op 21 allocs/op +BenchmarkProtobuf_MediaContent_Deserialize-12 1729706 815.8 ns/op 1088 B/op 21 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 801156 1553 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 787426 1520 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 750510 1538 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 803448 1543 ns/op 896 B/op 17 allocs/op +BenchmarkMsgpack_MediaContent_Deserialize-12 780190 1549 ns/op 896 B/op 17 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304236 4158 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 300002 4008 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 301590 4026 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 304356 3955 ns/op 0 B/op 0 allocs/op +BenchmarkFory_MediaContentList_Serialize-12 305937 4006 ns/op 0 B/op 0 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 113460 10919 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 105204 12060 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112503 10890 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 110593 10468 ns/op 22848 B/op 203 allocs/op +BenchmarkProtobuf_MediaContentList_Serialize-12 112423 10939 ns/op 22848 B/op 203 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77541 15641 ns/op 16881 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77178 16293 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 73228 16168 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 71780 16275 ns/op 16880 B/op 30 allocs/op +BenchmarkMsgpack_MediaContentList_Serialize-12 77145 17553 ns/op 16881 B/op 30 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 146653 8254 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 145420 8133 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 148918 8145 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 141998 8498 ns/op 13040 B/op 243 allocs/op +BenchmarkFory_MediaContentList_Deserialize-12 138174 8893 ns/op 13040 B/op 243 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 90056 13521 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 82902 13638 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91242 14083 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 85706 13089 ns/op 25400 B/op 428 allocs/op +BenchmarkProtobuf_MediaContentList_Deserialize-12 91342 13588 ns/op 25400 B/op 428 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 33295 31198 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 40077 31423 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39219 30604 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39799 30377 ns/op 20058 B/op 307 allocs/op +BenchmarkMsgpack_MediaContentList_Deserialize-12 39267 32439 ns/op 20058 B/op 307 allocs/op PASS -ok github.com/apache/fory/benchmarks/go 256.707s +ok github.com/apache/fory/benchmarks/go 268.327s diff --git a/docs/benchmarks/go/benchmark_sample.png b/docs/benchmarks/go/benchmark_sample.png deleted file mode 100644 index 027504a9d0..0000000000 Binary files a/docs/benchmarks/go/benchmark_sample.png and /dev/null differ diff --git a/docs/benchmarks/go/benchmark_samplelist.png b/docs/benchmarks/go/benchmark_samplelist.png deleted file mode 100644 index 48b6e478d3..0000000000 Binary files a/docs/benchmarks/go/benchmark_samplelist.png and /dev/null differ diff --git a/docs/benchmarks/go/benchmark_struct.png b/docs/benchmarks/go/benchmark_struct.png deleted file mode 100644 index 690c5c3ebf..0000000000 Binary files a/docs/benchmarks/go/benchmark_struct.png and /dev/null differ diff --git a/docs/benchmarks/go/benchmark_structlist.png b/docs/benchmarks/go/benchmark_structlist.png deleted file mode 100644 index 80b240588f..0000000000 Binary files a/docs/benchmarks/go/benchmark_structlist.png and /dev/null differ diff --git a/docs/benchmarks/go/throughput.png b/docs/benchmarks/go/throughput.png index f7104d7d9c..6cc809d476 100644 Binary files a/docs/benchmarks/go/throughput.png and b/docs/benchmarks/go/throughput.png differ diff --git a/docs/benchmarks/java/README.md b/docs/benchmarks/java/README.md index f3c6badb31..faed233646 100644 --- a/docs/benchmarks/java/README.md +++ b/docs/benchmarks/java/README.md @@ -240,28 +240,28 @@ But if you serialize data between processes on same node and use shared-memory, ## Xlang Benchmark -Run from `benchmarks/java/run.sh`. Raw JMH JSON stays under the ignored local `benchmarks/java/reports/` directory; only `throughput.png` is copied into `docs/benchmarks/java/`. +Run from `benchmarks/java/run.sh`. Raw JMH JSON stays under the ignored local `benchmarks/java/reports/` directory; `throughput.png` and this xlang section are synced into `docs/benchmarks/java/`. ```bash cd benchmarks/java -./run.sh --output-dir ../../docs/benchmarks/java +./run.sh ``` JMH parameters: `-f 1 -wi 3 -i 3 -t 1 -w 3s -r 3s -bm thrpt -tu s`. Higher throughput is better. ![Java Xlang Serialization Throughput](throughput.png) -| Data type | Operation | Fory ops/sec | Protobuf ops/sec | Flatbuffer ops/sec | Fastest | -| ----------------- | ----------- | ------------ | ---------------- | ------------------ | ---------- | -| NumericStruct | Serialize | 35,509,283 | 16,293,680 | 9,252,939 | Fory | -| NumericStruct | Deserialize | 39,366,466 | 13,216,969 | 44,655,865 | Flatbuffer | -| Sample | Serialize | 18,210,799 | 2,004,689 | 2,472,044 | Fory | -| Sample | Deserialize | 18,342,162 | 1,869,185 | 4,061,787 | Fory | -| MediaContent | Serialize | 10,637,892 | 3,270,255 | 1,387,937 | Fory | -| MediaContent | Deserialize | 9,091,945 | 2,512,194 | 3,548,934 | Fory | -| NumericStructList | Serialize | 11,321,689 | 2,617,864 | 3,309,333 | Fory | -| NumericStructList | Deserialize | 12,088,544 | 2,091,938 | 7,478,377 | Fory | -| SampleList | Serialize | 4,458,489 | 403,819 | 638,011 | Fory | -| SampleList | Deserialize | 2,335,308 | 360,378 | 760,997 | Fory | -| MediaContentList | Serialize | 1,795,904 | 351,794 | 250,981 | Fory | -| MediaContentList | Deserialize | 989,372 | 453,614 | 510,958 | Fory | +| Data type | Operation | Fory ops/sec | Protobuf ops/sec | Flatbuffer ops/sec | Fastest | +| ----------------- | ----------- | ------------ | ---------------- | ------------------ | ------- | +| NumericStruct | Serialize | 46,787,647 | 33,024,161 | 9,612,018 | Fory | +| NumericStruct | Deserialize | 71,683,707 | 29,837,931 | 40,514,436 | Fory | +| Sample | Serialize | 17,406,902 | 2,071,963 | 3,153,672 | Fory | +| Sample | Deserialize | 17,772,123 | 1,867,967 | 4,179,494 | Fory | +| MediaContent | Serialize | 10,783,325 | 1,781,338 | 1,444,737 | Fory | +| MediaContent | Deserialize | 7,950,203 | 2,184,597 | 3,453,985 | Fory | +| NumericStructList | Serialize | 21,263,673 | 2,511,081 | 3,047,836 | Fory | +| NumericStructList | Deserialize | 19,249,877 | 2,067,204 | 8,168,569 | Fory | +| SampleList | Serialize | 4,580,165 | 401,280 | 696,268 | Fory | +| SampleList | Deserialize | 3,811,985 | 344,945 | 773,625 | Fory | +| MediaContentList | Serialize | 1,657,717 | 353,717 | 296,868 | Fory | +| MediaContentList | Deserialize | 1,111,043 | 435,956 | 516,192 | Fory | diff --git a/docs/benchmarks/java/java_heap_deserialize_compatible.png b/docs/benchmarks/java/java_heap_deserialize_compatible.png index 1f65fe0f7d..52dd292a3c 100644 Binary files a/docs/benchmarks/java/java_heap_deserialize_compatible.png and b/docs/benchmarks/java/java_heap_deserialize_compatible.png differ diff --git a/docs/benchmarks/java/java_heap_deserialize_consistent.png b/docs/benchmarks/java/java_heap_deserialize_consistent.png index 4155f8b624..a21445421a 100644 Binary files a/docs/benchmarks/java/java_heap_deserialize_consistent.png and b/docs/benchmarks/java/java_heap_deserialize_consistent.png differ diff --git a/docs/benchmarks/java/java_heap_serialize_compatible.png b/docs/benchmarks/java/java_heap_serialize_compatible.png index edc19b6e00..2142dfdbab 100644 Binary files a/docs/benchmarks/java/java_heap_serialize_compatible.png and b/docs/benchmarks/java/java_heap_serialize_compatible.png differ diff --git a/docs/benchmarks/java/java_heap_serialize_consistent.png b/docs/benchmarks/java/java_heap_serialize_consistent.png index 5d88e2e96f..5703f72473 100644 Binary files a/docs/benchmarks/java/java_heap_serialize_consistent.png and b/docs/benchmarks/java/java_heap_serialize_consistent.png differ diff --git a/docs/benchmarks/java/java_offheap_deserialize_compatible.png b/docs/benchmarks/java/java_offheap_deserialize_compatible.png index 386091a217..ce77bf6fb7 100644 Binary files a/docs/benchmarks/java/java_offheap_deserialize_compatible.png and b/docs/benchmarks/java/java_offheap_deserialize_compatible.png differ diff --git a/docs/benchmarks/java/java_offheap_deserialize_consistent.png b/docs/benchmarks/java/java_offheap_deserialize_consistent.png index 079ec78cae..04c3e3d04a 100644 Binary files a/docs/benchmarks/java/java_offheap_deserialize_consistent.png and b/docs/benchmarks/java/java_offheap_deserialize_consistent.png differ diff --git a/docs/benchmarks/java/java_offheap_serialize_compatible.png b/docs/benchmarks/java/java_offheap_serialize_compatible.png index 7e83e3efb7..8d9197121c 100644 Binary files a/docs/benchmarks/java/java_offheap_serialize_compatible.png and b/docs/benchmarks/java/java_offheap_serialize_compatible.png differ diff --git a/docs/benchmarks/java/java_offheap_serialize_consistent.png b/docs/benchmarks/java/java_offheap_serialize_consistent.png index 49f6602f86..8bdea8f559 100644 Binary files a/docs/benchmarks/java/java_offheap_serialize_consistent.png and b/docs/benchmarks/java/java_offheap_serialize_consistent.png differ diff --git a/docs/benchmarks/java/java_repo_deserialization_throughput.png b/docs/benchmarks/java/java_repo_deserialization_throughput.png index 9ea114a36f..d64f5e77a1 100644 Binary files a/docs/benchmarks/java/java_repo_deserialization_throughput.png and b/docs/benchmarks/java/java_repo_deserialization_throughput.png differ diff --git a/docs/benchmarks/java/java_repo_serialization_throughput.png b/docs/benchmarks/java/java_repo_serialization_throughput.png index 83edd359c9..8f9824ffe1 100644 Binary files a/docs/benchmarks/java/java_repo_serialization_throughput.png and b/docs/benchmarks/java/java_repo_serialization_throughput.png differ diff --git a/docs/benchmarks/java/java_zero_copy_deserialize.png b/docs/benchmarks/java/java_zero_copy_deserialize.png index da83fefcd3..a91315eb34 100644 Binary files a/docs/benchmarks/java/java_zero_copy_deserialize.png and b/docs/benchmarks/java/java_zero_copy_deserialize.png differ diff --git a/docs/benchmarks/java/java_zero_copy_serialize.png b/docs/benchmarks/java/java_zero_copy_serialize.png index ea719dc9bf..a3f1b98346 100644 Binary files a/docs/benchmarks/java/java_zero_copy_serialize.png and b/docs/benchmarks/java/java_zero_copy_serialize.png differ diff --git a/docs/benchmarks/java/throughput.png b/docs/benchmarks/java/throughput.png index b49d9721b1..f0aad29a7d 100644 Binary files a/docs/benchmarks/java/throughput.png and b/docs/benchmarks/java/throughput.png differ diff --git a/docs/benchmarks/javascript/README.md b/docs/benchmarks/javascript/README.md index bb0691efe9..5846d44a18 100644 --- a/docs/benchmarks/javascript/README.md +++ b/docs/benchmarks/javascript/README.md @@ -1,6 +1,6 @@ # JavaScript Benchmark Performance Report -_Generated on 2026-05-08 11:29:18_ +_Generated on 2026-05-08 17:27:11_ ## How to Generate This Report @@ -13,6 +13,12 @@ cd benchmarks/javascript The timed serializer loops use serializer-native typed values. Fory receives the pre-normalized Fory value used by its schema, protobuf receives the prebuilt protobuf-shaped value, and JSON receives the benchmark JavaScript object. Protobuf timings do not include `toProto`, `fromProto`, `protobufjs.create`, or `toObject` conversion work. +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + ## Hardware & OS Info | Key | Value | @@ -23,78 +29,46 @@ The timed serializer loops use serializer-native typed values. Fory receives the | CPU Cores (Physical) | 12 | | CPU Cores (Logical) | 12 | | Total RAM (GB) | 48.0 | -| Benchmark Date | 2026-05-08T03:27:27.670Z | +| Benchmark Date | 2026-05-08T08:07:36.073Z | | CPU Cores (from benchmark) | 12 | -| Node.js | v25.8.1 | -| V8 | 14.1.146.11-node.21 | - -## Benchmark Plots - -All class-level plots below show throughput (ops/sec). - -### Throughput - -![Throughput](throughput.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) - -### Sample - -![Sample](sample.png) - -### SampleList - -![SampleList](samplelist.png) - -### NumericStruct - -![NumericStruct](struct.png) - -### NumericStructList - -![NumericStructList](structlist.png) +| Node.js | v22.20.0 | +| V8 | 12.4.254.21-node.33 | ## Benchmark Results ### Timing Results (nanoseconds) -| Datatype | Operation | fory (ns) | protobuf (ns) | json (ns) | Fastest | -| ----------------- | ----------- | --------- | ------------- | --------- | ------- | -| NumericStruct | Serialize | 77.8 | 447.6 | 121.8 | fory | -| NumericStruct | Deserialize | 46.4 | 83.2 | 321.6 | fory | -| Sample | Serialize | 270.9 | 1789.4 | 576.2 | fory | -| Sample | Deserialize | 528.3 | 824.3 | 1315.1 | fory | -| MediaContent | Serialize | 459.4 | 1324.7 | 566.5 | fory | -| MediaContent | Deserialize | 544.6 | 661.9 | 1194.3 | fory | -| NumericStructList | Serialize | 199.5 | 2407.5 | 606.6 | fory | -| NumericStructList | Deserialize | 143.7 | 552.3 | 1481.9 | fory | -| SampleList | Serialize | 1171.2 | 8526.9 | 2959.5 | fory | -| SampleList | Deserialize | 2467.5 | 4208.7 | 5973.5 | fory | -| MediaContentList | Serialize | 2164.1 | 6332.4 | 2593.9 | fory | -| MediaContentList | Deserialize | 2487.4 | 3462.5 | 5877.0 | fory | +| Datatype | Operation | fory (ns) | protobuf (ns) | json (ns) | Fastest | +| ----------------- | ----------- | --------- | ------------- | --------- | -------- | +| NumericStruct | Serialize | 76.0 | 613.0 | 496.0 | fory | +| NumericStruct | Deserialize | 56.9 | 94.8 | 333.0 | fory | +| Sample | Serialize | 318.0 | 2016.6 | 1409.3 | fory | +| Sample | Deserialize | 496.0 | 902.5 | 1609.6 | fory | +| MediaContent | Serialize | 494.1 | 1358.5 | 803.5 | fory | +| MediaContent | Deserialize | 539.3 | 628.3 | 1134.3 | fory | +| NumericStructList | Serialize | 195.3 | 3019.3 | 2013.5 | fory | +| NumericStructList | Deserialize | 183.7 | 606.9 | 1944.0 | fory | +| SampleList | Serialize | 1681.9 | 19346.7 | 11870.3 | fory | +| SampleList | Deserialize | 2571.9 | 5730.6 | 9074.5 | fory | +| MediaContentList | Serialize | 2785.9 | 7616.6 | 3611.5 | fory | +| MediaContentList | Deserialize | 3709.7 | 3018.6 | 5294.5 | protobuf | ### Throughput Results (ops/sec) -| Datatype | Operation | fory TPS | protobuf TPS | json TPS | Fastest | -| ----------------- | ----------- | ---------- | ------------ | --------- | ------- | -| NumericStruct | Serialize | 12,849,851 | 2,234,166 | 8,208,279 | fory | -| NumericStruct | Deserialize | 21,547,808 | 12,017,233 | 3,109,551 | fory | -| Sample | Serialize | 3,690,917 | 558,841 | 1,735,442 | fory | -| Sample | Deserialize | 1,892,771 | 1,213,083 | 760,401 | fory | -| MediaContent | Serialize | 2,176,977 | 754,884 | 1,765,218 | fory | -| MediaContent | Deserialize | 1,836,161 | 1,510,915 | 837,278 | fory | -| NumericStructList | Serialize | 5,013,481 | 415,368 | 1,648,437 | fory | -| NumericStructList | Deserialize | 6,958,482 | 1,810,451 | 674,822 | fory | -| SampleList | Serialize | 853,833 | 117,276 | 337,895 | fory | -| SampleList | Deserialize | 405,267 | 237,605 | 167,406 | fory | -| MediaContentList | Serialize | 462,077 | 157,919 | 385,519 | fory | -| MediaContentList | Deserialize | 402,028 | 288,809 | 170,156 | fory | +| Datatype | Operation | fory TPS | protobuf TPS | json TPS | Fastest | +| ----------------- | ----------- | ---------- | ------------ | --------- | -------- | +| NumericStruct | Serialize | 13,162,466 | 1,631,271 | 2,016,097 | fory | +| NumericStruct | Deserialize | 17,568,418 | 10,543,763 | 3,002,971 | fory | +| Sample | Serialize | 3,144,194 | 495,893 | 709,593 | fory | +| Sample | Deserialize | 2,015,942 | 1,108,010 | 621,285 | fory | +| MediaContent | Serialize | 2,023,719 | 736,097 | 1,244,512 | fory | +| MediaContent | Deserialize | 1,854,348 | 1,591,617 | 881,572 | fory | +| NumericStructList | Serialize | 5,121,376 | 331,201 | 496,645 | fory | +| NumericStructList | Deserialize | 5,444,504 | 1,647,728 | 514,414 | fory | +| SampleList | Serialize | 594,551 | 51,688 | 84,244 | fory | +| SampleList | Deserialize | 388,820 | 174,503 | 110,199 | fory | +| MediaContentList | Serialize | 358,954 | 131,293 | 276,891 | fory | +| MediaContentList | Deserialize | 269,561 | 331,275 | 188,876 | protobuf | ### Serialized Data Sizes (bytes) diff --git a/docs/benchmarks/javascript/mediacontent.png b/docs/benchmarks/javascript/mediacontent.png deleted file mode 100644 index 80cec1bd83..0000000000 Binary files a/docs/benchmarks/javascript/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/javascript/mediacontentlist.png b/docs/benchmarks/javascript/mediacontentlist.png deleted file mode 100644 index 9c106b94fc..0000000000 Binary files a/docs/benchmarks/javascript/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/javascript/sample.png b/docs/benchmarks/javascript/sample.png deleted file mode 100644 index b70f5c0019..0000000000 Binary files a/docs/benchmarks/javascript/sample.png and /dev/null differ diff --git a/docs/benchmarks/javascript/samplelist.png b/docs/benchmarks/javascript/samplelist.png deleted file mode 100644 index a45c3445d1..0000000000 Binary files a/docs/benchmarks/javascript/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/javascript/struct.png b/docs/benchmarks/javascript/struct.png deleted file mode 100644 index 69f6db9ba5..0000000000 Binary files a/docs/benchmarks/javascript/struct.png and /dev/null differ diff --git a/docs/benchmarks/javascript/structlist.png b/docs/benchmarks/javascript/structlist.png deleted file mode 100644 index a794c866d9..0000000000 Binary files a/docs/benchmarks/javascript/structlist.png and /dev/null differ diff --git a/docs/benchmarks/javascript/throughput.png b/docs/benchmarks/javascript/throughput.png index cdd5ac4928..0d974d32ca 100644 Binary files a/docs/benchmarks/javascript/throughput.png and b/docs/benchmarks/javascript/throughput.png differ diff --git a/docs/benchmarks/python/README.md b/docs/benchmarks/python/README.md index 544b417069..45a2bdbcd5 100644 --- a/docs/benchmarks/python/README.md +++ b/docs/benchmarks/python/README.md @@ -1,6 +1,6 @@ # Python Benchmark Performance Report -_Generated on 2026-05-08 11:35:12_ +_Generated on 2026-05-08 17:26:51_ ## How to Generate This Report @@ -9,6 +9,12 @@ cd benchmarks/python ./run.sh ``` +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + ## Hardware & OS Info | Key | Value | @@ -33,73 +39,41 @@ cd benchmarks/python | number | 1000 | | list_size | 5 | -## Benchmark Plots - -All plots show throughput (ops/sec); higher is better. - -### Throughput - -![Throughput](throughput.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) - -### Sample - -![Sample](sample.png) - -### SampleList - -![SampleList](samplelist.png) - -### NumericStruct - -![NumericStruct](struct.png) - -### NumericStructList - -![NumericStructList](structlist.png) - ## Benchmark Results ### Timing Results (nanoseconds) | Datatype | Operation | fory (ns) | protobuf (ns) | pickle (ns) | Fastest | | ----------------- | ----------- | --------- | ------------- | ----------- | ------- | -| NumericStruct | Serialize | 517.0 | 873.6 | 1038.7 | fory | -| NumericStruct | Deserialize | 504.6 | 1151.9 | 1280.5 | fory | -| Sample | Serialize | 924.4 | 3204.0 | 11322.5 | fory | -| Sample | Deserialize | 2891.0 | 11464.7 | 7169.7 | fory | -| MediaContent | Serialize | 1140.5 | 3460.1 | 5074.0 | fory | -| MediaContent | Deserialize | 1509.3 | 4476.4 | 4357.6 | fory | -| NumericStructList | Serialize | 1052.5 | 4606.0 | 3064.1 | fory | -| NumericStructList | Deserialize | 1635.3 | 5882.8 | 3945.2 | fory | -| SampleList | Serialize | 3680.0 | 18709.4 | 33800.9 | fory | -| SampleList | Deserialize | 13702.0 | 35818.0 | 23745.4 | fory | -| MediaContentList | Serialize | 3262.0 | 18170.7 | 11015.9 | fory | -| MediaContentList | Deserialize | 6267.0 | 22423.2 | 13695.5 | fory | +| NumericStruct | Serialize | 491.4 | 802.3 | 1119.8 | fory | +| NumericStruct | Deserialize | 522.2 | 1211.6 | 1788.7 | fory | +| Sample | Serialize | 1096.4 | 3315.8 | 10185.2 | fory | +| Sample | Deserialize | 2772.0 | 6659.7 | 7061.9 | fory | +| MediaContent | Serialize | 989.2 | 3433.2 | 4392.7 | fory | +| MediaContent | Deserialize | 1518.7 | 4381.2 | 4305.1 | fory | +| NumericStructList | Serialize | 1111.2 | 4707.9 | 3235.8 | fory | +| NumericStructList | Deserialize | 1891.7 | 6891.0 | 3974.9 | fory | +| SampleList | Serialize | 3447.2 | 18719.1 | 32125.7 | fory | +| SampleList | Deserialize | 13131.6 | 35264.2 | 24154.4 | fory | +| MediaContentList | Serialize | 2996.5 | 17597.4 | 11087.8 | fory | +| MediaContentList | Deserialize | 6228.7 | 21562.0 | 10459.3 | fory | ### Throughput Results (ops/sec) | Datatype | Operation | fory TPS | protobuf TPS | pickle TPS | Fastest | | ----------------- | ----------- | --------- | ------------ | ---------- | ------- | -| NumericStruct | Serialize | 1,934,203 | 1,144,735 | 962,697 | fory | -| NumericStruct | Deserialize | 1,981,615 | 868,166 | 780,935 | fory | -| Sample | Serialize | 1,081,792 | 312,107 | 88,320 | fory | -| Sample | Deserialize | 345,897 | 87,224 | 139,476 | fory | -| MediaContent | Serialize | 876,796 | 289,005 | 197,082 | fory | -| MediaContent | Deserialize | 662,558 | 223,395 | 229,483 | fory | -| NumericStructList | Serialize | 950,145 | 217,108 | 326,363 | fory | -| NumericStructList | Deserialize | 611,499 | 169,986 | 253,472 | fory | -| SampleList | Serialize | 271,739 | 53,449 | 29,585 | fory | -| SampleList | Deserialize | 72,982 | 27,919 | 42,114 | fory | -| MediaContentList | Serialize | 306,557 | 55,034 | 90,778 | fory | -| MediaContentList | Deserialize | 159,565 | 44,597 | 73,017 | fory | +| NumericStruct | Serialize | 2,035,025 | 1,246,379 | 893,009 | fory | +| NumericStruct | Deserialize | 1,915,112 | 825,344 | 559,055 | fory | +| Sample | Serialize | 912,072 | 301,590 | 98,182 | fory | +| Sample | Deserialize | 360,751 | 150,158 | 141,605 | fory | +| MediaContent | Serialize | 1,010,939 | 291,275 | 227,652 | fory | +| MediaContent | Deserialize | 658,462 | 228,247 | 232,281 | fory | +| NumericStructList | Serialize | 899,960 | 212,407 | 309,040 | fory | +| NumericStructList | Deserialize | 528,636 | 145,116 | 251,580 | fory | +| SampleList | Serialize | 290,092 | 53,421 | 31,128 | fory | +| SampleList | Deserialize | 76,152 | 28,357 | 41,400 | fory | +| MediaContentList | Serialize | 333,720 | 56,826 | 90,189 | fory | +| MediaContentList | Deserialize | 160,547 | 46,378 | 95,609 | fory | ### Serialized Data Sizes (bytes) diff --git a/docs/benchmarks/python/mediacontent.png b/docs/benchmarks/python/mediacontent.png deleted file mode 100644 index ef9eca86c3..0000000000 Binary files a/docs/benchmarks/python/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/python/mediacontentlist.png b/docs/benchmarks/python/mediacontentlist.png deleted file mode 100644 index 82ca2d9b98..0000000000 Binary files a/docs/benchmarks/python/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/python/sample.png b/docs/benchmarks/python/sample.png deleted file mode 100644 index 1324ca10c7..0000000000 Binary files a/docs/benchmarks/python/sample.png and /dev/null differ diff --git a/docs/benchmarks/python/samplelist.png b/docs/benchmarks/python/samplelist.png deleted file mode 100644 index f1fa8b0290..0000000000 Binary files a/docs/benchmarks/python/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/python/struct.png b/docs/benchmarks/python/struct.png deleted file mode 100644 index 01d1f3f6d2..0000000000 Binary files a/docs/benchmarks/python/struct.png and /dev/null differ diff --git a/docs/benchmarks/python/structlist.png b/docs/benchmarks/python/structlist.png deleted file mode 100644 index 1c8509a8ce..0000000000 Binary files a/docs/benchmarks/python/structlist.png and /dev/null differ diff --git a/docs/benchmarks/python/throughput.png b/docs/benchmarks/python/throughput.png index d0b551b556..f7a6dd68ff 100644 Binary files a/docs/benchmarks/python/throughput.png and b/docs/benchmarks/python/throughput.png differ diff --git a/docs/benchmarks/rust/README.md b/docs/benchmarks/rust/README.md index e7ab4d5ec5..6ee463f661 100644 --- a/docs/benchmarks/rust/README.md +++ b/docs/benchmarks/rust/README.md @@ -1,6 +1,6 @@ # Rust Benchmark Performance Report -_Generated on 2026-05-08 03:28:21_ +_Generated on 2026-05-08 17:27:11_ ## How to Generate This Report @@ -11,6 +11,12 @@ cargo run --release --bin fory_profiler -- --print-all-serialized-sizes | tee re python benchmark_report.py --log-file results/cargo_bench.log --size-file results/serialized_sizes.txt --output-dir results ``` +## Benchmark Plot + +The plot shows throughput (ops/sec); higher is better. + +![Throughput](throughput.png) + ## Hardware & OS Info | Key | Value | @@ -21,39 +27,7 @@ python benchmark_report.py --log-file results/cargo_bench.log --size-file result | CPU Cores (Physical) | 12 | | CPU Cores (Logical) | 12 | | Total RAM (GB) | 48.0 | -| Benchmark Date | 2026-05-08T03:28:19 | - -## Benchmark Plots - -All class-level plots below show throughput (ops/sec). - -### Throughput - -![Throughput](throughput.png) - -### MediaContent - -![MediaContent](mediacontent.png) - -### MediaContentList - -![MediaContentList](mediacontentlist.png) - -### Sample - -![Sample](sample.png) - -### SampleList - -![SampleList](samplelist.png) - -### NumericStruct - -![NumericStruct](struct.png) - -### NumericStructList - -![NumericStructList](structlist.png) +| Benchmark Date | 2026-05-08T16:47:49 | ## Benchmark Results @@ -61,35 +35,35 @@ All class-level plots below show throughput (ops/sec). | Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) | Fastest | | ----------------- | ----------- | --------- | ------------- | ------------ | ------- | -| NumericStruct | Serialize | 37.4 | 94.5 | 230.1 | fory | -| NumericStruct | Deserialize | 32.2 | 66.1 | 106.3 | fory | -| Sample | Serialize | 97.3 | 545.7 | 629.1 | fory | -| Sample | Deserialize | 349.1 | 894.3 | 789.1 | fory | -| MediaContent | Serialize | 121.2 | 548.3 | 447.5 | fory | -| MediaContent | Deserialize | 548.6 | 692.2 | 899.3 | fory | -| NumericStructList | Serialize | 120.6 | 499.9 | 631.8 | fory | -| NumericStructList | Deserialize | 124.4 | 419.7 | 598.6 | fory | -| SampleList | Serialize | 270.3 | 2895.9 | 1940.5 | fory | -| SampleList | Deserialize | 1650.2 | 4422.7 | 4102.0 | fory | -| MediaContentList | Serialize | 371.5 | 2788.5 | 1407.5 | fory | -| MediaContentList | Deserialize | 2679.7 | 3609.2 | 4762.7 | fory | +| NumericStruct | Serialize | 38.1 | 94.6 | 239.5 | fory | +| NumericStruct | Deserialize | 32.6 | 62.4 | 107.3 | fory | +| Sample | Serialize | 95.3 | 591.8 | 601.1 | fory | +| Sample | Deserialize | 410.1 | 925.8 | 805.9 | fory | +| MediaContent | Serialize | 120.0 | 553.9 | 446.9 | fory | +| MediaContent | Deserialize | 566.7 | 713.0 | 902.6 | fory | +| NumericStructList | Serialize | 121.5 | 512.0 | 618.0 | fory | +| NumericStructList | Deserialize | 137.9 | 404.9 | 615.9 | fory | +| SampleList | Serialize | 267.7 | 2920.2 | 2011.1 | fory | +| SampleList | Deserialize | 1831.9 | 4636.4 | 4141.4 | fory | +| MediaContentList | Serialize | 367.1 | 2835.6 | 1441.7 | fory | +| MediaContentList | Deserialize | 2703.8 | 3622.3 | 4832.3 | fory | ### Throughput Results (ops/sec) | Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest | | ----------------- | ----------- | ---------- | ------------ | ----------- | ------- | -| NumericStruct | Serialize | 26,727,248 | 10,577,086 | 4,346,503 | fory | -| NumericStruct | Deserialize | 31,097,428 | 15,129,280 | 9,410,879 | fory | -| Sample | Serialize | 10,275,803 | 1,832,475 | 1,589,648 | fory | -| Sample | Deserialize | 2,864,919 | 1,118,243 | 1,267,315 | fory | -| MediaContent | Serialize | 8,249,464 | 1,823,753 | 2,234,637 | fory | -| MediaContent | Deserialize | 1,822,722 | 1,444,627 | 1,112,013 | fory | -| NumericStructList | Serialize | 8,293,937 | 2,000,400 | 1,582,779 | fory | -| NumericStructList | Deserialize | 8,038,585 | 2,382,768 | 1,670,425 | fory | -| SampleList | Serialize | 3,700,004 | 345,316 | 515,331 | fory | -| SampleList | Deserialize | 605,987 | 226,106 | 243,784 | fory | -| MediaContentList | Serialize | 2,691,573 | 358,616 | 710,480 | fory | -| MediaContentList | Deserialize | 373,176 | 277,070 | 209,965 | fory | +| NumericStruct | Serialize | 26,237,767 | 10,572,613 | 4,174,668 | fory | +| NumericStruct | Deserialize | 30,720,079 | 16,035,920 | 9,322,271 | fory | +| Sample | Serialize | 10,494,611 | 1,689,874 | 1,663,700 | fory | +| Sample | Deserialize | 2,438,311 | 1,080,170 | 1,240,895 | fory | +| MediaContent | Serialize | 8,331,945 | 1,805,445 | 2,237,687 | fory | +| MediaContent | Deserialize | 1,764,633 | 1,402,426 | 1,107,960 | fory | +| NumericStructList | Serialize | 8,232,485 | 1,953,125 | 1,618,071 | fory | +| NumericStructList | Deserialize | 7,250,580 | 2,469,563 | 1,623,535 | fory | +| SampleList | Serialize | 3,735,664 | 342,442 | 497,240 | fory | +| SampleList | Deserialize | 545,881 | 215,685 | 241,464 | fory | +| MediaContentList | Serialize | 2,724,350 | 352,659 | 693,626 | fory | +| MediaContentList | Deserialize | 369,850 | 276,068 | 206,941 | fory | ### Serialized Data Sizes (bytes) diff --git a/docs/benchmarks/rust/mediacontent.png b/docs/benchmarks/rust/mediacontent.png deleted file mode 100644 index a6d1848a4b..0000000000 Binary files a/docs/benchmarks/rust/mediacontent.png and /dev/null differ diff --git a/docs/benchmarks/rust/mediacontentlist.png b/docs/benchmarks/rust/mediacontentlist.png deleted file mode 100644 index 2bf6131504..0000000000 Binary files a/docs/benchmarks/rust/mediacontentlist.png and /dev/null differ diff --git a/docs/benchmarks/rust/sample.png b/docs/benchmarks/rust/sample.png deleted file mode 100644 index c6794ef7e3..0000000000 Binary files a/docs/benchmarks/rust/sample.png and /dev/null differ diff --git a/docs/benchmarks/rust/samplelist.png b/docs/benchmarks/rust/samplelist.png deleted file mode 100644 index 3d5368a548..0000000000 Binary files a/docs/benchmarks/rust/samplelist.png and /dev/null differ diff --git a/docs/benchmarks/rust/struct.png b/docs/benchmarks/rust/struct.png deleted file mode 100644 index c868cc0cf8..0000000000 Binary files a/docs/benchmarks/rust/struct.png and /dev/null differ diff --git a/docs/benchmarks/rust/structlist.png b/docs/benchmarks/rust/structlist.png deleted file mode 100644 index 725ea4f7da..0000000000 Binary files a/docs/benchmarks/rust/structlist.png and /dev/null differ diff --git a/docs/benchmarks/rust/throughput.png b/docs/benchmarks/rust/throughput.png index 659b2753a6..dbb792da9c 100644 Binary files a/docs/benchmarks/rust/throughput.png and b/docs/benchmarks/rust/throughput.png differ diff --git a/docs/benchmarks/swift/README.md b/docs/benchmarks/swift/README.md index 5c70c5ab19..ffb824b12a 100644 --- a/docs/benchmarks/swift/README.md +++ b/docs/benchmarks/swift/README.md @@ -1,12 +1,16 @@ # Fory Swift Benchmark -This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and MessagePack in Swift. +This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and JSON in Swift. + +## Throughput Plot + +![Throughput](throughput.png) ## Hardware and Runtime Info | Key | Value | | --------------------- | ----------------------------- | -| Timestamp | 2026-05-07T19:46:19Z | +| Timestamp | 2026-05-08T09:05:32Z | | OS | Version 15.7.2 (Build 24G325) | | Host | macbook-pro.local | | CPU Cores (Logical) | 12 | @@ -15,30 +19,28 @@ This benchmark compares serialization and deserialization throughput for Apache ## Throughput Results -![Throughput](throughput.png) - -| Datatype | Operation | Fory TPS | Protobuf TPS | Msgpack TPS | Fastest | -| ----------------- | ----------- | ---------: | -----------: | ----------: | ------------ | -| NumericStruct | Serialize | 9,456,190 | 6,237,003 | 99,134 | fory (1.52x) | -| NumericStruct | Deserialize | 11,244,151 | 6,898,201 | 68,135 | fory (1.63x) | -| Sample | Serialize | 3,653,537 | 1,269,790 | 17,033 | fory (2.88x) | -| Sample | Deserialize | 992,566 | 751,855 | 12,379 | fory (1.32x) | -| MediaContent | Serialize | 1,586,123 | 673,382 | 28,762 | fory (2.36x) | -| MediaContent | Deserialize | 606,656 | 471,433 | 12,321 | fory (1.29x) | -| NumericStructList | Serialize | 2,981,475 | 930,517 | 18,067 | fory (3.20x) | -| NumericStructList | Deserialize | 2,466,526 | 720,704 | 6,191 | fory (3.42x) | -| SampleList | Serialize | 784,804 | 205,426 | 3,356 | fory (3.82x) | -| SampleList | Deserialize | 191,930 | 132,154 | 1,452 | fory (1.45x) | -| MediaContentList | Serialize | 347,354 | 100,939 | 5,460 | fory (3.44x) | -| MediaContentList | Deserialize | 114,145 | 84,897 | 1,446 | fory (1.34x) | +| Datatype | Operation | Fory TPS | Protobuf TPS | JSON TPS | Fastest | +| ----------------- | ----------- | ---------: | -----------: | -------: | ------------ | +| NumericStruct | Serialize | 9,435,623 | 6,175,939 | 408,960 | fory (1.53x) | +| NumericStruct | Deserialize | 11,037,225 | 6,842,676 | 328,302 | fory (1.61x) | +| Sample | Serialize | 3,596,835 | 1,257,100 | 79,781 | fory (2.86x) | +| Sample | Deserialize | 982,255 | 733,588 | 41,274 | fory (1.34x) | +| MediaContent | Serialize | 1,561,376 | 609,896 | 98,677 | fory (2.56x) | +| MediaContent | Deserialize | 523,836 | 395,202 | 70,528 | fory (1.33x) | +| NumericStructList | Serialize | 2,910,846 | 918,363 | 82,965 | fory (3.17x) | +| NumericStructList | Deserialize | 2,436,636 | 701,656 | 69,353 | fory (3.47x) | +| SampleList | Serialize | 694,557 | 202,040 | 16,679 | fory (3.44x) | +| SampleList | Deserialize | 187,109 | 131,947 | 8,236 | fory (1.42x) | +| MediaContentList | Serialize | 348,238 | 98,007 | 18,698 | fory (3.55x) | +| MediaContentList | Deserialize | 104,990 | 74,422 | 16,298 | fory (1.41x) | ## Serialized Size (bytes) -| Datatype | Fory | Protobuf | Msgpack | -| ----------------- | ---: | -------: | ------: | -| NumericStruct | 78 | 93 | 100 | -| Sample | 445 | 375 | 737 | -| MediaContent | 362 | 301 | 524 | -| NumericStructList | 255 | 475 | 513 | -| SampleList | 1978 | 1890 | 3698 | -| MediaContentList | 1531 | 1520 | 2639 | +| Datatype | Fory | Protobuf | JSON | +| ----------------- | ---: | -------: | ---: | +| NumericStruct | 78 | 93 | 159 | +| Sample | 445 | 375 | 696 | +| MediaContent | 362 | 301 | 608 | +| NumericStructList | 255 | 475 | 816 | +| SampleList | 1978 | 1890 | 3501 | +| MediaContentList | 1531 | 1520 | 3067 | diff --git a/docs/benchmarks/swift/throughput.png b/docs/benchmarks/swift/throughput.png index f4a140ebfb..cbb16ba178 100644 Binary files a/docs/benchmarks/swift/throughput.png and b/docs/benchmarks/swift/throughput.png differ