【问题标题】:Reading parquet file is slower in c++ than in python在 C++ 中读取镶木地板文件比在 python 中慢
【发布时间】:2021-06-06 06:43:19
【问题描述】:

我已经编写了代码来使用 c++ 和 python 读取相同的 parquet 文件。 python 读取文件所需的时间比 c++ 中的要少得多,但是众所周知,c++ 中的执行速度比 python 中的要快。我在这里附上了代码-

#include <arrow/api.h>
#include <parquet/arrow/reader.h>
#include <arrow/filesystem/localfs.h>
#include <chrono>
#include <iostream>

int main(){
   // ...
   arrow::Status st;
   arrow::MemoryPool* pool = arrow::default_memory_pool();
   arrow::fs::LocalFileSystem file_system;
   std::shared_ptr<arrow::io::RandomAccessFile> input = file_system.OpenInputFile("data.parquet").ValueOrDie();

   // Open Parquet file reader
   std::unique_ptr<parquet::arrow::FileReader> arrow_reader;
   st = parquet::arrow::OpenFile(input, pool, &arrow_reader);
   if (!st.ok()) {
      // Handle error instantiating file reader...
   }

   // Read entire file as a single Arrow table
   std::shared_ptr<arrow::Table> table;
   auto t1 = std::chrono::high_resolution_clock::now();
   st = arrow_reader->ReadTable(&table);
   auto t2 = std::chrono::high_resolution_clock::now();
   if (!st.ok()) {
      // Handle error reading Parquet data...
   }
   else{
       auto ms_int = std::chrono::duration_cast<std::chrono::milliseconds> (t2 - t1);
       std::cout << "Time taken to read parquet file is : " << ms_int.count() << "ms\n";
   }
}

我在python中使用的代码是-

#!/usr/bin/env python3
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
import time

start_time = time.time()

table = pq.read_table('data.parquet')

end_time = time.time()

print("Time taken to read parquet is : ",(end_time - start_time)*1000, "ms")

在为一个大小约为 87mb 的文件运行 c++ 代码时,c++ 的输出是 -

读取 parquet 文件所用时间:186ms

而对于 python,输出是 -

读取镶木地板的时间为:108.66141319274902 毫秒

为什么c++和python中函数read_table的执行时间会有这么大的差别?

【问题讨论】:

  • 要得到毫秒,你需要乘以 1000,而不是乘以 100。
  • 改变了,但python仍然更快,100毫秒与186毫秒相比,有什么可能的原因吗?
  • 我的猜测是python阅读器(很可能也是用C或C++编写的)使用频率更高,因此得到了更好的优化。不过,我不确定 SO 是否可以在此问题上得到明确的答案,如果有必要了解,您可能需要联系较慢 api 的作者。
  • 你运行过几次了吗?也许存在“缓存变暖”效应。
  • @SourabhKulhari -- 请提供您用于构建 C++ 程序的优化设置。任何涉及速度的 C++ 问题都不应遗漏这个非常 重要的信息。如果您正在运行未优化或“调试”构建,那么您显示的时间是没有意义的。您应该只进行时间优化(发布)构建。

标签: python c++ parquet pyarrow apache-arrow


【解决方案1】:

python pq.read_table 基于与您在示例中使用的完全相同的 C++ API(在后台它也使用 C++ parquet::arrow::FileReader),因为 Python 和 C++ API 都来自同一个 Arrow 项目.
因此,除了一点点 Python 调用堆栈开销外,预计这两种方式的执行方式相同。

但是,您可以指定/调整几个选项来提高性能,这可以解释您的情况的差异。例如,python 函数默认会并行读取文件(您可以指定use_threads=False 禁用此功能)。另一方面,C++ FileReader 默认不这样做(检查set_use_threads)。默认情况下,python 阅读器可能还会设置其他选项。
此外,编译 C++ 示例时的确切构建标志也会产生影响。

【讨论】:

  • 我使用 set_use_threads 命令,arrow_reader -> set_use_threads(1) 开启使用多线程,与使用单线程相比,它提高了 c++ 的性能,但性能不如我们在 python 中使用多线程的情况(python ~ 31ms,c++ ~ 75ms)。您能否在您自己的环境中运行这些并告诉我我得到的数字是否正确?
  • 另外,我看不到python中使用的任何其他优化。
  • 预缓冲可能是其余性能的解释。除了set_use_threads 之外,尝试将set_pre_buffer 添加到true。
【解决方案2】:

如果你想比较,试试这个 CPP 代码:

#include <cassert>
#include <chrono>
#include <cstdlib>
#include <iostream>

using namespace std::chrono;

#include <arrow/api.h>
#include <arrow/filesystem/api.h>
#include <parquet/arrow/reader.h>

using arrow::Result;
using arrow::Status;

namespace {

Result<std::unique_ptr<parquet::arrow::FileReader>> OpenReader() {
  arrow::fs::LocalFileSystem file_system;
  ARROW_ASSIGN_OR_RAISE(auto input, file_system.OpenInputFile("data.parquet"));

  parquet::ArrowReaderProperties arrow_reader_properties =
      parquet::default_arrow_reader_properties();

  arrow_reader_properties.set_pre_buffer(true);
  arrow_reader_properties.set_use_threads(true);

  parquet::ReaderProperties reader_properties =
      parquet::default_reader_properties();

  // Open Parquet file reader
  std::unique_ptr<parquet::arrow::FileReader> arrow_reader;
  auto reader_builder = parquet::arrow::FileReaderBuilder();
  reader_builder.properties(arrow_reader_properties);
  ARROW_RETURN_NOT_OK(reader_builder.Open(std::move(input), reader_properties));
  ARROW_RETURN_NOT_OK(reader_builder.Build(&arrow_reader));

  return arrow_reader;
}

Status RunMain(int argc, char **argv) {
  // Read entire file as a single Arrow table
  std::shared_ptr<arrow::Table> table;
  for (auto i = 0; i < 10; i++) {
    ARROW_ASSIGN_OR_RAISE(auto arrow_reader, OpenReader());
    auto t1 = std::chrono::high_resolution_clock::now();
    ARROW_RETURN_NOT_OK(arrow_reader->ReadTable(&table));
    std::cout << table->num_rows() << "," << table->num_columns() << std::endl;
    auto t2 = std::chrono::high_resolution_clock::now();
    auto ms_int =
        std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
    std::cout << "Time taken to read parquet file is : " << ms_int.count()
              << "ms\n";
  }

  return Status::OK();
}

} // namespace

int main(int argc, char **argv) {
  Status st = RunMain(argc, argv);
  if (!st.ok()) {
    std::cerr << st << std::endl;
    return 1;
  }
  return 0;
}

然后和这个python代码比较:

#!/usr/bin/env python3                                                                                                                                                                                     
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
import time

for i in range(10):
    parquet_file = pq.ParquetFile('/home/pace/experiments/so4/data.parquet', pre_buffer=True)
    start_time = time.time()
    table = parquet_file.read()
    end_time = time.time()
    print("Time taken to read parquet is : ",(end_time - start_time)*1000, "ms")

在我的系统上运行 10 次后,t 检验无法区分这两个分布 (p=0.64)。

【讨论】:

  • 在运行 python 脚本时,遇到了一个错误,意外的关键字参数 'pre_buffer',然后我查看了链接中的 Parquet 文件 - arrow.apache.org/docs/python/generated/…,它没有提到名称为 pre_buffer 的参数。 python如何使用pre_buffer?
  • 啊,是的。 4.0.0 似乎不完全支持预缓冲。 arg 将在 5.0.0 中出现。您可以尝试从 C++ 和 python 中提取预缓冲,您仍然应该得到类似的结果。
【解决方案3】:

Python 模块很可能绑定到使用 c++ 或使用 cython 等语言编译的函数。因此,python 模块的实现可能具有更好的性能,具体取决于它如何从文件中读取或处理数据。

【讨论】:

    【解决方案4】:

    1 秒是 1000 毫秒。所以差异并没有那么大。除此之外,python 的许多功能经常使用 CPython,这使它们处于非常公平的竞争环境中。然后它只取决于函数的编写和优化程度。在这种情况下,python 函数很可能比 C++ 函数更优化。

    【讨论】:

    • 所以如果我正在处理 parquet 文件,使用 python 是比 c++ 更好的选择,对吧?但是对于一个项目,我被要求使用 c++ 获得更好的性能,我个人认为使用内置库函数进行读写 parquet 是不可能获得更好的性能的。
    • @SourabhKulhari 在这种情况下,是的,python 是最好的选择。理论上可以在 C++ 中实现更好的性能,但是你一个人很难做到(除非有一些其他预先存在的库实现)。事实上,您使用的库函数是由比您和我聪明得多的人精心构思的,以尽可能提高效率。虽然我想你可以尝试找出 python 函数是如何在底层实现的,也许你可以从中汲取灵感?
    猜你喜欢
    • 2021-01-12
    • 2015-06-21
    • 2022-11-24
    • 1970-01-01
    • 2021-08-27
    • 2019-08-04
    • 2022-06-16
    • 2019-09-23
    • 2017-05-24
    相关资源
    最近更新 更多