[libc++] Optimize ofstream::write (#123337)

```
----------------------------
Benchmark       old      new
----------------------------
bm_write    1382 ns   521 ns
```
This commit is contained in:
Nikolas Klauser
2025-08-27 14:50:37 +02:00
committed by GitHub
parent bae177241c
commit c5949f74bf
7 changed files with 193 additions and 3 deletions

View File

@@ -58,6 +58,9 @@ Improvements and New Features
- The performance of ``insert(iterator, iterator)`` of ``multimap`` and ``multiset`` has been improved by up to 2.5x
- The performance of ``erase(iterator, iterator)`` in the unordered containers has been improved by up to 1.9x
- ``ofstream::write`` has been optimized to pass through large strings to system calls directly instead of copying them
in chunks into a buffer.
Deprecations and Removals
-------------------------

View File

@@ -299,6 +299,16 @@ protected:
int sync() override;
void imbue(const locale& __loc) override;
_LIBCPP_HIDE_FROM_ABI_VIRTUAL streamsize xsputn(const char_type* __str, streamsize __len) override {
if (__always_noconv_ && __len >= (this->epptr() - this->pbase())) {
if (traits_type::eq_int_type(overflow(), traits_type::eof()))
return 0;
return std::fwrite(__str, sizeof(char_type), __len, __file_);
}
return basic_streambuf<_CharT, _Traits>::xsputn(__str, __len);
}
private:
char* __extbuf_;
const char* __extbufnext_;

View File

@@ -0,0 +1,25 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include <fstream>
#include <vector>
#include <benchmark/benchmark.h>
static void bm_write(benchmark::State& state) {
std::vector<char> buffer;
buffer.resize(16384);
std::ofstream stream("/dev/null");
for (auto _ : state)
stream.write(buffer.data(), buffer.size());
}
BENCHMARK(bm_write);
BENCHMARK_MAIN();

View File

@@ -15,8 +15,10 @@
// UNSUPPORTED: no-wide-characters
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <fstream>
std::basic_filebuf<char, std::char_traits<wchar_t> > f;
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
// expected-error@*:* 9 {{only virtual member functions can be marked 'override'}}
// expected-error@*:* 10 {{only virtual member functions can be marked 'override'}}

View File

@@ -15,13 +15,15 @@
// UNSUPPORTED: no-wide-characters
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
#include <fstream>
std::basic_fstream<char, std::char_traits<wchar_t> > f;
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
// expected-error@*:* 11 {{only virtual member functions can be marked 'override'}}
// expected-error@*:* 12 {{only virtual member functions can be marked 'override'}}
// FIXME: As of commit r324062 Clang incorrectly generates a diagnostic about mismatching
// exception specifications for types which are already invalid for one reason or another.

View File

@@ -29,7 +29,6 @@ int main(int, char**)
| std::ios_base::trunc) != 0);
assert(f.is_open());
f.sputn("abcdefghijklmnopqrstuvwxyz", 26);
LIBCPP_ASSERT(buf[0] == 'v');
pos_type p = f.pubseekoff(-15, std::ios_base::cur);
assert(p == 11);
assert(f.sgetc() == 'l');

View File

@@ -0,0 +1,149 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03
// <fstream>
// streamsize xsputn(const char_type*, streamsize) override;
// This isn't a required override by the standard, but most implementations override it, since it allows for
// significantly improved performance in some cases. All of this code is required to work, so this isn't a libc++
// extension
#include <algorithm>
#include <cassert>
#include <codecvt>
#include <cstring>
#include <fstream>
#include <locale>
#include <vector>
#include "test_macros.h"
typedef std::filebuf::pos_type pos_type;
typedef std::filebuf::off_type off_type;
void sputn_seekoff(char* buf,
const size_t buf_size,
const std::streamsize chunk_size1,
const off_type offset1,
const std::streamsize chunk_size2) {
std::string data{"abcdefghijklmnopqrstuvwxyz"};
const std::streamsize data_size = static_cast<std::streamsize>(data.size());
assert(chunk_size1 <= data_size);
assert(chunk_size2 <= data_size);
// vector with expected data in the file to be written
std::size_t result_size = 5 + chunk_size1 + chunk_size2 + 1;
if (offset1 > 0) {
result_size += offset1;
}
std::vector<char> result(result_size, 0);
{
std::filebuf f;
f.pubsetbuf(buf, buf_size);
assert(f.open("sputn_seekoff.dat", std::ios_base::out) != 0);
assert(f.is_open());
assert(f.pubseekoff(off_type(5), std::ios_base::beg) = off_type(5));
std::vector<char> chunk(data.begin() + 5, data.begin() + 5 + chunk_size1);
std::copy(chunk.begin(), chunk.end(), result.begin() + 5);
const std::streamsize len1 = f.sputn(chunk.data(), chunk_size1);
assert(len1 == chunk_size1);
// check that nothing in the original chunk was modified by sputn()
assert(std::strncmp(chunk.data(), data.substr(5, len1).c_str(), len1) == 0);
pos_type p1 = f.pubseekoff(offset1, std::ios_base::cur);
char c;
if (p1 < 0) {
p1 = f.pubseekoff(0, std::ios_base::beg);
assert(p1 == 0);
c = '^';
} else {
assert(p1 == 5 + len1 + offset1);
if (p1 > data_size) {
c = '_';
} else {
c = data[p1];
}
}
result[p1] = c;
assert(f.sputc(c) == c);
f.pubseekpos(std::ios_base::beg);
result[0] = 'A';
assert(f.sputc(toupper(data[0])) == 'A');
pos_type end_pos = f.pubseekoff(off_type(0), std::ios_base::end);
assert(f.sputc(toupper(data[data_size - 1])) == 'Z');
result[end_pos] = 'Z';
assert(f.pubseekpos(p1) == p1);
result[p1] = toupper(c);
assert(f.sputc(toupper(c)) == toupper(c));
pos_type new_pos = result_size - chunk_size2;
pos_type p2 = f.pubseekoff(new_pos, std::ios_base::beg);
assert(p2 == new_pos);
chunk = std::vector<char>(data.end() - chunk_size2, data.end());
std::copy(chunk.begin(), chunk.end(), result.begin() + p2);
const std::streamsize len2 = f.sputn(chunk.data(), chunk_size2);
assert(len2 == chunk_size2);
assert(std::strncmp(chunk.data(), data.substr(data_size - chunk_size2, chunk_size2).c_str(), len2) == 0);
f.close();
}
std::filebuf f;
assert(f.open("sputn_seekoff.dat", std::ios_base::in) != 0);
assert(f.is_open());
std::vector<char> check(result.size(), -1);
const std::size_t len = f.sgetn(check.data(), check.size());
assert(len == result.size());
for (size_t i = 0; i < len; ++i) {
assert(check[i] == result[i]);
}
}
void sputn_not_open() {
std::vector<char> data(10, 'a');
std::filebuf f;
std::streamsize len = f.sputn(data.data(), data.size());
assert(len == 0);
assert(std::strncmp(data.data(), "aaaaaaaaaa", 10) == 0);
}
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
void sputn_not_open_wchar() {
std::vector<wchar_t> data(10, L'a');
std::wfilebuf f;
std::streamsize len = f.sputn(data.data(), data.size());
assert(len == 0);
assert(std::wcsncmp(data.data(), L"aaaaaaaaaa", 10) == 0);
}
#endif
int main(int, char**) {
sputn_not_open();
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
sputn_not_open_wchar();
#endif
sputn_seekoff(nullptr, 10, 21, -27, 1);
sputn_seekoff(nullptr, 10, 1, -27, 1);
sputn_seekoff(nullptr, 10, 10, 14, 12);
sputn_seekoff(nullptr, 10, 1, -2, 1);
sputn_seekoff(nullptr, 10, 10, -4, 12);
sputn_seekoff(nullptr, 10, 11, -12, 3);
sputn_seekoff(nullptr, 10, 7, 3, 8);
sputn_seekoff(nullptr, 10, 5, -5, 12);
sputn_seekoff(nullptr, 10, 1, 1, 1);
sputn_seekoff(nullptr, 10, 9, 0, 1);
return 0;
}