libstdc++: Also use sendfile for big files

We were previously only using sendfile for files smaller than 2GB, as
sendfile needs to be called repeatedly for files bigger than that.

Some quick numbers, copying a 16GB file, average of 10 repetitions:
    old:
        real: 13.4s
        user: 0.14s
        sys : 7.43s
    new:
        real: 8.90s
        user: 0.00s
        sys : 3.68s

libstdc++-v3/ChangeLog:

	* acinclude.m4 (_GLIBCXX_HAVE_LSEEK): Define.
	* config.h.in: Regenerate.
	* configure: Regenerate.
	* src/filesystem/ops-common.h (copy_file_sendfile): Define new
	function for sendfile logic. Loop to support large files. Skip
	zero-length files.
	(do_copy_file): Use it.

Signed-off-by: Jannik Glückert <jannik.glueckert@gmail.com>
This commit is contained in:
Jannik Glückert
2023-03-06 20:52:08 +01:00
committed by Jonathan Wakely
parent c4deccd446
commit f80a8b4229
4 changed files with 196 additions and 110 deletions

View File

@@ -4956,6 +4956,7 @@ dnl _GLIBCXX_USE_FCHMOD
dnl _GLIBCXX_USE_FCHMODAT
dnl _GLIBCXX_USE_SENDFILE
dnl HAVE_LINK
dnl HAVE_LSEEK
dnl HAVE_READLINK
dnl HAVE_SYMLINK
dnl
@@ -5091,25 +5092,6 @@ dnl
if test $glibcxx_cv_fchmodat = yes; then
AC_DEFINE(_GLIBCXX_USE_FCHMODAT, 1, [Define if fchmodat is available in <sys/stat.h>.])
fi
dnl
AC_CACHE_CHECK([for sendfile that can copy files],
glibcxx_cv_sendfile, [dnl
case "${target_os}" in
gnu* | linux* | solaris* | uclinux*)
GCC_TRY_COMPILE_OR_LINK(
[#include <sys/sendfile.h>],
[sendfile(1, 2, (off_t*)0, sizeof 1);],
[glibcxx_cv_sendfile=yes],
[glibcxx_cv_sendfile=no])
;;
*)
glibcxx_cv_sendfile=no
;;
esac
])
if test $glibcxx_cv_sendfile = yes; then
AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
fi
dnl
AC_CACHE_CHECK([for link],
glibcxx_cv_link, [dnl
@@ -5122,6 +5104,18 @@ dnl
if test $glibcxx_cv_link = yes; then
AC_DEFINE(HAVE_LINK, 1, [Define if link is available in <unistd.h>.])
fi
dnl
AC_CACHE_CHECK([for lseek],
glibcxx_cv_lseek, [dnl
GCC_TRY_COMPILE_OR_LINK(
[#include <unistd.h>],
[lseek(1, 0, SEEK_SET);],
[glibcxx_cv_lseek=yes],
[glibcxx_cv_lseek=no])
])
if test $glibcxx_cv_lseek = yes; then
AC_DEFINE(HAVE_LSEEK, 1, [Define if lseek is available in <unistd.h>.])
fi
dnl
AC_CACHE_CHECK([for readlink],
glibcxx_cv_readlink, [dnl
@@ -5158,6 +5152,25 @@ dnl
if test $glibcxx_cv_truncate = yes; then
AC_DEFINE(HAVE_TRUNCATE, 1, [Define if truncate is available in <unistd.h>.])
fi
dnl
AC_CACHE_CHECK([for sendfile that can copy files],
glibcxx_cv_sendfile, [dnl
case "${target_os}" in
gnu* | linux* | solaris* | uclinux*)
GCC_TRY_COMPILE_OR_LINK(
[#include <sys/sendfile.h>],
[sendfile(1, 2, (off_t*)0, sizeof 1);],
[glibcxx_cv_sendfile=yes],
[glibcxx_cv_sendfile=no])
;;
*)
glibcxx_cv_sendfile=no
;;
esac
])
if test $glibcxx_cv_sendfile = yes && test $glibcxx_cv_lseek = yes; then
AC_DEFINE(_GLIBCXX_USE_SENDFILE, 1, [Define if sendfile is available in <sys/sendfile.h>.])
fi
dnl
AC_CACHE_CHECK([for fdopendir],
glibcxx_cv_fdopendir, [dnl

View File

@@ -254,6 +254,9 @@
/* Define to 1 if you have the `logl' function. */
#undef HAVE_LOGL
/* Define if lseek is available in <unistd.h>. */
#undef HAVE_LSEEK
/* Define to 1 if you have the <machine/endian.h> header file. */
#undef HAVE_MACHINE_ENDIAN_H

179
libstdc++-v3/configure vendored
View File

@@ -71004,68 +71004,6 @@ $as_echo "$glibcxx_cv_fchmodat" >&6; }
$as_echo "#define _GLIBCXX_USE_FCHMODAT 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sendfile that can copy files" >&5
$as_echo_n "checking for sendfile that can copy files... " >&6; }
if ${glibcxx_cv_sendfile+:} false; then :
$as_echo_n "(cached) " >&6
else
case "${target_os}" in
gnu* | linux* | solaris* | uclinux*)
if test x$gcc_no_link = xyes; then
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <sys/sendfile.h>
int
main ()
{
sendfile(1, 2, (off_t*)0, sizeof 1);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
glibcxx_cv_sendfile=yes
else
glibcxx_cv_sendfile=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
else
if test x$gcc_no_link = xyes; then
as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
fi
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <sys/sendfile.h>
int
main ()
{
sendfile(1, 2, (off_t*)0, sizeof 1);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_link "$LINENO"; then :
glibcxx_cv_sendfile=yes
else
glibcxx_cv_sendfile=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
;;
*)
glibcxx_cv_sendfile=no
;;
esac
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_sendfile" >&5
$as_echo "$glibcxx_cv_sendfile" >&6; }
if test $glibcxx_cv_sendfile = yes; then
$as_echo "#define _GLIBCXX_USE_SENDFILE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for link" >&5
$as_echo_n "checking for link... " >&6; }
@@ -71121,6 +71059,61 @@ $as_echo "$glibcxx_cv_link" >&6; }
$as_echo "#define HAVE_LINK 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for lseek" >&5
$as_echo_n "checking for lseek... " >&6; }
if ${glibcxx_cv_lseek+:} false; then :
$as_echo_n "(cached) " >&6
else
if test x$gcc_no_link = xyes; then
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <unistd.h>
int
main ()
{
lseek(1, 0, SEEK_SET);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
glibcxx_cv_lseek=yes
else
glibcxx_cv_lseek=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
else
if test x$gcc_no_link = xyes; then
as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
fi
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <unistd.h>
int
main ()
{
lseek(1, 0, SEEK_SET);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_link "$LINENO"; then :
glibcxx_cv_lseek=yes
else
glibcxx_cv_lseek=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_lseek" >&5
$as_echo "$glibcxx_cv_lseek" >&6; }
if test $glibcxx_cv_lseek = yes; then
$as_echo "#define HAVE_LSEEK 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for readlink" >&5
$as_echo_n "checking for readlink... " >&6; }
@@ -71286,6 +71279,68 @@ $as_echo "$glibcxx_cv_truncate" >&6; }
$as_echo "#define HAVE_TRUNCATE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sendfile that can copy files" >&5
$as_echo_n "checking for sendfile that can copy files... " >&6; }
if ${glibcxx_cv_sendfile+:} false; then :
$as_echo_n "(cached) " >&6
else
case "${target_os}" in
gnu* | linux* | solaris* | uclinux*)
if test x$gcc_no_link = xyes; then
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <sys/sendfile.h>
int
main ()
{
sendfile(1, 2, (off_t*)0, sizeof 1);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_compile "$LINENO"; then :
glibcxx_cv_sendfile=yes
else
glibcxx_cv_sendfile=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
else
if test x$gcc_no_link = xyes; then
as_fn_error $? "Link tests are not allowed after GCC_NO_EXECUTABLES." "$LINENO" 5
fi
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
#include <sys/sendfile.h>
int
main ()
{
sendfile(1, 2, (off_t*)0, sizeof 1);
;
return 0;
}
_ACEOF
if ac_fn_cxx_try_link "$LINENO"; then :
glibcxx_cv_sendfile=yes
else
glibcxx_cv_sendfile=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
;;
*)
glibcxx_cv_sendfile=no
;;
esac
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $glibcxx_cv_sendfile" >&5
$as_echo "$glibcxx_cv_sendfile" >&6; }
if test $glibcxx_cv_sendfile = yes && test $glibcxx_cv_lseek = yes; then
$as_echo "#define _GLIBCXX_USE_SENDFILE 1" >>confdefs.h
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for fdopendir" >&5
$as_echo_n "checking for fdopendir... " >&6; }

View File

@@ -51,6 +51,7 @@
# include <ext/stdio_filebuf.h>
# ifdef _GLIBCXX_USE_SENDFILE
# include <sys/sendfile.h> // sendfile
# include <unistd.h> // lseek
# endif
#endif
@@ -358,6 +359,34 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
}
#ifdef NEED_DO_COPY_FILE
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
bool
copy_file_sendfile(int fd_in, int fd_out, size_t length) noexcept
{
// a zero-length file is either empty, or not copyable by this syscall
// return early to avoid the syscall cost
if (length == 0)
{
errno = EINVAL;
return false;
}
size_t bytes_left = length;
off_t offset = 0;
ssize_t bytes_copied;
do
{
bytes_copied = ::sendfile(fd_out, fd_in, &offset, bytes_left);
bytes_left -= bytes_copied;
}
while (bytes_left > 0 && bytes_copied > 0);
if (bytes_copied < 0)
{
::lseek(fd_out, 0, SEEK_SET);
return false;
}
return true;
}
#endif
bool
do_copy_file(const char_type* from, const char_type* to,
std::filesystem::copy_options_existing_file options,
@@ -498,16 +527,22 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
return false;
}
size_t count = from_st->st_size;
bool has_copied = false;
#if defined _GLIBCXX_USE_SENDFILE && ! defined _GLIBCXX_FILESYSTEM_IS_WINDOWS
off_t offset = 0;
ssize_t n = ::sendfile(out.fd, in.fd, &offset, count);
if (n < 0 && errno != ENOSYS && errno != EINVAL)
if (!has_copied)
has_copied = copy_file_sendfile(in.fd, out.fd, from_st->st_size);
if (!has_copied)
{
ec.assign(errno, std::generic_category());
return false;
if (errno != ENOSYS && errno != EINVAL)
{
ec.assign(errno, std::generic_category());
return false;
}
}
if ((size_t)n == count)
#endif
if (has_copied)
{
if (!out.close() || !in.close())
{
@@ -517,9 +552,6 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
ec.clear();
return true;
}
else if (n > 0)
count -= n;
#endif // _GLIBCXX_USE_SENDFILE
using std::ios;
__gnu_cxx::stdio_filebuf<char> sbin(in.fd, ios::in|ios::binary);
@@ -530,29 +562,12 @@ _GLIBCXX_BEGIN_NAMESPACE_FILESYSTEM
if (sbout.is_open())
out.fd = -1;
#ifdef _GLIBCXX_USE_SENDFILE
if (n != 0)
{
if (n < 0)
n = 0;
const auto p1 = sbin.pubseekoff(n, ios::beg, ios::in);
const auto p2 = sbout.pubseekoff(n, ios::beg, ios::out);
const std::streampos errpos(std::streamoff(-1));
if (p1 == errpos || p2 == errpos)
{
ec = std::make_error_code(std::errc::io_error);
return false;
}
}
#endif
if (count && !(std::ostream(&sbout) << &sbin))
if (from_st->st_size && !(std::ostream(&sbout) << &sbin))
{
ec = std::make_error_code(std::errc::io_error);
return false;
}
if (!sbout.close() || !sbin.close())
{
ec.assign(errno, std::generic_category());