Skip to content

Instantly share code, notes, and snippets.

@arrieta
Created October 18, 2021 15:04
Show Gist options
  • Save arrieta/97438876b9db784beffb0de8d27f1dc9 to your computer and use it in GitHub Desktop.
Save arrieta/97438876b9db784beffb0de8d27f1dc9 to your computer and use it in GitHub Desktop.
Read whole file in C++ std::string
// 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]);
}
@arrieta
Copy link
Author

arrieta commented Oct 18, 2021

Implementation of slurp.hpp with the best version.

#pragma once

#include <cassert>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <string>

namespace nzl {

/// @brief Slurp the contents of a file into a string.
/// @param[in] path Path to the file.
/// @return String with whole file contents.
/// @throw std::runtime_error if file cannot be read.
inline std::string slurp(const std::string& path) {
  auto fp = std::fopen(path.c_str(), "rb");

  if (fp == nullptr) {
    throw std::runtime_error(path + ": " + std::strerror(errno));
  }

  std::fseek(fp, 0u, SEEK_END);
  const auto size = std::ftell(fp);
  std::fseek(fp, 0u, SEEK_SET);

  std::string s;
  s.resize(size);

  const auto read = std::fread(&s[0], 1u, size, fp);
  std::fclose(fp);

  assert(read == size);

  return s;
}

}  // namespace nzl

@leezaj
Copy link

leezaj commented Aug 1, 2024

Thanks. On my platform I needed to #include <iomanip> as well as changing line 240 to
const auto time = duration_cast<microseconds>(cpu_end - cpu_beg).count();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment