From: Raihaan Shouhell Date: Sat, 13 Apr 2024 18:33:31 +0000 (+0800) Subject: perf: Use sendfile(2) for file copying (#1427) X-Git-Tag: v4.10~48 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0883b97fe7d15b8ac4afe458ba9652c5d50751da;p=thirdparty%2Fccache.git perf: Use sendfile(2) for file copying (#1427) Use sendfile for file copying as it avoids copying the data to user-space and back to kernel space. --- diff --git a/cmake/GenerateConfigurationFile.cmake b/cmake/GenerateConfigurationFile.cmake index ffb2ddef..7caf21b5 100644 --- a/cmake/GenerateConfigurationFile.cmake +++ b/cmake/GenerateConfigurationFile.cmake @@ -7,6 +7,7 @@ set(include_files sys/file.h sys/ioctl.h sys/mman.h + sys/sendfile.h sys/utime.h sys/wait.h syslog.h diff --git a/cmake/config.h.in b/cmake/config.h.in index b1cf34e8..44027dfb 100644 --- a/cmake/config.h.in +++ b/cmake/config.h.in @@ -87,6 +87,9 @@ // Define if you have the header file. #cmakedefine HAVE_SYS_MMAN_H +// Define if you have the header file. +#cmakedefine HAVE_SYS_SENDFILE_H + // Define if you have the header file. #cmakedefine HAVE_SYS_UTIME_H diff --git a/src/ccache/util/file.cpp b/src/ccache/util/file.cpp index af5335a9..15d1905c 100644 --- a/src/ccache/util/file.cpp +++ b/src/ccache/util/file.cpp @@ -66,6 +66,10 @@ # include #endif +#ifdef HAVE_SYS_SENDFILE_H +# include +#endif + namespace fs = util::filesystem; using pstr = util::PathString; @@ -100,9 +104,34 @@ copy_file(const fs::path& src, const fs::path& dest, ViaTmpFile via_tmp_file) FMT("Failed to open {} for writing: {}", dest, strerror(errno))); } } - TRY(read_fd(*src_fd, [&](nonstd::span data) { - write_fd(*dest_fd, data.data(), data.size()); - })); + +#if defined(HAVE_SYS_SENDFILE_H) + DirEntry dir_entry(src, *src_fd); + if (!dir_entry) { + return tl::unexpected(FMT("Failed to stat {}: {}", src, strerror(errno))); + } + ssize_t bytes_left = dir_entry.size(); + bool fallback_to_rw = false; + while (bytes_left > 0) { + ssize_t n = sendfile(*dest_fd, *src_fd, nullptr, bytes_left); + if (n < 0) { + fallback_to_rw = (errno == EINVAL || errno == ENOSYS); + if (!fallback_to_rw) { + return tl::unexpected(FMT("Failed to copy: {} to {}", src, dest)); + } + break; + } + bytes_left -= n; + } + + if (fallback_to_rw) { +#endif + TRY(read_fd(*src_fd, [&](nonstd::span data) { + write_fd(*dest_fd, data.data(), data.size()); + })); +#if defined(HAVE_SYS_SENDFILE_H) + } +#endif dest_fd.close(); src_fd.close(); diff --git a/unittest/test_util_file.cpp b/unittest/test_util_file.cpp index 8a9b9597..91767526 100644 --- a/unittest/test_util_file.cpp +++ b/unittest/test_util_file.cpp @@ -71,23 +71,26 @@ TEST_CASE("util::likely_size_on_disk") CHECK(util::likely_size_on_disk(4097) == 8192); } -TEST_CASE("util::read_file and util::write_file, text data") +TEST_CASE("util::read_file util::write_file and util::copy_file, text data") { TestContext test_context; REQUIRE(util::write_file("test", "foo\nbar\n")); - auto data = util::read_file("test"); + CHECK(util::copy_file("test", "test2")); + auto data = util::read_file("test2"); REQUIRE(data); CHECK(*data == "foo\nbar\n"); REQUIRE(util::write_file("test", "foo\r\nbar\r\n")); - data = util::read_file("test"); + CHECK(util::copy_file("test", "test2", util::ViaTmpFile::yes)); + data = util::read_file("test2"); REQUIRE(data); CHECK(*data == "foo\r\nbar\r\n"); // Newline handling REQUIRE(util::write_file("test", "foo\r\nbar\n")); - auto bin_data = util::read_file>("test"); + CHECK(util::copy_file("test", "test2")); + auto bin_data = util::read_file>("test2"); REQUIRE(bin_data); #ifdef _WIN32 const std::string expected_bin_data = "foo\r\r\nbar\r\n"; @@ -119,7 +122,7 @@ TEST_CASE("util::read_file and util::write_file, text data") CHECK(result.error() == "No such file or directory"); } -TEST_CASE("util::read_file and util::write_file, binary data") +TEST_CASE("util::read_file, util::write_file and util::copy_file, binary data") { TestContext test_context; @@ -129,7 +132,8 @@ TEST_CASE("util::read_file and util::write_file, binary data") } CHECK(util::write_file("test", expected)); - auto actual = util::read_file>("test"); + CHECK(util::copy_file("test", "test2", util::ViaTmpFile::yes)); + auto actual = util::read_file>("test2"); REQUIRE(actual); CHECK(*actual == expected);