libstdc++: basic_filebuf: don't flush more often than necessary [PR63746]
`basic_filebuf::xsputn` would bypass the buffer when passed a chunk of size 1024 and above, seemingly as an optimisation. This can have a significant performance impact if the overhead of a `write` syscall is non-negligible, e.g. on a slow disk, on network filesystems, or simply during IO contention because instead of flushing every `BUFSIZ` (by default), we can flush every 1024 char. The impact is even greater with custom larger buffers, e.g. for network filesystems, because the code could issue `write` for example 1000X more often than necessary with respect to the buffer size. It also introduces a significant discontinuity in performance when writing chunks of size 1024 and above. Instead, it makes sense to only bypass the buffer if the amount of data to be written is larger than the buffer capacity. Signed-off-by: Charles-Francois Natali <cf.natali@gmail.com> libstdc++-v3/ChangeLog: PR libstdc++/63746 * include/bits/fstream.tcc (basic_filbuf::xsputn): Remove 1024-byte chunking that bypasses the buffer for large writes. * testsuite/27_io/basic_filebuf/sputn/char/63746.cc: New test.
This commit is contained in:
parent
c93baa93df
commit
3f1519eef5
2 changed files with 41 additions and 6 deletions
|
@ -757,23 +757,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
|
|||
{
|
||||
streamsize __ret = 0;
|
||||
// Optimization in the always_noconv() case, to be generalized in the
|
||||
// future: when __n is sufficiently large we write directly instead of
|
||||
// using the buffer.
|
||||
// future: when __n is larger than the available capacity we write
|
||||
// directly instead of using the buffer.
|
||||
const bool __testout = (_M_mode & ios_base::out
|
||||
|| _M_mode & ios_base::app);
|
||||
if (__check_facet(_M_codecvt).always_noconv()
|
||||
&& __testout && !_M_reading)
|
||||
{
|
||||
// Measurement would reveal the best choice.
|
||||
const streamsize __chunk = 1ul << 10;
|
||||
streamsize __bufavail = this->epptr() - this->pptr();
|
||||
|
||||
// Don't mistake 'uncommitted' mode buffered with unbuffered.
|
||||
if (!_M_writing && _M_buf_size > 1)
|
||||
__bufavail = _M_buf_size - 1;
|
||||
|
||||
const streamsize __limit = std::min(__chunk, __bufavail);
|
||||
if (__n >= __limit)
|
||||
if (__n >= __bufavail)
|
||||
{
|
||||
const streamsize __buffill = this->pptr() - this->pbase();
|
||||
const char* __buf = reinterpret_cast<const char*>(this->pbase());
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
// { dg-require-fileio "" }
|
||||
|
||||
#include <fstream>
|
||||
#include <testsuite_hooks.h>
|
||||
|
||||
class testbuf : public std::filebuf {
|
||||
public:
|
||||
char_type* pub_pprt() const
|
||||
{
|
||||
return this->pptr();
|
||||
}
|
||||
|
||||
char_type* pub_pbase() const
|
||||
{
|
||||
return this->pbase();
|
||||
}
|
||||
};
|
||||
|
||||
void test01()
|
||||
{
|
||||
using namespace std;
|
||||
|
||||
// Leave capacity to avoid flush.
|
||||
const streamsize chunk_size = BUFSIZ - 1 - 1;
|
||||
const char data[chunk_size] = {};
|
||||
|
||||
testbuf a_f;
|
||||
VERIFY( a_f.open("tmp_63746_sputn", ios_base::out) );
|
||||
VERIFY( chunk_size == a_f.sputn(data, chunk_size) );
|
||||
VERIFY( (a_f.pub_pprt() - a_f.pub_pbase()) == chunk_size );
|
||||
VERIFY( a_f.close() );
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test01();
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Reference in a new issue