首页 > 解决方案 > 计算文件的数据字节平均值

问题描述

只是为了好玩,我正在尝试计算文件的数据字节平均值,本质上是复制现有工具(ent)中可用的功能。基本上,它只是将文件的所有字节相加并除以文件长度的结果。如果数据接近随机,这应该是大约 127.5。我正在测试两种计算平均值的方法,一种是for在 an 上工作的简单循环,另一种是直接在对象上unordered_map使用std::accumulate 。string

对这两种方法进行基准测试表明,它std::accumulate比简单的for循环使用起来要慢得多。此外,在我的系统上测量,平均而言,clang++累积方法比g++.

所以这是我的问题:

  1. 为什么for循环方法会在大约 2.5GB 的输入时产生错误的输出,g++但对于clang++. 我的猜测是我做错了(可能是 UB),但它们恰好与clang++. (已解决并相应修改代码)

  2. 为什么使用相同的优化设置该std::accumulate方法会慢得多?g++

谢谢!


编译器信息(目标是x86_64-pc-linux-gnu):

clang version 11.1.0

gcc version 11.1.0 (GCC)

构建信息:

g++ -Wall -Wextra -pedantic -O3 -DNDEBUG -std=gnu++2a main.cpp -o main-g

clang++ -Wall -Wextra -pedantic -O3 -DNDEBUG -std=gnu++20 main.cpp -o main-clang

示例文件(使用随机数据):

dd if=/dev/urandom iflag=fullblock bs=1G count=8 of=test-8g.bin(以 8GB 随机数据文件为例)

代码:

#include <chrono>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
#include <unordered_map>

auto main(int argc, char** argv) -> int {
  using std::cout;

  std::filesystem::path file_path{};

  if (argc == 2) {
    file_path = std::filesystem::path(argv[1]);
  } else {
    return 1;
  }

  std::string input{};
  std::unordered_map<char, int> char_map{};

  std::ifstream istrm(file_path, std::ios::binary);
  if (!istrm.is_open()) {
    throw std::runtime_error("Could not open file");
  }

  const auto file_size = std::filesystem::file_size(file_path);
  input.resize(file_size);
  istrm.read(input.data(), static_cast<std::streamsize>(file_size));

  istrm.close();

  // store frequency of individual chars in unordered_map
  for (const auto& c : input) {
    if (!char_map.contains(c)) {
      char_map.insert(std::pair<char, int>(c, 1));
    } else {
      char_map[c]++;
    }
  }

  double sum_for_loop = 0.0;

  cout << "using for loop\n";
  // start stopwatch
  auto start_timer = std::chrono::steady_clock::now();

  // for loop method
  for (const auto& item : char_map) {
    sum_for_loop += static_cast<unsigned char>(item.first) * static_cast<double>(item.second);
  }

  // stop stopwatch
  cout << std::chrono::duration<double>(std::chrono::steady_clock::now() - start_timer).count() << " s\n";

  auto mean_for_loop = static_cast<double>(sum_for_loop) / static_cast<double>(input.size());

  cout << std::fixed << "sum_for_loop: " << sum_for_loop << " size: " << input.size() << '\n';
  cout << "mean value of data bytes: " << mean_for_loop << '\n';

  cout << "using accumulate()\n";
  // start stopwatch
  start_timer = std::chrono::steady_clock::now();

  // accumulate method, but is slow (much slower in g++)
  auto sum_accum =
      std::accumulate(input.begin(), input.end(), 0.0, [](auto current_val, auto each_char) { return current_val + static_cast<unsigned char>(each_char); });

  // stop stopwatch
  cout << std::chrono::duration<double>(std::chrono::steady_clock::now() - start_timer).count() << " s\n";

  auto mean_accum = sum_accum / static_cast<double>(input.size());

  cout << std::fixed << "sum_for_loop: " << sum_accum << " size: " << input.size() << '\n';
  cout << "mean value of data bytes: " << mean_accum << '\n';
}

2GB 文件 ( ) 的示例输出clang++

using for loop
2.024e-05 s
sum_for_loop: 273805913805 size: 2147483648
mean value of data bytes: 127.500814
using accumulate()
1.317576 s
sum_for_loop: 273805913805.000000 size: 2147483648
mean value of data bytes: 127.500814

2GB 文件 ( ) 的示例输出g++

using for loop
2.41e-05 s
sum_for_loop: 273805913805 size: 2147483648
mean value of data bytes: 127.500814
using accumulate()
5.269024 s
sum_for_loop: 273805913805.000000 size: 2147483648
mean value of data bytes: 127.500814

8GB 文件 ( ) 的示例输出clang++

using for loop
1.853e-05 s
sum_for_loop: 1095220441576 size: 8589934592
mean value of data bytes: 127.500440
using accumulate()
5.247585 s
sum_for_loop: 1095220441576.000000 size: 8589934592
mean value of data bytes: 127.500440

8GB 文件 ( ) 的示例输出g++

using for loop
7.5e-07 s
sum_for_loop: 1095220441576.000000 size: 8589934592
mean value of data bytes: 127.500440
using accumulate()
21.484348 s
sum_for_loop: 1095220441576.000000 size: 8589934592
mean value of data bytes: 127.500440

标签: c++c++20unordered-mapaccumulate

解决方案


代码有很多问题。第一个 - 也是导致您的显示问题的那个 -sum_for_loop应该是一个double,而不是一个unsigned long。总和溢出了可以存储在 unsigned long 中的内容,导致发生这种情况时结果不正确。

计时器应在之后启动cout,否则您将在计算时间中包含输出时间。此外,“for 循环”经过的时间不包括构造char_map.

构建时char_map,您不需要if. 如果在映射中找不到条目,​​则将其初始化为零。更好的方法(因为您只有 256 个唯一值)是使用索引向量(记住将 to 强制char转换unsigned char)。


推荐阅读