diff --git a/libcxx/docs/ReleaseNotes/22.rst b/libcxx/docs/ReleaseNotes/22.rst index 8b8dce5083149..394d2cd5cfaba 100644 --- a/libcxx/docs/ReleaseNotes/22.rst +++ b/libcxx/docs/ReleaseNotes/22.rst @@ -46,6 +46,9 @@ Improvements and New Features - The performance of ``map::map(const map&)`` has been improved up to 2.3x - The performance of ``map::operator=(const map&)`` has been improved by up to 11x +- ``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 ------------------------- diff --git a/libcxx/include/fstream b/libcxx/include/fstream index 6d3f20fff688f..04cebdedc31d8 100644 --- a/libcxx/include/fstream +++ b/libcxx/include/fstream @@ -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_; diff --git a/libcxx/test/benchmarks/streams/ofstream.bench.cpp b/libcxx/test/benchmarks/streams/ofstream.bench.cpp new file mode 100644 index 0000000000000..60606a9d67e2f --- /dev/null +++ b/libcxx/test/benchmarks/streams/ofstream.bench.cpp @@ -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 +#include + +#include + +static void bm_write(benchmark::State& state) { + std::vector buffer; + buffer.resize(16384); + + std::ofstream stream("/dev/null"); + + for (auto _ : state) + stream.write(buffer.data(), buffer.size()); +} +BENCHMARK(bm_write); + +BENCHMARK_MAIN(); diff --git a/libcxx/test/libcxx/input.output/file.streams/fstreams/filebuf/traits_mismatch.verify.cpp b/libcxx/test/libcxx/input.output/file.streams/fstreams/filebuf/traits_mismatch.verify.cpp index 455c9979ae57d..fcbf6497c8d77 100644 --- a/libcxx/test/libcxx/input.output/file.streams/fstreams/filebuf/traits_mismatch.verify.cpp +++ b/libcxx/test/libcxx/input.output/file.streams/fstreams/filebuf/traits_mismatch.verify.cpp @@ -15,8 +15,10 @@ // UNSUPPORTED: no-wide-characters +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include std::basic_filebuf > 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'}} diff --git a/libcxx/test/libcxx/input.output/file.streams/fstreams/traits_mismatch.verify.cpp b/libcxx/test/libcxx/input.output/file.streams/fstreams/traits_mismatch.verify.cpp index cc52cc119d50e..8eca76c037bfe 100644 --- a/libcxx/test/libcxx/input.output/file.streams/fstreams/traits_mismatch.verify.cpp +++ b/libcxx/test/libcxx/input.output/file.streams/fstreams/traits_mismatch.verify.cpp @@ -15,13 +15,15 @@ // UNSUPPORTED: no-wide-characters +// XFAIL: FROZEN-CXX03-HEADERS-FIXME + #include std::basic_fstream > 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. diff --git a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/seekoff.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/seekoff.pass.cpp index 8008901802e91..f6378e7998ee9 100644 --- a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/seekoff.pass.cpp +++ b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/seekoff.pass.cpp @@ -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'); diff --git a/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/xsputn.pass.cpp b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/xsputn.pass.cpp new file mode 100644 index 0000000000000..4da97234fa2b9 --- /dev/null +++ b/libcxx/test/std/input.output/file.streams/fstreams/filebuf.virtuals/xsputn.pass.cpp @@ -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 + +// + +// 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 +#include +#include +#include +#include +#include +#include + +#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(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 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 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(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 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 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 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, 22, -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; +}