This is an automated email from the ASF dual-hosted git repository.

lxy-9602 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/paimon-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new d602c2c5 perf: avoid shared pointer contention in manifest and Avro 
decode (#239)
d602c2c5 is described below

commit d602c2c509f2495f6f5d721be1c5de995f4b5fcd
Author: gripleaf <[email protected]>
AuthorDate: Mon Aug 24 14:45:59 2026 +0800

    perf: avoid shared pointer contention in manifest and Avro decode (#239)
---
 src/paimon/core/manifest/manifest_file.cpp         |  3 ++-
 src/paimon/core/utils/objects_file.h               |  3 ++-
 src/paimon/format/avro/avro_direct_decoder.cpp     | 28 +++++++++++++++++-----
 src/paimon/format/avro/avro_direct_decoder.h       | 20 ++++++++++++++++
 .../avro/avro_direct_encoder_decoder_test.cpp      | 23 ++++++++++++++++++
 src/paimon/format/avro/avro_file_batch_reader.cpp  |  1 +
 6 files changed, 70 insertions(+), 8 deletions(-)

diff --git a/src/paimon/core/manifest/manifest_file.cpp 
b/src/paimon/core/manifest/manifest_file.cpp
index 1be49d0b..9f9c8aee 100644
--- a/src/paimon/core/manifest/manifest_file.cpp
+++ b/src/paimon/core/manifest/manifest_file.cpp
@@ -92,8 +92,9 @@ Status ManifestFile::ReadBucketEntries(const std::string& 
file_name, int32_t buc
     return ReadArrowBatches(
         file_name,
         [this, bucket, entries](const std::shared_ptr<arrow::StructArray>& 
batch) -> Status {
+            const arrow::ArrayVector& fields = batch->fields();
             for (int64_t i = 0; i < batch->length(); i++) {
-                ColumnarRow row(batch->fields(), pool_, i);
+                ColumnarRow row(fields, pool_, i);
                 
PAIMON_RETURN_NOT_OK(ManifestEntrySerializer::ValidateVersion(row.GetInt(0)));
                 if (ManifestEntrySerializer::GetBucket(row) != bucket) {
                     continue;
diff --git a/src/paimon/core/utils/objects_file.h 
b/src/paimon/core/utils/objects_file.h
index a56952ae..b3135b31 100644
--- a/src/paimon/core/utils/objects_file.h
+++ b/src/paimon/core/utils/objects_file.h
@@ -134,8 +134,9 @@ Status ObjectsFile<T>::Read(const std::string& file_name,
         file_name,
         [this, &filter, result](const std::shared_ptr<arrow::StructArray>& 
struct_array) -> Status {
             result->reserve(result->size() + struct_array->length());
+            const arrow::ArrayVector& fields = struct_array->fields();
             for (int64_t i = 0; i < struct_array->length(); i++) {
-                ColumnarRow row(struct_array->fields(), pool_, i);
+                ColumnarRow row(fields, pool_, i);
                 PAIMON_ASSIGN_OR_RAISE(T obj, serializer_->FromRow(row));
                 if (filter) {
                     PAIMON_ASSIGN_OR_RAISE(bool filter_res, filter(obj));
diff --git a/src/paimon/format/avro/avro_direct_decoder.cpp 
b/src/paimon/format/avro/avro_direct_decoder.cpp
index f837eed0..f9c8a9a4 100644
--- a/src/paimon/format/avro/avro_direct_decoder.cpp
+++ b/src/paimon/format/avro/avro_direct_decoder.cpp
@@ -33,6 +33,22 @@
 
 namespace paimon::avro {
 
+const AvroDirectDecoder::DecodeContext::BuilderMetadata&
+AvroDirectDecoder::DecodeContext::GetBuilderMetadata(const 
arrow::ArrayBuilder* builder) {
+    auto iter = builder_metadata_.find(builder);
+    if (iter != builder_metadata_.end()) {
+        return iter->second;
+    }
+
+    std::shared_ptr<arrow::DataType> data_type = builder->type();
+    BuilderMetadata metadata{data_type->id(), std::nullopt};
+    if (data_type->id() == arrow::Type::TIMESTAMP) {
+        metadata.timestamp_unit =
+            checked_cast<const arrow::TimestampType*>(data_type.get())->unit();
+    }
+    return builder_metadata_.emplace(builder, metadata).first->second;
+}
+
 namespace {
 
 /// Forward declaration for mutual recursion.
@@ -266,8 +282,8 @@ Status DecodeAvroValueToBuilder(const ::avro::NodePtr& 
avro_node,
 
         case ::avro::AVRO_INT: {
             int32_t value = decoder->decodeInt();
-            auto arrow_type = array_builder->type();
-            switch (arrow_type->id()) {
+            const auto& builder_metadata = 
ctx->GetBuilderMetadata(array_builder);
+            switch (builder_metadata.type) {
                 case arrow::Type::INT8: {
                     auto* builder = 
checked_cast<arrow::Int8Builder*>(array_builder);
                     PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
@@ -287,7 +303,7 @@ Status DecodeAvroValueToBuilder(const ::avro::NodePtr& 
avro_node,
                     if (logical_type.type() != 
::avro::LogicalType::Type::DATE) {
                         return Status::TypeError(
                             fmt::format("Unexpected avro type [{}] with arrow 
type [{}].",
-                                        ::avro::toString(type), 
arrow_type->ToString()));
+                                        ::avro::toString(type), 
array_builder->type()->ToString()));
                     }
                     auto* builder = 
checked_cast<arrow::Date32Builder*>(array_builder);
                     PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
@@ -296,7 +312,7 @@ Status DecodeAvroValueToBuilder(const ::avro::NodePtr& 
avro_node,
                 default:
                     return Status::TypeError(
                         fmt::format("Unexpected avro type [{}] with arrow type 
[{}].",
-                                    ::avro::toString(type), 
arrow_type->ToString()));
+                                    ::avro::toString(type), 
array_builder->type()->ToString()));
             }
         }
 
@@ -315,9 +331,9 @@ Status DecodeAvroValueToBuilder(const ::avro::NodePtr& 
avro_node,
                 case ::avro::LogicalType::Type::LOCAL_TIMESTAMP_MICROS:
                 case ::avro::LogicalType::Type::LOCAL_TIMESTAMP_NANOS: {
                     auto* builder = 
checked_cast<arrow::TimestampBuilder*>(array_builder);
-                    auto ts_type = 
checked_cast<arrow::TimestampType*>(builder->type().get());
                     // for arrow second, we need to convert it from avro 
millisecond
-                    if (ts_type->unit() == arrow::TimeUnit::type::SECOND) {
+                    const auto& builder_metadata = 
ctx->GetBuilderMetadata(builder);
+                    if (builder_metadata.timestamp_unit == 
arrow::TimeUnit::type::SECOND) {
                         value /= 
DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::MILLISECOND];
                     }
                     PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Append(value));
diff --git a/src/paimon/format/avro/avro_direct_decoder.h 
b/src/paimon/format/avro/avro_direct_decoder.h
index c507091a..6422f915 100644
--- a/src/paimon/format/avro/avro_direct_decoder.h
+++ b/src/paimon/format/avro/avro_direct_decoder.h
@@ -22,7 +22,11 @@
 
 #pragma once
 
+#include <optional>
 #include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
 
 #include "arrow/array/builder_base.h"
 #include "avro/Decoder.hh"
@@ -41,10 +45,26 @@ class AvroDirectDecoder {
     /// Avoids frequent small allocations by reusing temporary buffers across 
multiple decode
     /// operations. This is particularly important for string, binary, and 
decimal data types.
     struct DecodeContext {
+        struct BuilderMetadata {
+            arrow::Type::type type;
+            std::optional<arrow::TimeUnit::type> timestamp_unit;
+        };
+
+        /// Returns immutable type metadata without repeatedly copying the 
builder's DataType.
+        const BuilderMetadata& GetBuilderMetadata(const arrow::ArrayBuilder* 
builder);
+
+        /// Clears metadata before the builder tree is replaced or destroyed.
+        void ClearBuilderMetadata() {
+            builder_metadata_.clear();
+        }
+
         // Scratch buffer for string decoding (reused across rows)
         std::string string_scratch;
         // Scratch buffer for binary/decimal data (reused across rows)
         std::vector<uint8_t> bytes_scratch;
+
+     private:
+        std::unordered_map<const arrow::ArrayBuilder*, BuilderMetadata> 
builder_metadata_;
     };
 
     /// Directly decode Avro data to Arrow array builders without GenericDatum
diff --git a/src/paimon/format/avro/avro_direct_encoder_decoder_test.cpp 
b/src/paimon/format/avro/avro_direct_encoder_decoder_test.cpp
index f276d946..78f4ca48 100644
--- a/src/paimon/format/avro/avro_direct_encoder_decoder_test.cpp
+++ b/src/paimon/format/avro/avro_direct_encoder_decoder_test.cpp
@@ -62,6 +62,7 @@ class AvroDirectEncoderDecoderTest : public ::testing::Test {
         auto decoder = ::avro::binaryDecoder();
         decoder->init(*input_stream);
 
+        decode_ctx_.ClearBuilderMetadata();
         for (int32_t i = 0; i < expected_count; ++i) {
             PAIMON_RETURN_NOT_OK(AvroDirectDecoder::DecodeAvroToBuilder(
                 avro_node, projection, decoder.get(), builder, &decode_ctx_));
@@ -157,6 +158,18 @@ TEST_F(AvroDirectEncoderDecoderTest, TestIntegerTypes) {
         CheckResult(schema_json, input_array, &builder);
     }
 
+    // Test INT16
+    {
+        std::string schema_json = R"({"type": "int"})";
+        arrow::Int16Builder builder;
+        ASSERT_TRUE(builder.Append(1).ok());
+        ASSERT_TRUE(builder.Append(-32768).ok());
+        ASSERT_TRUE(builder.Append(32767).ok());
+        std::shared_ptr<arrow::Array> input_array;
+        ASSERT_TRUE(builder.Finish(&input_array).ok());
+        CheckResult(schema_json, input_array, &builder);
+    }
+
     // Test INT32
     {
         std::string schema_json = R"({"type": "int"})";
@@ -182,6 +195,16 @@ TEST_F(AvroDirectEncoderDecoderTest, TestIntegerTypes) {
     }
 }
 
+TEST_F(AvroDirectEncoderDecoderTest, 
TestDecodeContextBuilderMetadataLifecycle) {
+    arrow::Int8Builder int8_builder;
+    ASSERT_EQ(decode_ctx_.GetBuilderMetadata(&int8_builder).type, 
arrow::Type::INT8);
+
+    decode_ctx_.ClearBuilderMetadata();
+
+    arrow::Int16Builder int16_builder;
+    ASSERT_EQ(decode_ctx_.GetBuilderMetadata(&int16_builder).type, 
arrow::Type::INT16);
+}
+
 TEST_F(AvroDirectEncoderDecoderTest, TestFloatingPointTypes) {
     // Test FLOAT
     {
diff --git a/src/paimon/format/avro/avro_file_batch_reader.cpp 
b/src/paimon/format/avro/avro_file_batch_reader.cpp
index f48ec4cc..1e217f5c 100644
--- a/src/paimon/format/avro/avro_file_batch_reader.cpp
+++ b/src/paimon/format/avro/avro_file_batch_reader.cpp
@@ -172,6 +172,7 @@ Status AvroFileBatchReader::SetReadSchema(::ArrowSchema* 
read_schema,
     }
     reader_ = std::move(reader);
     array_builder_ = std::move(array_builder);
+    decode_context_.ClearBuilderMetadata();
     previous_first_row_ = std::numeric_limits<uint64_t>::max();
     previous_batch_row_count_ = 0;
     next_row_to_read_ = std::numeric_limits<uint64_t>::max();

Reply via email to