Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cpp/src/arrow/dataset/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,7 @@ endfunction()

add_arrow_dataset_benchmark(file_benchmark)
add_arrow_dataset_benchmark(scanner_benchmark)

if(ARROW_PARQUET)
add_arrow_dataset_benchmark(parquet_scan_benchmark)
endif()
80 changes: 79 additions & 1 deletion cpp/src/arrow/dataset/file_parquet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@

#include "arrow/compute/cast.h"
#include "arrow/compute/exec.h"
#include "arrow/dataset/dataset.h"
#include "arrow/dataset/dataset_internal.h"
#include "arrow/dataset/parquet_encryption_config.h"
#include "arrow/dataset/scanner.h"
#include "arrow/filesystem/path_util.h"
#include "arrow/memory_pool.h"
#include "arrow/record_batch.h"
#include "arrow/result.h"
#include "arrow/table.h"
#include "arrow/type.h"
#include "arrow/type_fwd.h"
#include "arrow/util/checked_cast.h"
#include "arrow/util/future.h"
#include "arrow/util/iterator.h"
Expand Down Expand Up @@ -565,6 +571,66 @@ Future<std::shared_ptr<parquet::arrow::FileReader>> ParquetFileFormat::GetReader
});
}

struct CastingGenerator {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit curious why casting generator is required, since during reading parquet to arrow, Parquet reader already applies a round of casting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're probably right and it likely does perform some casts during the read. However, it doesn't seem to be doing it for certain cases like String to LargeString.
Could you point me to where the Parquet Reader should be performing this cast? I'm happy to add it there.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!
Based on what I see, that is only responsible for casting the data to the logical type specified in the parquet metadata and not the Arrow type we want to convert to (the one in the dataset_schema). For strings, that seems to always map to a String type (based on FromByteArray which is called by GetArrowType which is called by GetTypeForNode which is called by NodeToSchemaField which is called in SchemaManifest::Make during the creation of the LeafReader). Am I missing something?

@mapleFU mapleFU Aug 13, 2024

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what I see, that is only responsible for casting the data to the logical type specified in the parquet metadata and not the Arrow type we want to convert to (the one in the dataset_schema)

Parquet logical type doesn't have an arrow schema, isn't it? Binary reader reads from ::arrow::BinaryBuilder, and casting it to user-specified binary type.

For strings, that seems to always map to a String type (based on FromByteArray which is called by GetArrowType which is called by GetTypeForNode which is called by NodeToSchemaField which is called in SchemaManifest::Make during the creation of the LeafReader).

Yeah, you're right, the read "cast" with file-schema rather than an expected schema. I think a native cast is better here but this doesn't solve your problem, perhaps I can trying to add a naive SchemaManifest with hint solving here, but it would spend some time.

::arrow::Result<std::shared_ptr<ArrowType>> GetTypeForNode(
    int column_index, const schema::PrimitiveNode& primitive_node,
    SchemaTreeContext* ctx)

Maybe we should rethink the GetTypeForNode handling for string/large_string/stringView, or using some handle written type hint here. A casting generator is also good for me when the reader cannot provide the right casting

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide hint would looks like: apache/arrow-rs#5939

Maybe I can add separate issue for that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parquet logical type doesn't have an arrow schema, isn't it?

As far as I understand, the parquet metadata may or may not have the arrow schema. I believe it depends on the writer. It looks like it tries to get that using GetOriginSchema in SchemaManifest::Make. However, the schema at write time might not be the same as the schema the reader expects.

Binary reader reads from ::arrow::BinaryBuilder, and casting it to user-specified binary type.

Sorry, I didn't quite follow. Are you saying that we should use this to do the cast at read time somehow?

I think a native cast is better here but this doesn't solve your problem, perhaps I can trying to add a naive SchemaManifest with hint solving here, but it would spend some time.
Maybe we should rethink the GetTypeForNode handling for string/large_string/stringView, or using some handle written type hint here.

That makes sense to me.

Maybe I can add separate issue for that

That would be great, thanks!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just check this. We can first add a CastingGenerator, because sometimes we would have schema evolution here, and need cast to a "final type". The Parquet reader code can be regard as an optimization

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, thanks!

CastingGenerator(RecordBatchGenerator source, std::shared_ptr<Schema> final_schema,
const std::unordered_set<std::string>& cols_to_skip,
MemoryPool* pool = default_memory_pool())
: source_(source),
final_schema_(std::move(final_schema)),
cols_to_skip_(cols_to_skip),
exec_ctx(std::make_shared<compute::ExecContext>(pool)) {}

Future<std::shared_ptr<RecordBatch>> operator()() {
return this->source_().Then([this](const std::shared_ptr<RecordBatch>& next)
-> Result<std::shared_ptr<RecordBatch>> {
if (IsIterationEnd(next) || this->final_schema_ == nullptr) {
return next;
}
std::vector<std::shared_ptr<Array>> out_cols;
std::vector<std::shared_ptr<Field>> out_schema_fields;

bool changed = false;
for (const auto& field : this->final_schema_->fields()) {
FieldRef field_ref = FieldRef(field->name());
ARROW_ASSIGN_OR_RAISE(std::shared_ptr<Array> column,
field_ref.GetOneOrNone(*next));
if (column) {
if (this->cols_to_skip_.count(field->name())) {
// Maintain the original input type.
out_schema_fields.emplace_back(field->WithType(column->type()));
out_cols.emplace_back(std::move(column));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So cols_to_skip_ is just not "Project" this field, rather than not need this field?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, to skip the cast and leave them as they are.

continue;
}
if (!column->type()->Equals(field->type())) {
// Referenced field was present but didn't have the expected type.
ARROW_ASSIGN_OR_RAISE(
auto converted,
compute::Cast(column, field->type(), compute::CastOptions::Safe(),
this->exec_ctx.get()));
column = converted.make_array();
changed = true;
}
out_schema_fields.emplace_back(field->Copy());
out_cols.emplace_back(std::move(column));
}
}

if (changed) {
return RecordBatch::Make(std::make_shared<Schema>(std::move(out_schema_fields),
next->schema()->metadata()),
next->num_rows(), std::move(out_cols));
} else {
return next;
}
});
}

RecordBatchGenerator source_;
std::shared_ptr<Schema> final_schema_;
const std::unordered_set<std::string>& cols_to_skip_;
std::shared_ptr<compute::ExecContext> exec_ctx;
};

struct SlicingGenerator {
SlicingGenerator(RecordBatchGenerator source, int64_t batch_size)
: state(std::make_shared<State>(source, batch_size)) {}
Expand Down Expand Up @@ -627,6 +693,9 @@ Result<RecordBatchGenerator> ParquetFileFormat::ScanBatchesAsync(
[this, options, parquet_fragment, pre_filtered,
row_groups](const std::shared_ptr<parquet::arrow::FileReader>& reader) mutable
-> Result<RecordBatchGenerator> {
// Since we already do the batching through the SlicingGenerator, we don't need the
// reader to batch its output.
reader->set_batch_size(std::numeric_limits<int64_t>::max());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if you have a very large row group (say 1 GB or more), what this will do is read the entire file at once, then slice it, instead of generating batches progressively.

So this sounds like a bad idea to me. The entire purpose of ScanBatchesAsync is to generate batches progressively so that the consumer can process them without waiting for the entire file to be read. This allows covering IO latencies and also decreasing memory footprint (say you're computing the average of a column: it would be wasteful to load the entire column at once instead of having a running average computation over smaller batches).

@scott-routledge2 scott-routledge2 Oct 27, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my understanding of this example, wouldn't calling ScanBatchesAsync create a RowGroupGenerator, which would then call ReadOneRowGroup and read the entire row group into an in-memory table anyways? The batch size here seems to just control the chunk size of that table.

The goal of this PR was to reduce the overhead of casting batches of string offset buffers (which would require O(N^2) memory/compute where N is the length of a row group) by performing the cast on the entire table/row-group at once, which we will have at some point because of ReadOneRowGroup, so I don't think this represents a regression.

However, I understand wanting to keep the intended semantics of ScanBatchesAsync the same. Maybe instead we can perform the cast at a different layer of the stack e.g. at the reader level?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation @scott-routledge2 .

// Ensure that parquet_fragment has FileMetaData

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming a large file, would memory usage grows high in this case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have that problem regardless of the batch size of the reader.
We pass this reader to reader->GetRecordBatchGenerator (a few lines down). This eventually creates a RowGroupGenerator (

RowGroupGenerator(::arrow::internal::checked_pointer_cast<FileReaderImpl>(reader),
). If we look at the implementation of FetchNext for RowGroupGenerator, it essentially calls ReadOneRowGroup which reads the entire row group into a table and then creates an output stream using TableBatchReader (
table_reader.set_chunksize(batch_size);
). So, we're already reading an entire row group into a single table. The batch_size just creates a reader on top of it which generates zero-copy slices. This is the same as what the SlicingGenerator does and is redundant. In my opinion, it's better to set the batch size to INT_MAX so that it returns one table per row group, and then we can perform the batching through the SlicingGenerator.

RETURN_NOT_OK(parquet_fragment->EnsureCompleteMetadata(reader.get()));
if (!pre_filtered) {
Expand All @@ -649,8 +718,17 @@ Result<RecordBatchGenerator> ParquetFileFormat::ScanBatchesAsync(
ARROW_ASSIGN_OR_RAISE(auto generator, reader->GetRecordBatchGenerator(
reader, row_groups, column_projection,
cpu_executor, rows_to_readahead));
// We need to skip casting the dictionary columns since the dataset_schema doesn't
// have the dictionary-encoding information. Parquet reader will return them with the
// dictionary type, which is what we eventually want.
Comment on lines +721 to +723

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've forget something here, would Dict(String) and Cast(xxx -> LargeString) matters?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure, so I left that case untouched. There are more casts done further up the chain (e.g. in MakeExecBatch potentially) that seem to handle those cases. I couldn't figure out how the dictionary case gets handled either, so I left it as is. I'm happy to implement it here if you could point me in the right direction.

const std::unordered_set<std::string>& dict_cols =
parquet_fragment->parquet_format_.reader_options.dict_columns;
// Casting before slicing is more efficient. Casts on slices might require wasteful
// allocations and computation.
Comment on lines +726 to +727

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain which kind of wasteful allocations and computations would be required when casting a slice?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When casting the string offsets buffer, we allocate offset + length + 1 elements, memset the first offset elements to zero, and perform the cast on length many elements. So assuming batch_size = 1000, a row group of 10,000 elements would require allocating buffers of lengths 1001, 2001, ... 10,001 to perform the casts on all batches.

ctx->Allocate((output->length + output->offset + 1) * sizeof(output_offset_type)));

RecordBatchGenerator casted = CastingGenerator(
std::move(generator), options->dataset_schema, dict_cols, options->pool);
RecordBatchGenerator sliced =
SlicingGenerator(std::move(generator), options->batch_size);
SlicingGenerator(std::move(casted), options->batch_size);
if (batch_readahead == 0) {
return sliced;
}
Expand Down
94 changes: 94 additions & 0 deletions cpp/src/arrow/dataset/parquet_scan_benchmark.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

#include "arrow/testing/gtest_util.h"
#include "benchmark/benchmark.h"

#include "arrow/api.h"
#include "arrow/dataset/file_parquet.h"
#include "arrow/io/memory.h"
#include "parquet/arrow/writer.h"

namespace arrow {
namespace dataset {

using parquet::arrow::WriteTable;

Result<std::shared_ptr<Buffer>> WriteStringColParquetBuffer(int64_t nrows) {
auto schema = arrow::schema({arrow::field("my_string_col", arrow::utf8())});

arrow::StringBuilder builder;
for (int64_t i = 0; i < nrows; i++) {
ARROW_RETURN_NOT_OK(builder.Append("row_" + std::to_string(i)));
}
std::shared_ptr<arrow::Array> arr;
ARROW_RETURN_NOT_OK(builder.Finish(&arr));
auto table = arrow::Table::Make(schema, {arr});

ARROW_ASSIGN_OR_RAISE(auto sink, arrow::io::BufferOutputStream::Create());
ARROW_RETURN_NOT_OK(WriteTable(*table, arrow::default_memory_pool(), sink));
return sink->Finish();
}

static void ParquetScanToTableCastStrings(benchmark::State& state) {
// GH-43660: Scan parquet data including a String column using a dataset object with
// LargeString in schema.
size_t num_batches = state.range(0);
size_t batch_size = state.range(1);
size_t nrows = num_batches * batch_size;
auto format = std::make_shared<ParquetFileFormat>();

// Create a buffer with a single String column and wrap with FileFragment
ASSERT_OK_AND_ASSIGN(std::shared_ptr<Buffer> buffer,
WriteStringColParquetBuffer(nrows));
auto buffer_reader = std::make_shared<arrow::io::BufferReader>(buffer);
FileSource source(buffer_reader, buffer->size());
ASSERT_OK_AND_ASSIGN(auto fragment, format->MakeFragment(source));
std::vector<std::shared_ptr<FileFragment>> fragments{fragment};

// Create a dataset from FileFragment and set schema to LargeString (require casting).
auto schema = arrow::schema({field("my_string_col", arrow::large_utf8())});
ASSERT_OK_AND_ASSIGN(auto dataset, FileSystemDataset::Make(
schema, compute::literal(true), format,
/*filesystem=*/nullptr, std::move(fragments)));

ASSERT_OK_AND_ASSIGN(auto builder, dataset->NewScan());
ASSERT_OK(builder->BatchSize(batch_size));
ASSERT_OK_AND_ASSIGN(auto scanner, builder->Finish());

for (auto _ : state) {
ASSERT_OK_AND_ASSIGN(auto table, scanner->ToTable());
benchmark::DoNotOptimize(table);
}

state.SetItemsProcessed(state.iterations() * nrows);
}

static void ParquetScanBenchmark_Customize(benchmark::internal::Benchmark* b) {
std::vector<int64_t> num_batches = {1000, 100, 10};
std::vector<int64_t> batch_sizes = {1000, 10000, 100000};

for (size_t i = 0; i < batch_sizes.size(); i++) {
b->Args({(num_batches[i]), batch_sizes[i]});
}
b->ArgNames({"num_batches", "batch_size"});
}

BENCHMARK(ParquetScanToTableCastStrings)->Apply(ParquetScanBenchmark_Customize);

} // namespace dataset

@scott-routledge2 scott-routledge2 Sep 25, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local results (comparing to main):

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Non-regressions: (20)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
                                                                 benchmark           baseline          contender  change %                                                                                                                                                                                                                                counters
            ParquetScanToTableCastStrings/num_batches:1000/batch_size:1000   1.068M items/sec  10.574M items/sec   890.391                               {'family_index': 0, 'per_family_instance_index': 2, 'run_name': 'ParquetScanToTableCastStrings/num_batches:1000/batch_size:1000', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 1}
             ParquetScanToTableCastStrings/num_batches:1000/batch_size:100 761.915K items/sec   1.065M items/sec    39.760                                {'family_index': 0, 'per_family_instance_index': 1, 'run_name': 'ParquetScanToTableCastStrings/num_batches:1000/batch_size:100', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 4}
              ParquetScanToTableCastStrings/num_batches:1000/batch_size:10  86.631K items/sec 107.530K items/sec    24.124                                 {'family_index': 0, 'per_family_instance_index': 0, 'run_name': 'ParquetScanToTableCastStrings/num_batches:1000/batch_size:10', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 5}
       ScanOnlyBench/num_batches:1000/batch_size:1000/scan_alg:0/real_time     41.041 MiB/sec     49.586 MiB/sec    20.820                          {'family_index': 1, 'per_family_instance_index': 4, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:1000/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 6}
         ScanOnlyBench/num_batches:1000/batch_size:10/scan_alg:0/real_time    457.346 KiB/sec    518.384 KiB/sec    13.346                            {'family_index': 1, 'per_family_instance_index': 0, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:10/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 6}
        ScanOnlyBench/num_batches:1000/batch_size:100/scan_alg:0/real_time      4.421 MiB/sec      5.011 MiB/sec    13.339                           {'family_index': 1, 'per_family_instance_index': 2, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:100/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 6}
         ScanOnlyBench/num_batches:1000/batch_size:10/scan_alg:1/real_time    634.336 KiB/sec    718.653 KiB/sec    13.292                            {'family_index': 1, 'per_family_instance_index': 1, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:10/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
 MinimalEndToEndBench/num_batches:1000/batch_size:100/scan_alg:0/real_time      4.563 MiB/sec      5.089 MiB/sec    11.535                    {'family_index': 0, 'per_family_instance_index': 2, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:100/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 7}
  MinimalEndToEndBench/num_batches:1000/batch_size:10/scan_alg:1/real_time    624.277 KiB/sec    680.752 KiB/sec     9.047                     {'family_index': 0, 'per_family_instance_index': 1, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:10/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
  MinimalEndToEndBench/num_batches:1000/batch_size:10/scan_alg:0/real_time    483.179 KiB/sec    522.102 KiB/sec     8.055                     {'family_index': 0, 'per_family_instance_index': 0, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:10/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 7}
        ScanOnlyBench/num_batches:1000/batch_size:100/scan_alg:1/real_time      6.578 MiB/sec      7.080 MiB/sec     7.645                           {'family_index': 1, 'per_family_instance_index': 3, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:100/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
MinimalEndToEndBench/num_batches:1000/batch_size:1000/scan_alg:0/real_time     46.819 MiB/sec     49.794 MiB/sec     6.353                   {'family_index': 0, 'per_family_instance_index': 4, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:1000/scan_alg:0/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 7}
       ScanOnlyBench/num_batches:1000/batch_size:1000/scan_alg:1/real_time     67.173 MiB/sec     70.629 MiB/sec     5.145                          {'family_index': 1, 'per_family_instance_index': 5, 'run_name': 'ScanOnlyBench/num_batches:1000/batch_size:1000/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
                                          GetFilteredFragments/single_file   1.501M items/sec   1.557M items/sec     3.709  {'family_index': 3, 'per_family_instance_index': 0, 'run_name': 'GetFilteredFragments/single_file', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 105, 'num_filtered_fragments': 1.0, 'num_fragments': 10000.0}
 MinimalEndToEndBench/num_batches:1000/batch_size:100/scan_alg:1/real_time      6.327 MiB/sec      6.481 MiB/sec     2.430                    {'family_index': 0, 'per_family_instance_index': 3, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:100/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
                                                GetFilteredFragments/range 229.364K items/sec 232.693K items/sec     1.451      {'family_index': 4, 'per_family_instance_index': 0, 'run_name': 'GetFilteredFragments/range', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 16, 'num_filtered_fragments': 9800.0, 'num_fragments': 10000.0}
MinimalEndToEndBench/num_batches:1000/batch_size:1000/scan_alg:1/real_time     61.721 MiB/sec     62.435 MiB/sec     1.156                   {'family_index': 0, 'per_family_instance_index': 5, 'run_name': 'MinimalEndToEndBench/num_batches:1000/batch_size:1000/scan_alg:1/real_time', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 9}
                                           GetFilteredFragments/single_dir   3.419M items/sec   3.444M items/sec     0.733 {'family_index': 1, 'per_family_instance_index': 0, 'run_name': 'GetFilteredFragments/single_dir', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 236, 'num_filtered_fragments': 100.0, 'num_fragments': 10000.0}
                                            GetFilteredFragments/multi_dir  40.060K items/sec  40.215K items/sec     0.386    {'family_index': 2, 'per_family_instance_index': 0, 'run_name': 'GetFilteredFragments/multi_dir', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 3, 'num_filtered_fragments': 100.0, 'num_fragments': 10000.0}
                                                           GetAllFragments  35.682M items/sec  35.433M items/sec    -0.698                                                 {'family_index': 0, 'per_family_instance_index': 0, 'run_name': 'GetAllFragments', 'repetitions': 1, 'repetition_index': 0, 'threads': 1, 'iterations': 2282, 'num_fragments': 10000.0}


} // namespace arrow
Loading