diff --git a/libcxx/docs/ReleaseNotes/20.rst b/libcxx/docs/ReleaseNotes/20.rst index f81a573845e6f..572d4321dc46f 100644 --- a/libcxx/docs/ReleaseNotes/20.rst +++ b/libcxx/docs/ReleaseNotes/20.rst @@ -120,6 +120,8 @@ Improvements and New Features - Added :ref:`hardening mode ` support for ``forward_list`` and ``bitset``. +- The performance of ``std::getline`` has been improved, resulting in a performance uplift of up to 10x. + Deprecations and Removals ------------------------- diff --git a/libcxx/include/istream b/libcxx/include/istream index 95340c739c118..02546902494e3 100644 --- a/libcxx/include/istream +++ b/libcxx/include/istream @@ -1263,41 +1263,70 @@ _LIBCPP_HIDE_FROM_ABI basic_istream<_CharT, _Traits>& getline(basic_istream<_CharT, _Traits>& __is, basic_string<_CharT, _Traits, _Allocator>& __str, _CharT __dlm) { ios_base::iostate __state = ios_base::goodbit; typename basic_istream<_CharT, _Traits>::sentry __sen(__is, true); - if (__sen) { + if (!__sen) + return __is; # if _LIBCPP_HAS_EXCEPTIONS - try { + try { # endif - __str.clear(); - streamsize __extr = 0; - while (true) { - typename _Traits::int_type __i = __is.rdbuf()->sbumpc(); - if (_Traits::eq_int_type(__i, _Traits::eof())) { - __state |= ios_base::eofbit; - break; + __str.clear(); + + auto& __buffer = *__is.rdbuf(); + + auto __next = __buffer.sgetc(); + for (; !_Traits::eq_int_type(__next, _Traits::eof()); __next = __buffer.sgetc()) { + const auto* __first = __buffer.gptr(); + const auto* __last = __buffer.egptr(); + _CharT __1buf; + + if (__first == __last) { + __1buf = __next; + __first = std::addressof(__1buf); + __last = std::addressof(__1buf) + 1; + } + + auto __bump_stream = [&](ptrdiff_t __diff) { + if (__first == std::addressof(__1buf)) { + _LIBCPP_ASSERT_INTERNAL(__diff == 0 || __diff == 1, "trying to bump stream further than buffer size"); + if (__diff != 0) + __buffer.sbumpc(); + } else { + __buffer.__gbump_ptrdiff(__diff); } - ++__extr; - _CharT __ch = _Traits::to_char_type(__i); - if (_Traits::eq(__ch, __dlm)) - break; - __str.push_back(__ch); - if (__str.size() == __str.max_size()) { - __state |= ios_base::failbit; + }; + + const auto* const __match = _Traits::find(__first, __last - __first, __dlm); + if (__match) + __last = __match; + + if (auto __cap = __str.max_size() - __str.size(); __cap > static_cast(__last - __first)) { + __str.append(__first, __last); + __bump_stream(__last - __first); + + if (__match) { + __bump_stream(1); // Remove the matched character break; } - } - if (__extr == 0) + } else { + __str.append(__first, __cap); + __bump_stream(__cap); __state |= ios_base::failbit; -# if _LIBCPP_HAS_EXCEPTIONS - } catch (...) { - __state |= ios_base::badbit; - __is.__setstate_nothrow(__state); - if (__is.exceptions() & ios_base::badbit) { - throw; + break; } } -# endif - __is.setstate(__state); + + if (_Traits::eq_int_type(__next, _Traits::eof())) + __state |= ios_base::eofbit | (__str.empty() ? ios_base::failbit : ios_base::goodbit); + +# if _LIBCPP_HAS_EXCEPTIONS + } catch (...) { + __state |= ios_base::badbit; + __is.__setstate_nothrow(__state); + if (__is.exceptions() & ios_base::badbit) { + throw; + } } +# endif + __is.setstate(__state); return __is; } diff --git a/libcxx/include/streambuf b/libcxx/include/streambuf index 85f3af1b88ae7..e25647909378e 100644 --- a/libcxx/include/streambuf +++ b/libcxx/include/streambuf @@ -267,6 +267,9 @@ protected: inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void gbump(int __n) { __ninp_ += __n; } + // gbump takes an int, so it might not be able to represent the offset we want to add. + _LIBCPP_HIDE_FROM_ABI void __gbump_ptrdiff(ptrdiff_t __n) { __ninp_ += __n; } + inline _LIBCPP_HIDE_FROM_ABI_AFTER_V1 void setg(char_type* __gbeg, char_type* __gnext, char_type* __gend) { _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__is_valid_range(__gbeg, __gnext), "[gbeg, gnext) must be a valid range"); _LIBCPP_ASSERT_VALID_INPUT_RANGE(std::__is_valid_range(__gbeg, __gend), "[gbeg, gend) must be a valid range"); @@ -370,6 +373,10 @@ private: char_type* __bout_ = nullptr; char_type* __nout_ = nullptr; char_type* __eout_ = nullptr; + + template + _LIBCPP_HIDE_FROM_ABI friend basic_istream<_CharT2, _Traits2>& + getline(basic_istream<_CharT2, _Traits2>&, basic_string<_CharT2, _Traits2, _Allocator>&, _CharT2); }; extern template class _LIBCPP_EXTERN_TEMPLATE_TYPE_VIS basic_streambuf; diff --git a/libcxx/test/benchmarks/streams/getline.bench.cpp b/libcxx/test/benchmarks/streams/getline.bench.cpp new file mode 100644 index 0000000000000..5ccefce876224 --- /dev/null +++ b/libcxx/test/benchmarks/streams/getline.bench.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#include +#include + +#include + +void BM_getline_string(benchmark::State& state) { + std::istringstream iss; + + std::string str; + str.reserve(128); + iss.str("A long string to let getline do some more work, making sure that longer strings are parsed fast enough"); + + for (auto _ : state) { + benchmark::DoNotOptimize(iss); + + std::getline(iss, str); + benchmark::DoNotOptimize(str); + iss.seekg(0); + } +} + +BENCHMARK(BM_getline_string); + +BENCHMARK_MAIN(); diff --git a/libcxx/test/std/strings/basic.string/string.nonmembers/string.io/get_line.pass.cpp b/libcxx/test/std/strings/basic.string/string.nonmembers/string.io/get_line.pass.cpp index 7bcb34135440b..28b1f9b67f28d 100644 --- a/libcxx/test/std/strings/basic.string/string.nonmembers/string.io/get_line.pass.cpp +++ b/libcxx/test/std/strings/basic.string/string.nonmembers/string.io/get_line.pass.cpp @@ -13,55 +13,42 @@ // getline(basic_istream& is, // basic_string& str); -#include -#include #include +#include +#include +#include "make_string.h" #include "min_allocator.h" +#include "stream_types.h" #include "test_macros.h" -template