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
 
-![Throughput](throughput.png)
+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]


Reply via email to