This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 115ee55e9 feat(c++): add msgpack cpp benchmark (#3365)
115ee55e9 is described below
commit 115ee55e92605135522a8146dfe638aaa3e1c182
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Feb 20 14:54:54 2026 +0800
feat(c++): add msgpack cpp benchmark (#3365)
## Why?
## What does this PR do?
add msgpack cpp benchmark.
Since protobuf only support schema evolution mode, this pr use msgpack
to serialize struct into a map instead of array
## Related issues
#2906
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
<img width="4200" height="900" alt="image"
src="https://github.com/user-attachments/assets/79d303d1-e4ff-459d-b8ee-cd64ebccab1f"
/>
### Throughput Results (ops/sec)
| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest
|
|----------|-----------|----------|--------------|-------------|---------|
| MediaContent | Serialize | 8,448,220 | 1,167,763 | 3,516,463 | fory |
| MediaContent | Deserialize | 2,569,039 | 853,100 | 362,346 | fory |
| MediaContentList | Serialize | 2,135,221 | 218,387 | 723,326 | fory |
| MediaContentList | Deserialize | 499,536 | 159,693 | 74,932 | fory |
| Sample | Serialize | 14,171,197 | 11,109,574 | 3,301,701 | fory |
| Sample | Deserialize | 3,101,117 | 1,543,406 | 377,200 | fory |
| SampleList | Serialize | 3,665,359 | 210,339 | 682,267 | fory |
| SampleList | Deserialize | 595,373 | 206,990 | 77,166 | fory |
| Struct | Serialize | 37,444,357 | 31,187,829 | 17,868,627 | fory |
| Struct | Deserialize | 47,602,758 | 41,423,123 | 1,346,630 | fory |
| StructList | Serialize | 14,470,682 | 2,422,814 | 3,499,255 | fory |
| StructList | Deserialize | 7,913,226 | 2,900,435 | 295,133 | fory |
### Serialized Data Sizes (bytes)
| Datatype | fory | protobuf | msgpack |
|----------|------|----------|---------|
| Struct | 58 | 61 | 55 |
| Sample | 446 | 375 | 530 |
| MediaContent | 365 | 301 | 480 |
| StructList | 184 | 315 | 289 |
| SampleList | 1980 | 1890 | 2664 |
| MediaContentList | 1535 | 1520 | 2421 |
---
benchmarks/cpp_benchmark/CMakeLists.txt | 27 ++-
benchmarks/cpp_benchmark/benchmark.cc | 104 ++++++++++-
benchmarks/cpp_benchmark/benchmark_report.py | 248 ++++++++++++++++-----------
benchmarks/cpp_benchmark/run.sh | 70 ++++++--
docs/benchmarks/cpp/README.md | 104 ++++++++---
docs/benchmarks/cpp/mediacontent.png | Bin 51863 -> 52226 bytes
docs/benchmarks/cpp/mediacontentlist.png | Bin 54504 -> 57111 bytes
docs/benchmarks/cpp/sample.png | Bin 48714 -> 55251 bytes
docs/benchmarks/cpp/samplelist.png | Bin 51686 -> 53909 bytes
docs/benchmarks/cpp/struct.png | Bin 47309 -> 51352 bytes
docs/benchmarks/cpp/structlist.png | Bin 48428 -> 53820 bytes
docs/benchmarks/cpp/throughput.png | Bin 73763 -> 78902 bytes
12 files changed, 417 insertions(+), 136 deletions(-)
diff --git a/benchmarks/cpp_benchmark/CMakeLists.txt
b/benchmarks/cpp_benchmark/CMakeLists.txt
index 8d8b4f95f..8599a8313 100644
--- a/benchmarks/cpp_benchmark/CMakeLists.txt
+++ b/benchmarks/cpp_benchmark/CMakeLists.txt
@@ -17,9 +17,13 @@
cmake_minimum_required(VERSION 3.16)
+if(POLICY CMP0169)
+ cmake_policy(SET CMP0169 OLD)
+endif()
+
project(fory_cpp_benchmark
VERSION 0.16.0
- DESCRIPTION "C++ Benchmark comparing Fory and Protobuf serialization"
+ DESCRIPTION "C++ Benchmark comparing Fory, Protobuf, and Msgpack
serialization"
LANGUAGES CXX
)
@@ -71,6 +75,23 @@ set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(benchmark)
+# msgpack-c (header-only)
+FetchContent_Declare(
+ msgpack
+ GIT_REPOSITORY https://github.com/msgpack/msgpack-c.git
+ GIT_TAG cpp-6.1.0
+ GIT_SHALLOW TRUE
+)
+FetchContent_GetProperties(msgpack)
+if(NOT msgpack_POPULATED)
+ FetchContent_Populate(msgpack)
+endif()
+
+add_library(msgpack-cxx INTERFACE)
+target_include_directories(msgpack-cxx INTERFACE
+ ${msgpack_SOURCE_DIR}/include
+)
+
# =============================================================================
# Proto file compilation
# =============================================================================
@@ -114,12 +135,16 @@ add_executable(fory_benchmark
target_include_directories(fory_benchmark PRIVATE
${PROTO_OUT_DIR}
)
+target_compile_definitions(fory_benchmark PRIVATE
+ MSGPACK_NO_BOOST
+)
target_link_libraries(fory_benchmark PRIVATE
fory::serialization
bench_proto
benchmark::benchmark
benchmark::benchmark_main
+ msgpack-cxx
)
# Compiler optimizations for benchmarks
diff --git a/benchmarks/cpp_benchmark/benchmark.cc
b/benchmarks/cpp_benchmark/benchmark.cc
index 3cfa461c2..f1d65bff4 100644
--- a/benchmarks/cpp_benchmark/benchmark.cc
+++ b/benchmarks/cpp_benchmark/benchmark.cc
@@ -19,6 +19,7 @@
#include <benchmark/benchmark.h>
#include <cstdint>
+#include <msgpack.hpp>
#include <string>
#include <vector>
@@ -46,6 +47,7 @@ struct NumericStruct {
f4 == other.f4 && f5 == other.f5 && f6 == other.f6 &&
f7 == other.f7 && f8 == other.f8;
}
+ MSGPACK_DEFINE_MAP(f1, f2, f3, f4, f5, f6, f7, f8);
};
FORY_STRUCT(NumericStruct, f1, f2, f3, f4, f5, f6, f7, f8);
FORY_FIELD_TAGS(NumericStruct, (f1, 1), (f2, 2), (f3, 3), (f4, 4), (f5, 5),
@@ -94,6 +96,12 @@ struct Sample {
short_array == other.short_array && char_array == other.char_array
&&
boolean_array == other.boolean_array && string == other.string;
}
+ MSGPACK_DEFINE_MAP(int_value, long_value, float_value, double_value,
+ short_value, char_value, boolean_value, int_value_boxed,
+ long_value_boxed, float_value_boxed, double_value_boxed,
+ short_value_boxed, char_value_boxed, boolean_value_boxed,
+ int_array, long_array, float_array, double_array,
+ short_array, char_array, boolean_array, string);
};
FORY_STRUCT(Sample, int_value, long_value, float_value, double_value,
short_value, char_value, boolean_value, int_value_boxed,
@@ -112,8 +120,10 @@ FORY_FIELD_TAGS(Sample, (int_value, 1), (long_value, 2),
(float_value, 3),
// Enums for MediaContent benchmark
enum class Player : int32_t { JAVA = 0, FLASH = 1 };
+MSGPACK_ADD_ENUM(Player);
enum class Size : int32_t { SMALL = 0, LARGE = 1 };
+MSGPACK_ADD_ENUM(Size);
struct Media {
std::string uri;
@@ -137,6 +147,8 @@ struct Media {
persons == other.persons && player == other.player &&
copyright == other.copyright;
}
+ MSGPACK_DEFINE_MAP(uri, title, width, height, format, duration, size,
bitrate,
+ has_bitrate, persons, player, copyright);
};
FORY_STRUCT(Media, uri, title, width, height, format, duration, size, bitrate,
has_bitrate, persons, player, copyright);
@@ -155,6 +167,7 @@ struct Image {
return uri == other.uri && title == other.title && width == other.width &&
height == other.height && size == other.size;
}
+ MSGPACK_DEFINE_MAP(uri, title, width, height, size);
};
FORY_STRUCT(Image, uri, title, width, height, size);
FORY_FIELD_TAGS(Image, (uri, 1), (title, 2), (width, 3), (height, 4),
@@ -167,6 +180,7 @@ struct MediaContent {
bool operator==(const MediaContent &other) const {
return media == other.media && images == other.images;
}
+ MSGPACK_DEFINE_MAP(media, images);
};
FORY_STRUCT(MediaContent, media, images);
FORY_FIELD_TAGS(MediaContent, (media, 1), (images, 2));
@@ -177,6 +191,7 @@ struct StructList {
bool operator==(const StructList &other) const {
return struct_list == other.struct_list;
}
+ MSGPACK_DEFINE_MAP(struct_list);
};
FORY_STRUCT(StructList, struct_list);
FORY_FIELD_TAGS(StructList, (struct_list, 1));
@@ -187,6 +202,7 @@ struct SampleList {
bool operator==(const SampleList &other) const {
return sample_list == other.sample_list;
}
+ MSGPACK_DEFINE_MAP(sample_list);
};
FORY_STRUCT(SampleList, sample_list);
FORY_FIELD_TAGS(SampleList, (sample_list, 1));
@@ -197,6 +213,7 @@ struct MediaContentList {
bool operator==(const MediaContentList &other) const {
return media_content_list == other.media_content_list;
}
+ MSGPACK_DEFINE_MAP(media_content_list);
};
FORY_STRUCT(MediaContentList, media_content_list);
FORY_FIELD_TAGS(MediaContentList, (media_content_list, 1));
@@ -597,6 +614,55 @@ void register_fory_types(fory::serialization::Fory &fory) {
fory.register_struct<MediaContentList>(8);
}
+template <typename T, typename Factory>
+void run_msgpack_serialize_benchmark(benchmark::State &state, Factory factory)
{
+ T obj = factory();
+ msgpack::sbuffer output;
+
+ for (auto _ : state) {
+ output.clear();
+ msgpack::pack(output, obj);
+ benchmark::DoNotOptimize(output.data());
+ benchmark::DoNotOptimize(output.size());
+ }
+}
+
+template <typename T, typename Factory>
+void run_msgpack_deserialize_benchmark(benchmark::State &state,
+ Factory factory) {
+ T obj = factory();
+ msgpack::sbuffer output;
+ msgpack::pack(output, obj);
+
+ for (auto _ : state) {
+ msgpack::object_handle handle =
+ msgpack::unpack(output.data(), output.size());
+ T result;
+ handle.get().convert(result);
+ benchmark::DoNotOptimize(result);
+ }
+}
+
+#define DEFINE_MSGPACK_BENCHMARKS(name, type, create_fn)
\
+ static void BM_Msgpack_##name##_Serialize(benchmark::State &state) {
\
+ run_msgpack_serialize_benchmark<type>(state, create_fn);
\
+ }
\
+ BENCHMARK(BM_Msgpack_##name##_Serialize);
\
+ static void BM_Msgpack_##name##_Deserialize(benchmark::State &state) {
\
+ run_msgpack_deserialize_benchmark<type>(state, create_fn);
\
+ }
\
+ BENCHMARK(BM_Msgpack_##name##_Deserialize)
+
+DEFINE_MSGPACK_BENCHMARKS(Struct, NumericStruct, create_numeric_struct);
+DEFINE_MSGPACK_BENCHMARKS(Sample, Sample, create_sample);
+DEFINE_MSGPACK_BENCHMARKS(MediaContent, MediaContent, create_media_content);
+DEFINE_MSGPACK_BENCHMARKS(StructList, StructList, create_struct_list);
+DEFINE_MSGPACK_BENCHMARKS(SampleList, SampleList, create_sample_list);
+DEFINE_MSGPACK_BENCHMARKS(MediaContentList, MediaContentList,
+ create_media_content_list);
+
+#undef DEFINE_MSGPACK_BENCHMARKS
+
// ============================================================================
// Struct benchmarks (simple object with 8 int32 fields)
// ============================================================================
@@ -1096,12 +1162,14 @@ static void BM_PrintSerializedSizes(benchmark::State
&state) {
.track_ref(false)
.build();
register_fory_types(fory);
+
NumericStruct fory_struct = create_numeric_struct();
Sample fory_sample = create_sample();
MediaContent fory_media = create_media_content();
StructList fory_struct_list = create_struct_list();
SampleList fory_sample_list = create_sample_list();
MediaContentList fory_media_list = create_media_content_list();
+
auto fory_struct_bytes = fory.serialize(fory_struct).value();
auto fory_sample_bytes = fory.serialize(fory_sample).value();
auto fory_media_bytes = fory.serialize(fory_media).value();
@@ -1126,21 +1194,45 @@ static void BM_PrintSerializedSizes(benchmark::State
&state) {
proto_sample_list.SerializeToString(&proto_sample_list_bytes);
proto_media_list.SerializeToString(&proto_media_list_bytes);
+ auto msgpack_size = [](const auto &obj) -> size_t {
+ msgpack::sbuffer output;
+ msgpack::pack(output, obj);
+ return output.size();
+ };
+
+ auto msgpack_struct_size = msgpack_size(fory_struct);
+ auto msgpack_sample_size = msgpack_size(fory_sample);
+ auto msgpack_media_size = msgpack_size(fory_media);
+ auto msgpack_struct_list_size = msgpack_size(fory_struct_list);
+ auto msgpack_sample_list_size = msgpack_size(fory_sample_list);
+ auto msgpack_media_list_size = msgpack_size(fory_media_list);
+
for (auto _ : state) {
// Just run once to print sizes
}
state.counters["fory_struct_size"] = fory_struct_bytes.size();
- state.counters["proto_struct_size"] = proto_struct_bytes.size();
+ state.counters["protobuf_struct_size"] = proto_struct_bytes.size();
+ state.counters["msgpack_struct_size"] = msgpack_struct_size;
+
state.counters["fory_sample_size"] = fory_sample_bytes.size();
- state.counters["proto_sample_size"] = proto_sample_bytes.size();
+ state.counters["protobuf_sample_size"] = proto_sample_bytes.size();
+ state.counters["msgpack_sample_size"] = msgpack_sample_size;
+
state.counters["fory_media_size"] = fory_media_bytes.size();
- state.counters["proto_media_size"] = proto_media_bytes.size();
+ state.counters["protobuf_media_size"] = proto_media_bytes.size();
+ state.counters["msgpack_media_size"] = msgpack_media_size;
+
state.counters["fory_struct_list_size"] = fory_struct_list_bytes.size();
- state.counters["proto_struct_list_size"] = proto_struct_list_bytes.size();
+ state.counters["protobuf_struct_list_size"] = proto_struct_list_bytes.size();
+ state.counters["msgpack_struct_list_size"] = msgpack_struct_list_size;
+
state.counters["fory_sample_list_size"] = fory_sample_list_bytes.size();
- state.counters["proto_sample_list_size"] = proto_sample_list_bytes.size();
+ state.counters["protobuf_sample_list_size"] = proto_sample_list_bytes.size();
+ state.counters["msgpack_sample_list_size"] = msgpack_sample_list_size;
+
state.counters["fory_media_list_size"] = fory_media_list_bytes.size();
- state.counters["proto_media_list_size"] = proto_media_list_bytes.size();
+ state.counters["protobuf_media_list_size"] = proto_media_list_bytes.size();
+ state.counters["msgpack_media_list_size"] = msgpack_media_list_size;
}
BENCHMARK(BM_PrintSerializedSizes)->Iterations(1);
diff --git a/benchmarks/cpp_benchmark/benchmark_report.py
b/benchmarks/cpp_benchmark/benchmark_report.py
index 5e49c1106..0d6eb4204 100644
--- a/benchmarks/cpp_benchmark/benchmark_report.py
+++ b/benchmarks/cpp_benchmark/benchmark_report.py
@@ -31,9 +31,18 @@ try:
except ImportError:
HAS_PSUTIL = False
-# === Colors ===
-FORY_COLOR = "#FF6f01" # Orange for Fory
-PROTOBUF_COLOR = "#55BCC2" # Teal for Protobuf
+# === Colors and serializer order ===
+COLORS = {
+ "fory": "#FF6f01", # Orange
+ "protobuf": "#55BCC2", # Teal
+ "msgpack": (0.55, 0.40, 0.45),
+}
+SERIALIZER_ORDER = ["fory", "protobuf", "msgpack"]
+SERIALIZER_LABELS = {
+ "fory": "fory",
+ "protobuf": "protobuf",
+ "msgpack": "msgpack",
+}
# === Parse arguments ===
parser = argparse.ArgumentParser(
@@ -82,6 +91,7 @@ def parse_benchmark_name(name):
Parse benchmark names like:
- BM_Fory_Struct_Serialize
- BM_Protobuf_Sample_Deserialize
+ - BM_Msgpack_MediaContent_Deserialize
Returns: (library, datatype, operation)
"""
# Remove BM_ prefix
@@ -90,9 +100,9 @@ def parse_benchmark_name(name):
parts = name.split("_")
if len(parts) >= 3:
- library = parts[0].lower() # fory or protobuf
- datatype = parts[1].lower() # struct or sample
- operation = parts[2].lower() # serialize or deserialize
+ library = parts[0].lower()
+ datatype = parts[1].lower()
+ operation = parts[2].lower()
return library, datatype, operation
return None, None, None
@@ -110,6 +120,19 @@ def format_datatype_label(datatype):
return datatype.capitalize()
+def format_datatype_table_label(datatype):
+ if not datatype:
+ return ""
+ if datatype.endswith("list"):
+ base = datatype[: -len("list")]
+ if base == "mediacontent":
+ return "MediaContentList"
+ return f"{base.capitalize()}List"
+ if datatype == "mediacontent":
+ return "MediaContent"
+ return datatype.capitalize()
+
+
# === Read and parse benchmark JSON ===
def load_benchmark_data(json_file):
with open(json_file, "r", encoding="utf-8") as f:
@@ -135,22 +158,9 @@ for bench in benchmark_data.get("benchmarks", []):
if "/iterations:" in name or "PrintSerializedSizes" in name:
# Extract sizes from PrintSerializedSizes
if "PrintSerializedSizes" in name:
- for key in [
- "fory_struct_size",
- "proto_struct_size",
- "fory_sample_size",
- "proto_sample_size",
- "fory_media_size",
- "proto_media_size",
- "fory_struct_list_size",
- "proto_struct_list_size",
- "fory_sample_list_size",
- "proto_sample_list_size",
- "fory_media_list_size",
- "proto_media_list_size",
- ]:
- if key in bench:
- sizes[key] = int(bench[key])
+ for key, value in bench.items():
+ if key.endswith("_size"):
+ sizes[key] = int(value)
continue
library, datatype, operation = parse_benchmark_name(name)
@@ -181,33 +191,45 @@ if context:
# === 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 plot_datatype(ax, datatype, operation):
- """Plot a single datatype/operation comparison."""
+ """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 = sorted(data[datatype][operation].keys())
- lib_order = [lib for lib in ["fory", "protobuf"] if lib in libs]
+ 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]
- colors = [FORY_COLOR if lib == "fory" else PROTOBUF_COLOR 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, times, color=colors, width=0.6)
+ bars = ax.bar(x, throughput, color=colors, width=0.6)
- ax.set_title(f"{datatype.capitalize()} {operation.capitalize()}")
+ ax.set_title(f"{operation.capitalize()} Throughput (higher is better)")
ax.set_xticks(x)
- ax.set_xticklabels([lib.capitalize() for lib in lib_order])
- ax.set_ylabel("Time (ns)")
+ 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, time_val in zip(bars, times):
+ for bar, tps_val in zip(bars, throughput):
height = bar.get_height()
ax.annotate(
- f"{time_val:.1f}",
+ format_tps_label(tps_val),
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3),
textcoords="offset points",
@@ -226,7 +248,7 @@ 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()} Benchmark Results", fontsize=14)
+ 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)
@@ -245,17 +267,28 @@ def plot_combined_tps_subplot(ax, grouped_datatypes,
operation, title):
return
x = np.arange(len(grouped_datatypes))
- width = 0.35
-
- fory_times = [data[dt][operation].get("fory", 0) for dt in
grouped_datatypes]
- proto_times = [data[dt][operation].get("protobuf", 0) for dt in
grouped_datatypes]
-
- # Convert to TPS (operations per second)
- fory_tps = [1e9 / t if t > 0 else 0 for t in fory_times]
- proto_tps = [1e9 / t if t > 0 else 0 for t in proto_times]
+ available_libs = [
+ lib
+ for lib in SERIALIZER_ORDER
+ if any(data[dt][operation].get(lib, 0) > 0 for dt in grouped_datatypes)
+ ]
+ if not available_libs:
+ ax.set_title(f"{title}\nNo Data")
+ ax.axis("off")
+ return
- ax.bar(x - width / 2, fory_tps, width, label="Fory", color=FORY_COLOR)
- ax.bar(x + width / 2, proto_tps, width, label="Protobuf",
color=PROTOBUF_COLOR)
+ width = 0.8 / len(available_libs)
+ for idx, lib in enumerate(available_libs):
+ times = [data[dt][operation].get(lib, 0) for dt in grouped_datatypes]
+ tps = [1e9 / t if t > 0 else 0 for t in times]
+ offset = (idx - (len(available_libs) - 1) / 2) * width
+ ax.bar(
+ x + offset,
+ tps,
+ width,
+ label=SERIALIZER_LABELS.get(lib, lib),
+ color=COLORS.get(lib, "#888888"),
+ )
ax.set_title(title)
ax.set_xticks(x)
@@ -267,7 +300,7 @@ def plot_combined_tps_subplot(ax, grouped_datatypes,
operation, title):
ax.ticklabel_format(style="scientific", axis="y", scilimits=(0, 0))
-fig, axes = plt.subplots(1, 4, figsize=(24, 6))
+fig, axes = plt.subplots(1, 4, figsize=(28, 6))
fig.supylabel("Throughput (ops/sec)")
combined_subplots = [
@@ -306,7 +339,11 @@ for k, v in system_info.items():
# Plots section
md_report.append("\n## Benchmark Plots\n")
-for datatype, img in plot_images:
+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,
item[0])
+)
+for datatype, img in plot_images_sorted:
img_filename = os.path.basename(img)
img_path_report = args.plot_prefix + img_filename
md_report.append(f"\n### {datatype.replace('_', ' ').title()}\n\n")
@@ -317,76 +354,95 @@ for datatype, img in plot_images:
# Results table
md_report.append("\n## Benchmark Results\n\n")
md_report.append("### Timing Results (nanoseconds)\n\n")
-md_report.append("| Datatype | Operation | Fory (ns) | Protobuf (ns) | Faster
|\n")
-md_report.append("|----------|-----------|-----------|---------------|--------|\n")
+md_report.append(
+ "| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) |
Fastest |\n"
+)
+md_report.append(
+
"|----------|-----------|-----------|---------------|--------------|---------|\n"
+)
for datatype in datatypes:
for op in operations:
- f_time = data[datatype][op].get("fory", 0)
- p_time = data[datatype][op].get("protobuf", 0)
- if f_time > 0 and p_time > 0:
- faster = "Fory" if f_time < p_time else "Protobuf"
- ratio = max(f_time, p_time) / min(f_time, p_time)
- faster_str = f"{faster} ({ratio:.1f}x)"
- else:
- faster_str = "N/A"
+ times = {lib: data[datatype][op].get(lib, 0) for lib in
SERIALIZER_ORDER}
+ positive_times = {lib: t for lib, t in times.items() if t > 0}
+ fastest_str = "N/A"
+ if positive_times:
+ fastest_lib = min(positive_times, key=positive_times.get)
+ fastest_str = SERIALIZER_LABELS.get(fastest_lib, fastest_lib)
md_report.append(
- f"| {datatype.capitalize()} | {op.capitalize()} | {f_time:.1f} |
{p_time:.1f} | {faster_str} |\n"
+ "| "
+ + f"{format_datatype_table_label(datatype)} | {op.capitalize()} | "
+ + " | ".join(
+ f"{times[lib]:.1f}" if times[lib] > 0 else "N/A"
+ for lib in SERIALIZER_ORDER
+ )
+ + f" | {fastest_str} |\n"
)
# Throughput table
md_report.append("\n### Throughput Results (ops/sec)\n\n")
-md_report.append("| Datatype | Operation | Fory TPS | Protobuf TPS | Faster
|\n")
-md_report.append("|----------|-----------|----------|--------------|--------|\n")
+md_report.append(
+ "| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS | Fastest
|\n"
+)
+md_report.append(
+
"|----------|-----------|----------|--------------|-------------|---------|\n"
+)
for datatype in datatypes:
for op in operations:
- f_time = data[datatype][op].get("fory", 0)
- p_time = data[datatype][op].get("protobuf", 0)
- f_tps = 1e9 / f_time if f_time > 0 else 0
- p_tps = 1e9 / p_time if p_time > 0 else 0
- if f_tps > 0 and p_tps > 0:
- faster = "Fory" if f_tps > p_tps else "Protobuf"
- ratio = max(f_tps, p_tps) / min(f_tps, p_tps)
- faster_str = f"{faster} ({ratio:.1f}x)"
- else:
- faster_str = "N/A"
+ times = {lib: data[datatype][op].get(lib, 0) for lib in
SERIALIZER_ORDER}
+ tps = {lib: (1e9 / t if t > 0 else 0) for lib, t in times.items()}
+ positive_tps = {lib: v for lib, v in tps.items() if v > 0}
+ fastest_str = "N/A"
+ if positive_tps:
+ fastest_lib = max(positive_tps, key=positive_tps.get)
+ fastest_str = SERIALIZER_LABELS.get(fastest_lib, fastest_lib)
md_report.append(
- f"| {datatype.capitalize()} | {op.capitalize()} | {f_tps:,.0f} |
{p_tps:,.0f} | {faster_str} |\n"
+ "| "
+ + f"{format_datatype_table_label(datatype)} | {op.capitalize()} | "
+ + " | ".join(
+ f"{tps[lib]:,.0f}" if tps[lib] > 0 else "N/A"
+ for lib in SERIALIZER_ORDER
+ )
+ + f" | {fastest_str} |\n"
)
# Serialized sizes
if sizes:
md_report.append("\n### Serialized Data Sizes (bytes)\n\n")
- md_report.append("| Datatype | Fory | Protobuf |\n")
- md_report.append("|----------|------|----------|\n")
- if "fory_struct_size" in sizes and "proto_struct_size" in sizes:
- md_report.append(
- f"| Struct | {sizes['fory_struct_size']} |
{sizes['proto_struct_size']} |\n"
- )
- if "fory_sample_size" in sizes and "proto_sample_size" in sizes:
- md_report.append(
- f"| Sample | {sizes['fory_sample_size']} |
{sizes['proto_sample_size']} |\n"
- )
- if "fory_media_size" in sizes and "proto_media_size" in sizes:
- md_report.append(
- f"| MediaContent | {sizes['fory_media_size']} |
{sizes['proto_media_size']} |\n"
- )
- if "fory_struct_list_size" in sizes and "proto_struct_list_size" in sizes:
- md_report.append(
- f"| StructList | {sizes['fory_struct_list_size']} |
{sizes['proto_struct_list_size']} |\n"
- )
- if "fory_sample_list_size" in sizes and "proto_sample_list_size" in sizes:
- md_report.append(
- f"| SampleList | {sizes['fory_sample_list_size']} |
{sizes['proto_sample_list_size']} |\n"
- )
- if "fory_media_list_size" in sizes and "proto_media_list_size" in sizes:
- md_report.append(
- f"| MediaContentList | {sizes['fory_media_list_size']} |
{sizes['proto_media_list_size']} |\n"
- )
+ md_report.append("| Datatype | fory | protobuf | msgpack |\n")
+ md_report.append("|----------|------|----------|---------|\n")
+ size_prefix = {
+ "fory": "fory",
+ "protobuf": "protobuf",
+ "msgpack": "msgpack",
+ }
+ size_datatypes = [
+ ("struct", "Struct"),
+ ("sample", "Sample"),
+ ("media", "MediaContent"),
+ ("struct_list", "StructList"),
+ ("sample_list", "SampleList"),
+ ("media_list", "MediaContentList"),
+ ]
+ for datatype_key, datatype_label in size_datatypes:
+ row_values = []
+ has_value = False
+ for lib in SERIALIZER_ORDER:
+ key = f"{size_prefix[lib]}_{datatype_key}_size"
+ value = sizes.get(key)
+ if value is None and lib == "protobuf":
+ value = sizes.get(f"proto_{datatype_key}_size")
+ if value is None:
+ row_values.append("N/A")
+ else:
+ row_values.append(str(value))
+ has_value = True
+ if has_value:
+ md_report.append(f"| {datatype_label} | " + " | ".join(row_values)
+ " |\n")
# Save Markdown
-report_path = os.path.join(output_dir, "REPORT.md")
+report_path = os.path.join(output_dir, "README.md")
with open(report_path, "w", encoding="utf-8") as f:
f.writelines(md_report)
diff --git a/benchmarks/cpp_benchmark/run.sh b/benchmarks/cpp_benchmark/run.sh
index 3d6500c46..53130865c 100755
--- a/benchmarks/cpp_benchmark/run.sh
+++ b/benchmarks/cpp_benchmark/run.sh
@@ -41,8 +41,10 @@ usage() {
echo "Build and run C++ benchmarks"
echo ""
echo "Options:"
- echo " --data <struct|sample> Filter benchmark by data type"
- echo " --serializer <fory|protobuf> Filter benchmark by serializer"
+ echo " --data
<struct|sample|mediacontent|structlist|samplelist|mediacontentlist>"
+ echo " Filter benchmark by data type"
+ echo " --serializer <fory|protobuf|msgpack>"
+ echo " Filter benchmark by serializer"
echo " --duration <seconds> Minimum time to run each benchmark
(e.g., 10, 30)"
echo " --debug Build with debug symbols and low
optimization for profiling"
echo " --help Show this help message"
@@ -51,6 +53,7 @@ usage() {
echo " $0 # Run all benchmarks"
echo " $0 --data struct # Run only Struct benchmarks"
echo " $0 --serializer fory # Run only Fory benchmarks"
+ echo " $0 --serializer msgpack # Run only Msgpack benchmarks"
echo " $0 --data struct --serializer fory"
echo " $0 --duration 10 # Run each benchmark for at least 10
seconds"
echo " $0 --debug # Build for profiling (visible
function names in flamegraph)"
@@ -89,17 +92,64 @@ done
# Build benchmark filter
FILTER=""
+DATA_PATTERN=""
+SERIALIZER_PATTERN=""
+
if [[ -n "$DATA" ]]; then
- DATA_CAP="$(echo "${DATA:0:1}" | tr '[:lower:]' '[:upper:]')${DATA:1}"
- FILTER="${DATA_CAP}"
+ DATA_LOWER="$(echo "$DATA" | tr '[:upper:]' '[:lower:]')"
+ case "$DATA_LOWER" in
+ struct)
+ DATA_PATTERN="Struct"
+ ;;
+ sample)
+ DATA_PATTERN="Sample"
+ ;;
+ mediacontent)
+ DATA_PATTERN="MediaContent"
+ ;;
+ structlist)
+ DATA_PATTERN="StructList"
+ ;;
+ samplelist)
+ DATA_PATTERN="SampleList"
+ ;;
+ mediacontentlist)
+ DATA_PATTERN="MediaContentList"
+ ;;
+ *)
+ echo -e "${RED}Unknown data type: $DATA${NC}"
+ echo "Expected one of: struct, sample, mediacontent, structlist,
samplelist, mediacontentlist"
+ exit 1
+ ;;
+ esac
fi
+
if [[ -n "$SERIALIZER" ]]; then
- SER_CAP="$(echo "${SERIALIZER:0:1}" | tr '[:lower:]'
'[:upper:]')${SERIALIZER:1}"
- if [[ -n "$FILTER" ]]; then
- FILTER="${SER_CAP}_${FILTER}"
- else
- FILTER="${SER_CAP}"
- fi
+ SERIALIZER_LOWER="$(echo "$SERIALIZER" | tr '[:upper:]' '[:lower:]')"
+ case "$SERIALIZER_LOWER" in
+ fory)
+ SERIALIZER_PATTERN="Fory"
+ ;;
+ protobuf)
+ SERIALIZER_PATTERN="Protobuf"
+ ;;
+ msgpack)
+ SERIALIZER_PATTERN="Msgpack"
+ ;;
+ *)
+ echo -e "${RED}Unknown serializer: $SERIALIZER${NC}"
+ echo "Expected one of: fory, protobuf, msgpack"
+ exit 1
+ ;;
+ esac
+fi
+
+if [[ -n "$SERIALIZER_PATTERN" && -n "$DATA_PATTERN" ]]; then
+ FILTER="^BM_${SERIALIZER_PATTERN}_${DATA_PATTERN}_(Serialize|Deserialize)$"
+elif [[ -n "$SERIALIZER_PATTERN" ]]; then
+ FILTER="^BM_${SERIALIZER_PATTERN}_"
+elif [[ -n "$DATA_PATTERN" ]]; then
+ FILTER="^BM_.*_${DATA_PATTERN}_(Serialize|Deserialize)$"
fi
echo -e "${GREEN}=== Fory C++ Benchmark ===${NC}"
diff --git a/docs/benchmarks/cpp/README.md b/docs/benchmarks/cpp/README.md
index 1721869d8..63eba5adb 100644
--- a/docs/benchmarks/cpp/README.md
+++ b/docs/benchmarks/cpp/README.md
@@ -1,6 +1,6 @@
# C++ Benchmark Performance Report
-_Generated on 2025-12-15 23:03:06_
+_Generated on 2026-02-19 10:20:08_
## How to Generate This Report
@@ -21,40 +21,98 @@ 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 | 2025-12-15T23:02:49+08:00 |
+| Benchmark Date | 2026-02-19T10:19:31+08:00 |
| CPU Cores (from benchmark) | 12 |
## Benchmark Plots
-
+All class-level plots below show throughput (ops/sec).
+
+### Throughput
+
+<p align="center">
+<img src="throughput.png" width="90%" />
+</p>
+
+### Mediacontent
+
+<p align="center">
+<img src="mediacontent.png" width="90%" />
+</p>
+
+### Mediacontentlist
+
+<p align="center">
+<img src="mediacontentlist.png" width="90%" />
+</p>
+
+### Sample
+
+<p align="center">
+<img src="sample.png" width="90%" />
+</p>
+
+### Samplelist
+
+<p align="center">
+<img src="samplelist.png" width="90%" />
+</p>
+
+### Struct
+
+<p align="center">
+<img src="struct.png" width="90%" />
+</p>
+
+### Structlist
+
+<p align="center">
+<img src="structlist.png" width="90%" />
+</p>
## Benchmark Results
### Timing Results (nanoseconds)
-| Datatype | Operation | Fory (ns) | Protobuf (ns) | Faster |
-| ------------ | ----------- | --------- | ------------- | ----------- |
-| Mediacontent | Serialize | 443.5 | 1982.5 | Fory (4.5x) |
-| Mediacontent | Deserialize | 1349.0 | 2525.2 | Fory (1.9x) |
-| Sample | Serialize | 235.4 | 309.7 | Fory (1.3x) |
-| Sample | Deserialize | 1068.7 | 1397.0 | Fory (1.3x) |
-| Struct | Serialize | 109.4 | 170.0 | Fory (1.6x) |
-| Struct | Deserialize | 129.1 | 161.2 | Fory (1.2x) |
+| Datatype | Operation | fory (ns) | protobuf (ns) | msgpack (ns) |
Fastest |
+| ---------------- | ----------- | --------- | ------------- | ------------ |
------- |
+| MediaContent | Serialize | 120.4 | 863.8 | 281.1 |
fory |
+| MediaContent | Deserialize | 397.9 | 1197.9 | 2768.6 |
fory |
+| MediaContentList | Serialize | 480.9 | 4744.3 | 1407.5 |
fory |
+| MediaContentList | Deserialize | 2022.1 | 6426.0 | 13595.9 |
fory |
+| Sample | Serialize | 72.8 | 92.0 | 296.1 |
fory |
+| Sample | Deserialize | 328.2 | 641.2 | 2642.9 |
fory |
+| SampleList | Serialize | 287.3 | 4761.2 | 1506.7 |
fory |
+| SampleList | Deserialize | 1711.8 | 4875.2 | 13232.0 |
fory |
+| Struct | Serialize | 27.3 | 32.4 | 55.2 |
fory |
+| Struct | Deserialize | 21.4 | 25.0 | 747.3 |
fory |
+| StructList | Serialize | 69.4 | 419.8 | 285.9 |
fory |
+| StructList | Deserialize | 129.4 | 334.4 | 3385.8 |
fory |
### Throughput Results (ops/sec)
-| Datatype | Operation | Fory TPS | Protobuf TPS | Faster |
-| ------------ | ----------- | --------- | ------------ | ----------- |
-| Mediacontent | Serialize | 2,254,915 | 504,410 | Fory (4.5x) |
-| Mediacontent | Deserialize | 741,303 | 396,013 | Fory (1.9x) |
-| Sample | Serialize | 4,248,973 | 3,229,102 | Fory (1.3x) |
-| Sample | Deserialize | 935,709 | 715,837 | Fory (1.3x) |
-| Struct | Serialize | 9,143,618 | 5,881,005 | Fory (1.6x) |
-| Struct | Deserialize | 7,746,787 | 6,202,164 | Fory (1.2x) |
+| Datatype | Operation | fory TPS | protobuf TPS | msgpack TPS |
Fastest |
+| ---------------- | ----------- | ---------- | ------------ | ----------- |
------- |
+| MediaContent | Serialize | 8,306,128 | 1,157,712 | 3,557,700 |
fory |
+| MediaContent | Deserialize | 2,513,488 | 834,808 | 361,190 |
fory |
+| MediaContentList | Serialize | 2,079,229 | 210,777 | 710,492 |
fory |
+| MediaContentList | Deserialize | 494,523 | 155,617 | 73,551 |
fory |
+| Sample | Serialize | 13,745,041 | 10,871,787 | 3,377,292 |
fory |
+| Sample | Deserialize | 3,047,224 | 1,559,633 | 378,369 |
fory |
+| SampleList | Serialize | 3,481,110 | 210,029 | 663,693 |
fory |
+| SampleList | Deserialize | 584,168 | 205,121 | 75,574 |
fory |
+| Struct | Serialize | 36,672,581 | 30,900,039 | 18,114,682 |
fory |
+| Struct | Deserialize | 46,637,124 | 39,947,557 | 1,338,225 |
fory |
+| StructList | Serialize | 14,419,548 | 2,381,886 | 3,497,903 |
fory |
+| StructList | Deserialize | 7,729,173 | 2,990,652 | 295,353 |
fory |
### Serialized Data Sizes (bytes)
-| Datatype | Fory | Protobuf |
-| -------- | ---- | -------- |
-| Struct | 32 | 61 |
-| Sample | 384 | 375 |
+| Datatype | fory | protobuf | msgpack |
+| ---------------- | ---- | -------- | ------- |
+| Struct | 58 | 61 | 55 |
+| Sample | 446 | 375 | 530 |
+| MediaContent | 365 | 301 | 480 |
+| StructList | 184 | 315 | 289 |
+| SampleList | 1980 | 1890 | 2664 |
+| MediaContentList | 1535 | 1520 | 2421 |
diff --git a/docs/benchmarks/cpp/mediacontent.png
b/docs/benchmarks/cpp/mediacontent.png
index 3c42b14f3..7fd07e461 100644
Binary files a/docs/benchmarks/cpp/mediacontent.png and
b/docs/benchmarks/cpp/mediacontent.png differ
diff --git a/docs/benchmarks/cpp/mediacontentlist.png
b/docs/benchmarks/cpp/mediacontentlist.png
index 604663b68..1fccde649 100644
Binary files a/docs/benchmarks/cpp/mediacontentlist.png and
b/docs/benchmarks/cpp/mediacontentlist.png differ
diff --git a/docs/benchmarks/cpp/sample.png b/docs/benchmarks/cpp/sample.png
index 2932de24b..127a6e1aa 100644
Binary files a/docs/benchmarks/cpp/sample.png and
b/docs/benchmarks/cpp/sample.png differ
diff --git a/docs/benchmarks/cpp/samplelist.png
b/docs/benchmarks/cpp/samplelist.png
index 6d903f53c..2ebde46ef 100644
Binary files a/docs/benchmarks/cpp/samplelist.png and
b/docs/benchmarks/cpp/samplelist.png differ
diff --git a/docs/benchmarks/cpp/struct.png b/docs/benchmarks/cpp/struct.png
index 94209b375..88e586ff6 100644
Binary files a/docs/benchmarks/cpp/struct.png and
b/docs/benchmarks/cpp/struct.png differ
diff --git a/docs/benchmarks/cpp/structlist.png
b/docs/benchmarks/cpp/structlist.png
index fe2dca4ca..c676e84df 100644
Binary files a/docs/benchmarks/cpp/structlist.png and
b/docs/benchmarks/cpp/structlist.png differ
diff --git a/docs/benchmarks/cpp/throughput.png
b/docs/benchmarks/cpp/throughput.png
index 1aa86b16b..9ffa742b6 100644
Binary files a/docs/benchmarks/cpp/throughput.png and
b/docs/benchmarks/cpp/throughput.png differ
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]