c++ - 计算文件的数据字节平均值
问题描述
只是为了好玩,我正在尝试计算文件的数据字节平均值,本质上是复制现有工具(ent)中可用的功能。基本上,它只是将文件的所有字节相加并除以文件长度的结果。如果数据接近随机,这应该是大约 127.5。我正在测试两种计算平均值的方法,一种是for
在 an 上工作的简单循环,另一种是直接在对象上unordered_map
使用std::accumulate 。string
对这两种方法进行基准测试表明,它std::accumulate
比简单的for
循环使用起来要慢得多。此外,在我的系统上测量,平均而言,clang++
累积方法比g++
.
所以这是我的问题:
为什么
for
循环方法会在大约 2.5GB 的输入时产生错误的输出,g++
但对于clang++
. 我的猜测是我做错了(可能是 UB),但它们恰好与clang++
. (已解决并相应修改代码)为什么使用相同的优化设置该
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
解决方案
代码有很多问题。第一个 - 也是导致您的显示问题的那个 -sum_for_loop
应该是一个double
,而不是一个unsigned long
。总和溢出了可以存储在 unsigned long 中的内容,导致发生这种情况时结果不正确。
计时器应在之后启动cout
,否则您将在计算时间中包含输出时间。此外,“for 循环”经过的时间不包括构造char_map
.
构建时char_map
,您不需要if
. 如果在映射中找不到条目,则将其初始化为零。更好的方法(因为您只有 256 个唯一值)是使用索引向量(记住将 to 强制char
转换unsigned char
)。
推荐阅读
- git - 无法 ping github,但可以在我的工作场所通过浏览器访问 github。我运气不好?
- flutter - Flutter 错误原因:SocketException: Failed to create server socket (OS Error: Failed to start accept), address = localhost, port = xxxx
- python - Altair 生成带有水平连接表的水平条形图
- ios - 尝试制作 UIAlertController,似乎无法初始化
- xamarin - 设置 BindingContext 后调用 Xamarin 表单的 Prism Initialize
- automation - 通过自动完成相同的工作
- python - 需要两个参数或都不使用 argparse
- php - 使用 uasort() php 按多个条件排序
- amazon-ec2 - Ansible : MODULE FAILURE\n请参阅 stdout/stderr 以了解确切的错误
- rust - 如何使用嵌入式 HAL 在 Rust 中配置 UART?