From 22c9ba0f3267f789fd7fe9dafdc50315c88f94b7 Mon Sep 17 00:00:00 2001 From: Rudi Grinberg Date: Thu, 2 Mar 2023 17:39:28 -0600 Subject: [PATCH] feature: use clonefile on macos Use mac's [clonefile] instead of manually copying. [clonefile] is like hardlink but it will copy-on-write when edited. Signed-off-by: Rudi Grinberg --- CHANGES.md | 3 ++ bench/micro/copyfile.ml | 14 +++++++++ bench/micro/dune | 5 ++++ otherlibs/stdune/src/copyfile_stubs.c | 41 +++++++++++++++++++++++++++ otherlibs/stdune/src/dune | 2 +- otherlibs/stdune/src/io.ml | 21 ++++++++++++++ 6 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 bench/micro/copyfile.ml create mode 100644 otherlibs/stdune/src/copyfile_stubs.c diff --git a/CHANGES.md b/CHANGES.md index 1e7fdf0c92c6..3b5701d8dda4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ Unreleased ---------- +- Speed up file copying on macos by using `clonefile` when available + (@rgrinberg, #7210) + - Support commands that output 8-bit and 24-bit colors in the terminal (#7188, @Alizter) diff --git a/bench/micro/copyfile.ml b/bench/micro/copyfile.ml new file mode 100644 index 000000000000..a43188c2e203 --- /dev/null +++ b/bench/micro/copyfile.ml @@ -0,0 +1,14 @@ +open Stdune + +let dir = Temp.create Dir ~prefix:"copyfile" ~suffix:"bench" + +let contents = String.make 50_000 '0' + +let () = + let src = Path.relative dir "initial" in + Io.write_file (Path.relative dir "initial") contents; + let chmod _ = 444 in + for i = 1 to 10_000 do + let dst = Path.relative dir (sprintf "dst-%d" i) in + Io.copy_file ~chmod ~src ~dst () + done diff --git a/bench/micro/dune b/bench/micro/dune index 345f896dfaa5..71503f3bb538 100644 --- a/bench/micro/dune +++ b/bench/micro/dune @@ -1,3 +1,8 @@ +(executable + (name copyfile) + (modules copyfile) + (libraries stdune)) + (executable (name main) (modules main) diff --git a/otherlibs/stdune/src/copyfile_stubs.c b/otherlibs/stdune/src/copyfile_stubs.c new file mode 100644 index 000000000000..ed66c4948926 --- /dev/null +++ b/otherlibs/stdune/src/copyfile_stubs.c @@ -0,0 +1,41 @@ +#include +#include +#include + +#if defined(__APPLE__) +#include +#include +#include + +CAMLprim value stdune_copyfile(value v_from, value v_to) { + CAMLparam2(v_from, v_to); + char *from = caml_stat_strdup(String_val(v_from)); + char *to = caml_stat_strdup(String_val(v_to)); + caml_release_runtime_system(); + int ret = copyfile(from, to, NULL, COPYFILE_CLONE); + caml_stat_free(from); + caml_stat_free(to); + caml_acquire_runtime_system(); + if (ret < 0) { + uerror("copyfile", Nothing); + } + CAMLreturn(Val_unit); +} + +CAMLprim value stdune_is_darwin(value v_unit) { + CAMLparam1(v_unit); + CAMLreturn(Val_true); +} + +#else + +CAMLprim value stdune_copyfile(value v_from, value v_to) { + caml_failwith("copyfile: only on macos"); +} + +CAMLprim value stdune_is_darwin(value v_unit) { + CAMLparam1(v_unit); + CAMLreturn(Val_false); +} + +#endif diff --git a/otherlibs/stdune/src/dune b/otherlibs/stdune/src/dune index 44006601972b..00899f60b934 100644 --- a/otherlibs/stdune/src/dune +++ b/otherlibs/stdune/src/dune @@ -12,6 +12,6 @@ (re_export dune_filesystem_stubs)) (foreign_stubs (language c) - (names wait3_stubs)) + (names wait3_stubs copyfile_stubs)) (instrumentation (backend bisect_ppx))) diff --git a/otherlibs/stdune/src/io.ml b/otherlibs/stdune/src/io.ml index 33846e94dd59..f736ee10b055 100644 --- a/otherlibs/stdune/src/io.ml +++ b/otherlibs/stdune/src/io.ml @@ -248,10 +248,31 @@ struct in (ic, oc) + module Copyfile = struct + (* Bindings to mac's fast copy function. It's similar to a hardlink, except + it does COW when edited. It will also default back to regular copying if + it fails for w/e reason *) + external copyfile : string -> string -> unit = "stdune_copyfile" + + external available : unit -> bool = "stdune_is_darwin" + end + let copy_file ?chmod ~src ~dst () = Exn.protectx (setup_copy ?chmod ~src ~dst ()) ~finally:close_both ~f:(fun (ic, oc) -> copy_channels ic oc) + let copy_file = + match Copyfile.available () with + | false -> copy_file + | true -> ( + fun ?chmod ~src ~dst () -> + let src = Path.to_string src in + let dst = Path.to_string dst in + Copyfile.copyfile src dst; + match chmod with + | None -> () + | Some chmod -> (Unix.stat src).st_perm |> chmod |> Unix.chmod dst) + let file_line path n = with_file_in ~binary:false path ~f:(fun ic -> for _ = 1 to n - 1 do