Skip to content

Commit 11bc68b

Browse files
committed
[libc++] Optimize ofstream::write
1 parent 4c4fc46 commit 11bc68b

File tree

8 files changed

+221
-11
lines changed

8 files changed

+221
-11
lines changed

libcxx/docs/ReleaseNotes/21.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ Improvements and New Features
4646
- The ``std::ranges::{copy, copy_n, copy_backward}`` algorithms have been optimized for ``std::vector<bool>::iterator``\s,
4747
resulting in a performance improvement of up to 2000x.
4848

49+
- ``ofstream::write`` has been optimized to pass through large strings to system calls directly instead of copying them
50+
in chunks into a buffer.
51+
4952
- Updated formatting library to Unicode 16.0.0.
5053

5154
Deprecations and Removals

libcxx/include/fstream

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,8 @@ _LIBCPP_EXPORTED_FROM_ABI void* __filebuf_windows_native_handle(FILE* __file) no
226226

227227
template <class _CharT, class _Traits>
228228
class _LIBCPP_TEMPLATE_VIS basic_filebuf : public basic_streambuf<_CharT, _Traits> {
229+
using __base _LIBCPP_NODEBUG = basic_streambuf<_CharT, _Traits>;
230+
229231
public:
230232
typedef _CharT char_type;
231233
typedef _Traits traits_type;
@@ -298,6 +300,16 @@ protected:
298300
int sync() override;
299301
void imbue(const locale& __loc) override;
300302

303+
_LIBCPP_HIDE_FROM_ABI_VIRTUAL streamsize xsputn(const char_type* __str, streamsize __len) override {
304+
if (__always_noconv_ && __len >= (this->epptr() - this->pbase())) {
305+
if (traits_type::eq_int_type(overflow(), traits_type::eof()))
306+
return 0;
307+
308+
return std::fwrite(__str, sizeof(char_type), __len, __file_);
309+
}
310+
return __base::xsputn(__str, __len);
311+
}
312+
301313
private:
302314
char* __extbuf_;
303315
const char* __extbufnext_;
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include <fstream>
10+
#include <vector>
11+
12+
#include <benchmark/benchmark.h>
13+
14+
static void bm_write(benchmark::State& state) {
15+
std::vector<char> buffer;
16+
buffer.resize(16384);
17+
18+
std::ofstream stream("/dev/null");
19+
20+
for (auto _ : state)
21+
stream.write(buffer.data(), buffer.size());
22+
}
23+
BENCHMARK(bm_write);
24+
25+
BENCHMARK_MAIN();

libcxx/test/libcxx/input.output/file.streams/fstreams/filebuf/traits_mismatch.verify.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,4 @@
2121

2222
std::basic_filebuf<char, std::char_traits<wchar_t> > f;
2323
// expected-error-re@streambuf:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
24-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
25-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
26-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
27-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
28-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
29-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
30-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
31-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
32-
// expected-error@fstream:* {{only virtual member functions can be marked 'override'}}
24+
// expected-error@*:* 10 {{only virtual member functions can be marked 'override'}}

libcxx/test/libcxx/input.output/file.streams/fstreams/traits_mismatch.verify.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ std::basic_fstream<char, std::char_traits<wchar_t> > f;
2323
// expected-error-re@ios:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
2424
// expected-error-re@streambuf:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
2525

26-
// expected-error@*:* 11 {{only virtual member functions can be marked 'override'}}
26+
// expected-error@*:* 12 {{only virtual member functions can be marked 'override'}}
2727

2828
// FIXME: As of commit r324062 Clang incorrectly generates a diagnostic about mismatching
2929
// exception specifications for types which are already invalid for one reason or another.

libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/seekoff.pass.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ int main(int, char**)
2929
| std::ios_base::trunc) != 0);
3030
assert(f.is_open());
3131
f.sputn("abcdefghijklmnopqrstuvwxyz", 26);
32-
LIBCPP_ASSERT(buf[0] == 'v');
3332
pos_type p = f.pubseekoff(-15, std::ios_base::cur);
3433
assert(p == 11);
3534
assert(f.sgetc() == 'l');
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS
10+
11+
// <fstream>
12+
13+
#include <cassert>
14+
#include <fstream>
15+
#include <vector>
16+
17+
void sputn_not_open() {
18+
std::vector<char> data(10, 'a');
19+
std::filebuf f;
20+
std::streamsize len = f.sputn(data.data(), data.size());
21+
assert(len == 0);
22+
assert(std::strncmp(data.data(), "aaaaaaaaaa", 10) == 0);
23+
}
24+
25+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
26+
void sputn_not_open_wchar() {
27+
std::vector<wchar_t> data(10, L'a');
28+
std::wfilebuf f;
29+
std::streamsize len = f.sputn(data.data(), data.size());
30+
assert(len == 0);
31+
assert(std::wcsncmp(data.data(), L"aaaaaaaaaa", 10) == 0);
32+
}
33+
#endif
34+
35+
int main(int, char **) {
36+
sputn_not_open();
37+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
38+
sputn_not_open_wchar();
39+
#endif
40+
return 0;
41+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_DISABLE_DEPRECATION_WARNINGS
10+
// UNSUPPORTED: c++03
11+
12+
// <fstream>
13+
14+
#define TEST_HAS_NO_WIDE_CHARACTERS 1
15+
16+
#include <algorithm>
17+
#include <cassert>
18+
#include <codecvt>
19+
#include <fstream>
20+
#include <locale>
21+
#include <vector>
22+
23+
#include "platform_support.h"
24+
#include "test_macros.h"
25+
26+
typedef std::filebuf::pos_type pos_type;
27+
typedef std::filebuf::off_type off_type;
28+
29+
void sputn_seekoff(char* buf,
30+
const size_t buf_size,
31+
const std::streamsize chunk_size1,
32+
const off_type offset1,
33+
const std::streamsize chunk_size2) {
34+
std::string data{"abcdefghijklmnopqrstuvwxyz"};
35+
const std::streamsize data_size = static_cast<std::streamsize>(data.size());
36+
assert(chunk_size1 <= data_size);
37+
assert(chunk_size2 <= data_size);
38+
// vector with expected data in the file to be written
39+
std::size_t result_size = 5 + chunk_size1 + chunk_size2 + 1;
40+
if (offset1 > 0) {
41+
result_size += offset1;
42+
}
43+
std::vector<char> result(result_size, 0);
44+
{
45+
std::filebuf f;
46+
f.pubsetbuf(buf, buf_size);
47+
assert(f.open("sputn_seekoff.dat", std::ios_base::out) != 0);
48+
assert(f.is_open());
49+
50+
assert(f.pubseekoff(off_type(5), std::ios_base::beg) = off_type(5));
51+
52+
std::vector<char> chunk(data.begin() + 5, data.begin() + 5 + chunk_size1);
53+
std::copy(chunk.begin(), chunk.end(), result.begin() + 5);
54+
const std::streamsize len1 = f.sputn(chunk.data(), chunk_size1);
55+
assert(len1 == chunk_size1);
56+
// check that nothing in the original chunk was modified by sputn()
57+
assert(std::strncmp(chunk.data(), data.substr(5, len1).c_str(), len1) == 0);
58+
59+
pos_type p1 = f.pubseekoff(offset1, std::ios_base::cur);
60+
char c;
61+
if (p1 < 0) {
62+
p1 = f.pubseekoff(0, std::ios_base::beg);
63+
assert(p1 == 0);
64+
c = '^';
65+
} else {
66+
assert(p1 == 5 + len1 + offset1);
67+
if (p1 > data_size) {
68+
c = '_';
69+
} else {
70+
c = data[p1];
71+
}
72+
}
73+
74+
result[p1] = c;
75+
assert(f.sputc(c) == c);
76+
77+
f.pubseekpos(std::ios_base::beg);
78+
result[0] = 'A';
79+
assert(f.sputc(toupper(data[0])) == 'A');
80+
81+
pos_type end_pos = f.pubseekoff(off_type(0), std::ios_base::end);
82+
assert(f.sputc(toupper(data[data_size - 1])) == 'Z');
83+
result[end_pos] = 'Z';
84+
85+
assert(f.pubseekpos(p1) == p1);
86+
result[p1] = toupper(c);
87+
assert(f.sputc(toupper(c)) == toupper(c));
88+
89+
pos_type new_pos = result_size - chunk_size2;
90+
pos_type p2 = f.pubseekoff(new_pos, std::ios_base::beg);
91+
assert(p2 == new_pos);
92+
chunk = std::vector<char>(data.end() - chunk_size2, data.end());
93+
std::copy(chunk.begin(), chunk.end(), result.begin() + p2);
94+
const std::streamsize len2 = f.sputn(chunk.data(), chunk_size2);
95+
assert(len2 == chunk_size2);
96+
assert(std::strncmp(chunk.data(), data.substr(data_size - chunk_size2, chunk_size2).c_str(), len2) == 0);
97+
f.close();
98+
}
99+
std::filebuf f;
100+
assert(f.open("sputn_seekoff.dat", std::ios_base::in) != 0);
101+
assert(f.is_open());
102+
std::vector<char> check(result.size(), -1);
103+
const std::size_t len = f.sgetn(check.data(), check.size());
104+
assert(len == result.size());
105+
for (size_t i = 0; i < len; ++i) {
106+
assert(check[i] == result[i]);
107+
}
108+
}
109+
110+
int main(int, char**) {
111+
sputn_seekoff(nullptr, 10, 22, -27, 1);
112+
sputn_seekoff(nullptr, 10, 1, -27, 1);
113+
sputn_seekoff(nullptr, 10, 10, 14, 12);
114+
sputn_seekoff(nullptr, 10, 1, -2, 1);
115+
sputn_seekoff(nullptr, 10, 10, -4, 12);
116+
sputn_seekoff(nullptr, 10, 11, -12, 3);
117+
sputn_seekoff(nullptr, 10, 7, 3, 8);
118+
sputn_seekoff(nullptr, 10, 5, -5, 12);
119+
sputn_seekoff(nullptr, 10, 1, 1, 1);
120+
sputn_seekoff(nullptr, 10, 9, 0, 1);
121+
122+
// // the below are very long duration tests to cover many possible variations of I/O and hit different corner cases
123+
// // may want to run them (for minutes) in case of some major refactoring or changes to implementation of stream class
124+
// auto buffer_sizes = {7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 4096};
125+
// for (std::size_t chunk_size1 = 1; chunk_size1 < 27; ++chunk_size1) {
126+
// for (std::size_t chunk_size2 = 1; chunk_size2 < 27; ++chunk_size2) {
127+
// for (off_type off1 = -27; off1 < 27; off1 += 1) {
128+
// for (const auto& buf_sz : buffer_sizes) {
129+
// sputn_seekoff(nullptr, buf_sz, chunk_size1, off1, chunk_size2);
130+
// std::vector<char> buf;
131+
// buf.resize(buf_sz);
132+
// sputn_seekoff(buf.data(), buf_sz, chunk_size1, off1, chunk_size2);
133+
// }
134+
// }
135+
// }
136+
// }
137+
return 0;
138+
}

0 commit comments

Comments
 (0)