-
Notifications
You must be signed in to change notification settings - Fork 329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Copy operations are (relatively) slow #1685
Comments
Lf copies a file in relatively small chunks of fixed size (4KB) Lines 51 to 86 in ce12116
which could be suboptimal. In an ideal world it would use However, it's also nice to have a % progress of an ongoing copy, which Increasing it to 4MB decreases the copy duration by about 30%. * - at least I haven't found any in 2021. |
Would this approach provide a possible solution: |
I don't think so, staticcheck gives me a bunch of type errors and an infinite recursive call. But let's assume it's a skill issue on my part and/or author forgot to copy the working version to his blog, and it actually does what it's supposed to. Most likely it would just use a default 32KB buffer of So different implementation, but no big difference, no accelerated copy methods used, which is where the actual speed comes from. Look, buffer can be increased even right now, I personally think it's quite conservative - aside from a small text file, what will even fit in 4KB in our days? But I don't think it will help a lot - as I've said above, even with 4 MB it's only -30% time to copy, not five times difference. |
Basically what we would need to get the same speed as with the Alternatively there could be a copy mode that does not show progress but benefits from io.Copy() as it is?! |
TL;DR: make (but it will not make really cool shit like reflinks or server side copy work, hence my rant about If you don't care about the progress at all, you could just implement your own copy function in lf config. Here and below are few examples for various use cases of how to do that.
However, I've just checked:
Does it hold within lf?But one thing I've originally dismissed as "probably fine and asynchronous anyway" is UI. During a file copy a number of bytes being read (EACH read) is sent to
Findings
Read the process below, if you care. I've added a simple timer to measure how long those draws take: case n := <-app.nav.copyBytesChan:
app.nav.copyBytes += n
// n is usually 4096B so update roughly per 4096B x 1024 = 4MB copied
if app.nav.copyUpdate++; app.nav.copyUpdate >= 1024 {
app.nav.copyUpdate = 0
start := time.Now()
app.ui.draw(app.nav)
log.Printf("`app.ui.draw(app.nav)` took %s", time.Since(start))
} And for the same file, median time to So how much buffering in channels help? Adding
Making both channels So does it actually help with copy speed?
FUCK. Okay so longer channels and having a preview redraw updates doesn't matter as much as I thought, maybe
Okay, so bigger buffer gets you about the same time as But here is the question: Does bigger buffer itself help, or does it help because it means that instead of every
Okay, buffer is helpful, the rest isn't. But can we do better?
So 32KB is a sweet spot. Here is the patch I've used to get those resultsdiff --git a/app.go b/app.go
index a95b180..3e5a561 100644
--- a/app.go
+++ b/app.go
@@ -333,9 +333,15 @@ func (app *app) loop() {
case n := <-app.nav.copyBytesChan:
app.nav.copyBytes += n
// n is usually 4096B so update roughly per 4096B x 1024 = 4MB copied
- if app.nav.copyUpdate++; app.nav.copyUpdate >= 1024 {
+ if app.nav.copyUpdate++; app.nav.copyUpdate >= updatePeriodItems {
app.nav.copyUpdate = 0
+ // start := time.Now()
app.ui.draw(app.nav)
+ // log.Printf(
+ // "`len(app.nav.copyBytesChan) = %d; app.ui.draw(app.nav)` took %s",
+ // len(app.nav.copyBytesChan),
+ // time.Since(start),
+ // )
}
case n := <-app.nav.copyTotalChan:
app.nav.copyTotal += n
diff --git a/copy.go b/copy.go
index cec83c2..01e1ce6 100644
--- a/copy.go
+++ b/copy.go
@@ -3,14 +3,22 @@ package main
import (
"fmt"
"io"
+ "log"
"os"
"path/filepath"
"strconv"
"strings"
+ "time"
"github.com/djherbis/times"
)
+const (
+ numsChanSize = 1024
+ copyBufSize = 32 * 1024
+ updatePeriodItems = 1024
+)
+
func copySize(srcs []string) (int64, error) {
var total int64
@@ -36,6 +44,7 @@ func copySize(srcs []string) (int64, error) {
}
func copyFile(src, dst string, preserve []string, info os.FileInfo, nums chan int64) error {
+ start := time.Now()
var dst_mode os.FileMode = 0o666
preserve_timestamps := false
for _, s := range preserve {
@@ -47,7 +56,7 @@ func copyFile(src, dst string, preserve []string, info os.FileInfo, nums chan in
}
}
- buf := make([]byte, 4096)
+ buf := make([]byte, copyBufSize)
r, err := os.Open(src)
if err != nil {
@@ -94,11 +103,19 @@ func copyFile(src, dst string, preserve []string, info os.FileInfo, nums chan in
}
}
+ log.Printf(
+ "copyFile() took %s; nums set as copyBytesChan as %d; buf = %d; update every %d item",
+ time.Since(start),
+ numsChanSize,
+ copyBufSize,
+ updatePeriodItems,
+ )
+
return nil
}
func copyAll(srcs []string, dstDir string, preserve []string) (nums chan int64, errs chan error) {
- nums = make(chan int64, 1024)
+ nums = make(chan int64, numsChanSize)
errs = make(chan error, 1024)
go func() {
diff --git a/nav.go b/nav.go
index ba9b2c3..b9257ea 100644
--- a/nav.go
+++ b/nav.go
@@ -577,7 +577,7 @@ func (nav *nav) getDirs(wd string) {
func newNav(height int) *nav {
nav := &nav{
- copyBytesChan: make(chan int64, 1024),
+ copyBytesChan: make(chan int64, numsChanSize),
copyTotalChan: make(chan int64, 1024),
moveCountChan: make(chan int, 1024),
moveTotalChan: make(chan int, 1024),
|
But how does it affect copying a directory with a bunch of smaller files (e.g. 4.4GB of pictures)?
Again, about -30% time. For reference, |
This makes `:paste` comparable in performance to `cp --reflink=never` for large files. For large number of small files improvements are less substantial (compared to `cp -r --reflink=never`), though still noticeable. In both cases the copy takes about 30% less time than with `buf` size at 4096. 32KB is the same number `io.Copy()` uses internally (when it can), and is about where improvements stop. Context: gokcehan#1685 (comment)
* Use 32KB buffer for `copyFile()` (reduces copy time by 30%) This makes `:paste` comparable in performance to `cp --reflink=never` for large files. For large number of small files improvements are less substantial (compared to `cp -r --reflink=never`), though still noticeable. In both cases the copy takes about 30% less time than with `buf` size at 4096. 32KB is the same number `io.Copy()` uses internally (when it can), and is about where improvements stop. Context: #1685 (comment) * Apply suggested changes * Just delete incomplete an file on all errors * Mention new progress UI update frequency * Return the old (each 4MB copied) progress update frequency (progress UI updates don't seem to impact performance much)
It takes my setup about 1 second to copy a 1GB file using "cp"
But the same operation takes about 5 seconds in lf.
After digging through the code I am still unsure what causes this issue.
The text was updated successfully, but these errors were encountered: