Skip to content

Commit c5a0169

Browse files
committed
[libc++] Optimize ofstream::write
1 parent 3b10b9a commit c5a0169

File tree

7 files changed

+193
-3
lines changed

7 files changed

+193
-3
lines changed

libcxx/docs/ReleaseNotes/22.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 performance of ``map::map(const map&)`` has been improved up to 2.3x
4747
- The performance of ``map::operator=(const map&)`` has been improved by up to 11x
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
Deprecations and Removals
5053
-------------------------
5154

libcxx/include/fstream

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

228228
template <class _CharT, class _Traits>
229229
class basic_filebuf : public basic_streambuf<_CharT, _Traits> {
230+
using __base _LIBCPP_NODEBUG = basic_streambuf<_CharT, _Traits>;
231+
230232
public:
231233
typedef _CharT char_type;
232234
typedef _Traits traits_type;
@@ -299,6 +301,16 @@ protected:
299301
int sync() override;
300302
void imbue(const locale& __loc) override;
301303

304+
_LIBCPP_HIDE_FROM_ABI_VIRTUAL streamsize xsputn(const char_type* __str, streamsize __len) override {
305+
if (__always_noconv_ && __len >= (this->epptr() - this->pbase())) {
306+
if (traits_type::eq_int_type(overflow(), traits_type::eof()))
307+
return 0;
308+
309+
return std::fwrite(__str, sizeof(char_type), __len, __file_);
310+
}
311+
return __base::xsputn(__str, __len);
312+
}
313+
302314
private:
303315
char* __extbuf_;
304316
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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515

1616
// UNSUPPORTED: no-wide-characters
1717

18+
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
19+
1820
#include <fstream>
1921

2022
std::basic_filebuf<char, std::char_traits<wchar_t> > f;
2123
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
22-
// expected-error@*:* 9 {{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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515

1616
// UNSUPPORTED: no-wide-characters
1717

18+
// XFAIL: FROZEN-CXX03-HEADERS-FIXME
19+
1820
#include <fstream>
1921

2022
std::basic_fstream<char, std::char_traits<wchar_t> > f;
2123
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
2224
// expected-error-re@*:* {{static assertion failed{{.*}}traits_type::char_type must be the same type as CharT}}
2325

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

2628
// FIXME: As of commit r324062 Clang incorrectly generates a diagnostic about mismatching
2729
// 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: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
// UNSUPPORTED: c++03
10+
11+
// <fstream>
12+
13+
// streamsize xsputn(const char_type*, streamsize) override;
14+
15+
// This isn't a required override by the standard, but most implementation override it, sine it allows for significantly
16+
// improved performance in some cases. All of this code is required to work, so this isn't an extension
17+
18+
#include <algorithm>
19+
#include <cassert>
20+
#include <codecvt>
21+
#include <fstream>
22+
#include <locale>
23+
#include <vector>
24+
25+
#include "test_macros.h"
26+
27+
typedef std::filebuf::pos_type pos_type;
28+
typedef std::filebuf::off_type off_type;
29+
30+
void sputn_seekoff(char* buf,
31+
const size_t buf_size,
32+
const std::streamsize chunk_size1,
33+
const off_type offset1,
34+
const std::streamsize chunk_size2) {
35+
std::string data{"abcdefghijklmnopqrstuvwxyz"};
36+
const std::streamsize data_size = static_cast<std::streamsize>(data.size());
37+
assert(chunk_size1 <= data_size);
38+
assert(chunk_size2 <= data_size);
39+
// vector with expected data in the file to be written
40+
std::size_t result_size = 5 + chunk_size1 + chunk_size2 + 1;
41+
if (offset1 > 0) {
42+
result_size += offset1;
43+
}
44+
std::vector<char> result(result_size, 0);
45+
{
46+
std::filebuf f;
47+
f.pubsetbuf(buf, buf_size);
48+
assert(f.open("sputn_seekoff.dat", std::ios_base::out) != 0);
49+
assert(f.is_open());
50+
51+
assert(f.pubseekoff(off_type(5), std::ios_base::beg) = off_type(5));
52+
53+
std::vector<char> chunk(data.begin() + 5, data.begin() + 5 + chunk_size1);
54+
std::copy(chunk.begin(), chunk.end(), result.begin() + 5);
55+
const std::streamsize len1 = f.sputn(chunk.data(), chunk_size1);
56+
assert(len1 == chunk_size1);
57+
// check that nothing in the original chunk was modified by sputn()
58+
assert(std::strncmp(chunk.data(), data.substr(5, len1).c_str(), len1) == 0);
59+
60+
pos_type p1 = f.pubseekoff(offset1, std::ios_base::cur);
61+
char c;
62+
if (p1 < 0) {
63+
p1 = f.pubseekoff(0, std::ios_base::beg);
64+
assert(p1 == 0);
65+
c = '^';
66+
} else {
67+
assert(p1 == 5 + len1 + offset1);
68+
if (p1 > data_size) {
69+
c = '_';
70+
} else {
71+
c = data[p1];
72+
}
73+
}
74+
75+
result[p1] = c;
76+
assert(f.sputc(c) == c);
77+
78+
f.pubseekpos(std::ios_base::beg);
79+
result[0] = 'A';
80+
assert(f.sputc(toupper(data[0])) == 'A');
81+
82+
pos_type end_pos = f.pubseekoff(off_type(0), std::ios_base::end);
83+
assert(f.sputc(toupper(data[data_size - 1])) == 'Z');
84+
result[end_pos] = 'Z';
85+
86+
assert(f.pubseekpos(p1) == p1);
87+
result[p1] = toupper(c);
88+
assert(f.sputc(toupper(c)) == toupper(c));
89+
90+
pos_type new_pos = result_size - chunk_size2;
91+
pos_type p2 = f.pubseekoff(new_pos, std::ios_base::beg);
92+
assert(p2 == new_pos);
93+
chunk = std::vector<char>(data.end() - chunk_size2, data.end());
94+
std::copy(chunk.begin(), chunk.end(), result.begin() + p2);
95+
const std::streamsize len2 = f.sputn(chunk.data(), chunk_size2);
96+
assert(len2 == chunk_size2);
97+
assert(std::strncmp(chunk.data(), data.substr(data_size - chunk_size2, chunk_size2).c_str(), len2) == 0);
98+
f.close();
99+
}
100+
std::filebuf f;
101+
assert(f.open("sputn_seekoff.dat", std::ios_base::in) != 0);
102+
assert(f.is_open());
103+
std::vector<char> check(result.size(), -1);
104+
const std::size_t len = f.sgetn(check.data(), check.size());
105+
assert(len == result.size());
106+
for (size_t i = 0; i < len; ++i) {
107+
assert(check[i] == result[i]);
108+
}
109+
}
110+
111+
void sputn_not_open() {
112+
std::vector<char> data(10, 'a');
113+
std::filebuf f;
114+
std::streamsize len = f.sputn(data.data(), data.size());
115+
assert(len == 0);
116+
assert(std::strncmp(data.data(), "aaaaaaaaaa", 10) == 0);
117+
}
118+
119+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
120+
void sputn_not_open_wchar() {
121+
std::vector<wchar_t> data(10, L'a');
122+
std::wfilebuf f;
123+
std::streamsize len = f.sputn(data.data(), data.size());
124+
assert(len == 0);
125+
assert(std::wcsncmp(data.data(), L"aaaaaaaaaa", 10) == 0);
126+
}
127+
#endif
128+
129+
int main(int, char**) {
130+
sputn_not_open();
131+
#ifndef TEST_HAS_NO_WIDE_CHARACTERS
132+
sputn_not_open_wchar();
133+
#endif
134+
135+
sputn_seekoff(nullptr, 10, 22, -27, 1);
136+
sputn_seekoff(nullptr, 10, 1, -27, 1);
137+
sputn_seekoff(nullptr, 10, 10, 14, 12);
138+
sputn_seekoff(nullptr, 10, 1, -2, 1);
139+
sputn_seekoff(nullptr, 10, 10, -4, 12);
140+
sputn_seekoff(nullptr, 10, 11, -12, 3);
141+
sputn_seekoff(nullptr, 10, 7, 3, 8);
142+
sputn_seekoff(nullptr, 10, 5, -5, 12);
143+
sputn_seekoff(nullptr, 10, 1, 1, 1);
144+
sputn_seekoff(nullptr, 10, 9, 0, 1);
145+
146+
return 0;
147+
}

0 commit comments

Comments
 (0)