Created
October 18, 2021 15:04
-
-
Save arrieta/97438876b9db784beffb0de8d27f1dc9 to your computer and use it in GitHub Desktop.
Read whole file in C++ std::string
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// slurp-test.cpp. | |
// | |
// Benchmark several methods to read a file into a C++ std::string. Partly based | |
// on the answers to my question: | |
// | |
// https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring | |
// | |
// asked on April 08, 2010. | |
// | |
// Tests on several files with different sizes (from a few bytes to a couple of | |
// gigabytes) were conducted on a computer running macOS Big Sur 11.6. Slurp | |
// versions V5 and V5 seem to perform significantly better than the other | |
// alternatives, delivering read speeds of between 4300 and 4700 | |
// MB/sec. Meanwhile, slurp versions V1 and V4 delivers the worst performance | |
// (between 150 and 220 MB/sec). | |
// | |
// The computer has the following hardware specifications. It is possible | |
// (though this was not verified) that our conclusions could be significantly | |
// different if the tests had been run on computers with different hardware. | |
// | |
// Model Name: iMac | |
// Model Identifier: iMac15,1 | |
// Processor Name: Quad-Core Intel Core i7 | |
// Processor Speed: 4 GHz | |
// Number of Processors: 1 | |
// Total Number of Cores: 4 | |
// L2 Cache (per Core): 256 KB | |
// L3 Cache: 8 MB | |
// Hyper-Threading Technology: Enabled | |
// Memory: 32 GB | |
// | |
// Vendor: Apple | |
// Product: SSD Controller | |
// Physical Interconnect: PCI | |
// Link Width: x2 | |
// Link Speed: 5.0 GT/s | |
// Description: AHCI Version 1.30 Supported | |
// | |
// APPLE SSD SM1024F: | |
// | |
// Capacity: 1 TB (1,000,555,581,440 bytes) | |
// Model: APPLE SSD SM1024F | |
// Revision: UXM6JA1Q | |
// Native Command Queuing: Yes | |
// Queue Depth: 32 | |
// Removable Media: No | |
// Detachable Drive: No | |
// BSD Name: disk0 | |
// Medium Type: Solid State | |
// TRIM Support: Yes | |
// Bay Name: SSD | |
// Partition Map Type: GPT (GUID Partition Table) | |
// S.M.A.R.T. status: Verified | |
// Volumes: | |
// disk0s2: | |
// Capacity: 1 TB (1,000,345,825,280 bytes) | |
// BSD Name: disk0s2 | |
// Content: Apple_APFS | |
// | |
// Compiler and compiler options used for the benchmark: | |
// | |
// $ /usr/bin/clang++ --version | |
// Apple clang version 13.0.0 (clang-1300.0.29.3) | |
// Target: x86_64-apple-darwin20.6.0 | |
// Thread model: posix | |
// | |
// $ clang++ slurp-test.cpp -o slurp-test -std=c++20 \ | |
// -Wall -Wextra -O3 -march=native | |
// | |
// Sample output on a 119,741,440-byte (114 Mb) file: | |
// | |
// $ ./slurp-test file | |
// V1: 634923 μsec, 179.855 MB/sec | |
// V1: 531180 μsec, 214.982 MB/sec | |
// V1: 526570 μsec, 216.864 MB/sec | |
// V1: 526736 μsec, 216.796 MB/sec | |
// V1: 528415 μsec, 216.107 MB/sec | |
// ==================== | |
// V2: 360654 μsec, 316.631 MB/sec | |
// V2: 309933 μsec, 368.448 MB/sec | |
// V2: 318911 μsec, 358.076 MB/sec | |
// V2: 314319 μsec, 363.307 MB/sec | |
// V2: 316232 μsec, 361.109 MB/sec | |
// ==================== | |
// V3: 57627 μsec, 1981.61 MB/sec | |
// V3: 57295 μsec, 1993.09 MB/sec | |
// V3: 56192 μsec, 2032.22 MB/sec | |
// V3: 57038 μsec, 2002.07 MB/sec | |
// V3: 56718 μsec, 2013.37 MB/sec | |
// ==================== | |
// V4: 553348 μsec, 206.37 MB/sec | |
// V4: 553840 μsec, 206.187 MB/sec | |
// V4: 546298 μsec, 209.033 MB/sec | |
// V4: 548282 μsec, 208.277 MB/sec | |
// V4: 549178 μsec, 207.937 MB/sec | |
// ==================== | |
// V5: 24992 μsec, 4569.24 MB/sec | |
// V5: 25241 μsec, 4524.16 MB/sec | |
// V5: 24588 μsec, 4644.31 MB/sec | |
// V5: 25366 μsec, 4501.87 MB/sec | |
// V5: 24463 μsec, 4668.04 MB/sec | |
// ==================== | |
// V6: 26066 μsec, 4380.97 MB/sec | |
// V6: 24739 μsec, 4615.96 MB/sec | |
// V6: 25871 μsec, 4413.99 MB/sec | |
// V6: 24453 μsec, 4669.95 MB/sec | |
// V6: 27580 μsec, 4140.48 MB/sec | |
// ==================== | |
// | |
// Sample output on slurp-test executable, 66,648 bytes (65K): | |
// | |
// $ ./slurp-test slurp-test | |
// | |
// V1: 463 μsec, 137.28 MB/sec | |
// V1: 426 μsec, 149.203 MB/sec | |
// V1: 344 μsec, 184.769 MB/sec | |
// V1: 490 μsec, 129.715 MB/sec | |
// V1: 345 μsec, 184.233 MB/sec | |
// ==================== | |
// V2: 219 μsec, 290.231 MB/sec | |
// V2: 185 μsec, 343.57 MB/sec | |
// V2: 190 μsec, 334.529 MB/sec | |
// V2: 170 μsec, 373.885 MB/sec | |
// V2: 170 μsec, 373.885 MB/sec | |
// ==================== | |
// V3: 64 μsec, 993.133 MB/sec | |
// V3: 59 μsec, 1077.3 MB/sec | |
// V3: 46 μsec, 1381.75 MB/sec | |
// V3: 57 μsec, 1115.1 MB/sec | |
// V3: 48 μsec, 1324.18 MB/sec | |
// ==================== | |
// V4: 356 μsec, 178.541 MB/sec | |
// V4: 310 μsec, 205.034 MB/sec | |
// V4: 309 μsec, 205.697 MB/sec | |
// V4: 309 μsec, 205.697 MB/sec | |
// V4: 309 μsec, 205.697 MB/sec | |
// ==================== | |
// V5: 24 μsec, 2648.35 MB/sec | |
// V5: 30 μsec, 2118.68 MB/sec | |
// V5: 20 μsec, 3178.02 MB/sec | |
// V5: 29 μsec, 2191.74 MB/sec | |
// V5: 29 μsec, 2191.74 MB/sec | |
// ==================== | |
// V6: 40 μsec, 1589.01 MB/sec | |
// V6: 20 μsec, 3178.02 MB/sec | |
// V6: 29 μsec, 2191.74 MB/sec | |
// V6: 29 μsec, 2191.74 MB/sec | |
// V6: 18 μsec, 3531.14 MB/sec | |
// ==================== | |
// | |
// (C) 2021 Nabla Zero Labs. | |
#include <chrono> | |
#include <cstdio> | |
#include <fstream> | |
#include <iostream> | |
#include <iterator> | |
#include <sstream> | |
namespace slurp { | |
inline namespace v1 { | |
std::string file(std::string path) { | |
std::ifstream fp(path, std::ios::in | std::ios::binary); | |
return {std::istreambuf_iterator<char>{fp}, std::istreambuf_iterator<char>{}}; | |
} | |
} // namespace v1 | |
namespace v2 { | |
std::string file(std::string path) { | |
std::ifstream fp(path, std::ios::in | std::ios::binary); | |
std::stringstream buffer; | |
buffer << fp.rdbuf(); | |
return buffer.str(); | |
} | |
} // namespace v2 | |
namespace v3 { | |
std::string file(std::string path) { | |
std::ifstream fp(path, std::ios::in | std::ios::binary | std::ios::ate); | |
const auto size = fp.tellg(); | |
std::string s; | |
s.resize(size); | |
fp.seekg(0u); | |
fp.read(&s[0], size); | |
return s; | |
} | |
} // namespace v3 | |
namespace v4 { | |
std::string file(std::string path) { | |
std::ifstream t(path); | |
std::string str; | |
t.seekg(0, std::ios::end); | |
str.reserve(t.tellg()); | |
t.seekg(0, std::ios::beg); | |
str.assign((std::istreambuf_iterator<char>(t)), | |
std::istreambuf_iterator<char>()); | |
return str; | |
} | |
} // namespace v4 | |
namespace v5 { | |
std::string file(std::string path) { | |
auto fp = std::fopen(path.c_str(), "rb"); | |
std::string s; | |
std::fseek(fp, 0u, SEEK_END); | |
auto size = std::ftell(fp); | |
std::fseek(fp, 0u, SEEK_SET); | |
s.resize(size); | |
std::fread(&s[0], 1u, size, fp); | |
std::fclose(fp); | |
return s; | |
} | |
} // namespace v5 | |
namespace v6 { | |
std::string file(std::string path) { | |
auto fp = std::fopen(path.c_str(), "rb"); | |
std::fseek(fp, 0u, SEEK_END); | |
auto size = std::ftell(fp); | |
std::fseek(fp, 0u, SEEK_SET); | |
std::string s(size, '\0'); | |
std::fread(&s[0], 1u, size, fp); | |
std::fclose(fp); | |
return s; | |
} | |
} // namespace v6 | |
} // namespace slurp | |
template <typename Func> | |
void time(Func f, const char* name, const char* path) { | |
for (auto k = 0; k < 5; ++k) { | |
using namespace std::chrono; | |
const auto cpu_beg = system_clock::now(); | |
const auto data = f(path); | |
const auto cpu_end = system_clock::now(); | |
const auto time = microseconds(cpu_end - cpu_beg).count(); | |
const auto mb = data.size() / (1024.0 * 1024.0); | |
const auto rate = mb / (time / 1.0E6); | |
std::cout << std::setprecision(6) << name << ": " << std::setw(10) << time | |
<< " μsec, " << std::setw(10) << rate << " MB/sec\n"; | |
} | |
std::cout << "====================\n"; | |
} | |
int main(int argc, char* argv[]) { | |
if (argc != 2) { | |
std::cerr << "usage: slurp-test PATH\n"; | |
return 1; | |
} | |
time(slurp::v1::file, "V1", argv[1]); | |
time(slurp::v2::file, "V2", argv[1]); | |
time(slurp::v3::file, "V3", argv[1]); | |
time(slurp::v4::file, "V4", argv[1]); | |
time(slurp::v5::file, "V5", argv[1]); | |
time(slurp::v6::file, "V6", argv[1]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks. On my platform I needed to
#include <iomanip>
as well as changing line 240 toconst auto time = duration_cast<microseconds>(cpu_end - cpu_beg).count();