]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
perf: Use sendfile(2) for file copying (#1427)
authorRaihaan Shouhell <raihaanhimself@gmail.com>
Sat, 13 Apr 2024 18:33:31 +0000 (02:33 +0800)
committerGitHub <noreply@github.com>
Sat, 13 Apr 2024 18:33:31 +0000 (20:33 +0200)
Use sendfile for file copying as it avoids copying the data to
user-space and back to kernel space.

cmake/GenerateConfigurationFile.cmake
cmake/config.h.in
src/ccache/util/file.cpp
unittest/test_util_file.cpp

index ffb2ddef3f141e8d45ac6e4b1e80b2c32b671953..7caf21b58054ce5424eb7f419188d65e07908ce0 100644 (file)
@@ -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
index b1cf34e8335c4c8b1556dd9c4536554bd3819758..44027dfb88ecb10124b9b430b1f24d6e6a32a158 100644 (file)
@@ -87,6 +87,9 @@
 // Define if you have the <sys/mman.h> header file.
 #cmakedefine HAVE_SYS_MMAN_H
 
+// Define if you have the <sys/sendfile.h> header file.
+#cmakedefine HAVE_SYS_SENDFILE_H 
+
 // Define if you have the <sys/utime.h> header file.
 #cmakedefine HAVE_SYS_UTIME_H
 
index af5335a9e5ee0cfa278d7a671b9a646d66b99049..15d1905c3caf79029b1bd531cec14687394c8a69 100644 (file)
 #  include <windows.h>
 #endif
 
+#ifdef HAVE_SYS_SENDFILE_H
+#  include <sys/sendfile.h>
+#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<const uint8_t> 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<const uint8_t> data) {
+      write_fd(*dest_fd, data.data(), data.size());
+    }));
+#if defined(HAVE_SYS_SENDFILE_H)
+  }
+#endif
 
   dest_fd.close();
   src_fd.close();
index 8a9b9597b27d890c96a21d2b0561b330ea42150f..917675267be1e65dd02dc595f8004b7d7ff785e5 100644 (file)
@@ -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<std::string>("test");
+  CHECK(util::copy_file("test", "test2"));
+  auto data = util::read_file<std::string>("test2");
   REQUIRE(data);
   CHECK(*data == "foo\nbar\n");
 
   REQUIRE(util::write_file("test", "foo\r\nbar\r\n"));
-  data = util::read_file<std::string>("test");
+  CHECK(util::copy_file("test", "test2", util::ViaTmpFile::yes));
+  data = util::read_file<std::string>("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<std::vector<uint8_t>>("test");
+  CHECK(util::copy_file("test", "test2"));
+  auto bin_data = util::read_file<std::vector<uint8_t>>("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<std::vector<uint8_t>>("test");
+  CHECK(util::copy_file("test", "test2", util::ViaTmpFile::yes));
+  auto actual = util::read_file<std::vector<uint8_t>>("test2");
   REQUIRE(actual);
   CHECK(*actual == expected);