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

pitrou pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 6b419a1035b GH-48977: [C++] Fix quadratic field name index 
construction on libc++ (#50970)
6b419a1035b is described below

commit 6b419a1035bdf3bd413138a004ca4ca2d597cfd2
Author: Advit Arora <[email protected]>
AuthorDate: Tue Aug 25 12:41:06 2026 +0530

    GH-48977: [C++] Fix quadratic field name index construction on libc++ 
(#50970)
    
    ### Rationale for this change
    
    Building a schema with 500,000 identically named fields takes about 300 
seconds.
    
    `CreateNameToIndexMap` fills an `unordered_multimap` with one bare 
`emplace` per field. libc++ keeps equal keys contiguous, so each unhinted 
insert walks to the end of the equal range before splicing, making the loop 
quadratic. libstdc++ splices next to the first match and is already linear.
    
    ### What changes are included in this PR?
    
    `emplace_hint(find(name), ...)`, the shape arvidjonasson worked out on the 
issue from the LLVM bug kou linked. `reserve(fields.size())` goes in alongside 
to pay for the extra `find`. `SchemaBuilder::Impl::AppendField` had the same 
pattern, and `Schema`'s copy constructor now rebuilds from `fields_` instead of 
copying the multimap node by node.
    
    ### Are these changes tested?
    
    `arrow-type-test` passes 157/157. Nothing observable changes, so no new 
unit test. `type_benchmark.cc` gains a schema construction case: 
`duplicate_names/10000` goes from 117 ms to 0.41 ms, `distinct_names` is level 
to 7% faster.
    
    ### Are there any user-facing changes?
    
    Fields sharing a name come back from `GetAllFieldsByName` in a different 
order. That order isn't a contract: `GetAllFieldIndices` sorts, both pyarrow 
callers reject more than one field, and the two standard libraries already 
disagree.
    
    * GitHub Issue: #48977
    
    Authored-by: Advit Arora <[email protected]>
    Signed-off-by: Antoine Pitrou <[email protected]>
---
 cpp/src/arrow/type.cc           | 11 ++++++++---
 cpp/src/arrow/type_benchmark.cc | 22 ++++++++++++++++++++++
 2 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/cpp/src/arrow/type.cc b/cpp/src/arrow/type.cc
index 1dedf1978da..e841f091809 100644
--- a/cpp/src/arrow/type.cc
+++ b/cpp/src/arrow/type.cc
@@ -1333,8 +1333,11 @@ namespace {
 std::unordered_multimap<std::string_view, int> CreateNameToIndexMap(
     const FieldVector& fields) {
   std::unordered_multimap<std::string_view, int> name_to_index;
+  name_to_index.reserve(fields.size());
   for (size_t i = 0; i < fields.size(); ++i) {
-    name_to_index.emplace(fields[i]->name(), static_cast<int>(i));
+    const std::string_view name = fields[i]->name();
+    // The find() hint avoids libc++'s quadratic scan of equal keys on plain 
emplace.
+    name_to_index.emplace_hint(name_to_index.find(name), name, 
static_cast<int>(i));
   }
   return name_to_index;
 }
@@ -2294,7 +2297,7 @@ Schema::Schema(FieldVector fields, std::shared_ptr<const 
KeyValueMetadata> metad
       impl_(new Impl(std::move(fields), Endianness::Native, 
std::move(metadata))) {}
 
 Schema::Schema(const Schema& schema)
-    : detail::Fingerprintable(), impl_(new Impl(*schema.impl_)) {}
+    : Schema(schema.impl_->fields_, schema.impl_->endianness_, 
schema.impl_->metadata_) {}
 
 Schema::~Schema() = default;
 
@@ -2568,7 +2571,9 @@ class SchemaBuilder::Impl {
   }
 
   Status AppendField(const std::shared_ptr<Field>& field) {
-    name_to_index_.emplace(field->name(), static_cast<int>(fields_.size()));
+    const std::string_view name = field->name();
+    name_to_index_.emplace_hint(name_to_index_.find(name), name,
+                                static_cast<int>(fields_.size()));
     fields_.push_back(field);
     return Status::OK();
   }
diff --git a/cpp/src/arrow/type_benchmark.cc b/cpp/src/arrow/type_benchmark.cc
index f502c4b7c86..6544e3e4f99 100644
--- a/cpp/src/arrow/type_benchmark.cc
+++ b/cpp/src/arrow/type_benchmark.cc
@@ -170,6 +170,20 @@ static void SchemaEqualsWithMetadata(
   state.SetItemsProcessed(state.iterations() * 2);
 }
 
+static void SchemaConstruct(benchmark::State& state,  // NOLINT non-const 
reference
+                            bool duplicate_names) {
+  const int num_fields = static_cast<int>(state.range(0));
+  FieldVector fields(num_fields);
+  for (int i = 0; i < num_fields; ++i) {
+    fields[i] = field(duplicate_names ? "f" : "f" + std::to_string(i), 
int32());
+  }
+
+  for (auto _ : state) {
+    benchmark::DoNotOptimize(::arrow::schema(fields));
+  }
+  state.SetItemsProcessed(state.iterations() * num_fields);
+}
+
 // ------------------------------------------------------------------------
 // Micro-benchmark various error reporting schemes
 
@@ -541,6 +555,14 @@ BENCHMARK(TypeEqualsComplex);
 BENCHMARK(TypeEqualsWithMetadata);
 BENCHMARK(SchemaEquals);
 BENCHMARK(SchemaEqualsWithMetadata);
+BENCHMARK_CAPTURE(SchemaConstruct, distinct_names, /*duplicate_names=*/false)
+    ->Arg(100)
+    ->Arg(1000)
+    ->Arg(10000);
+BENCHMARK_CAPTURE(SchemaConstruct, duplicate_names, /*duplicate_names=*/true)
+    ->Arg(100)
+    ->Arg(1000)
+    ->Arg(10000);
 
 BENCHMARK(ErrorSchemeNoError);
 BENCHMARK(ErrorSchemeBool);

Reply via email to