lsd: LSD is very slow compared to ls when listing large directories

Listing the Void Linux repo srcpkgs directory (about 12055 directories) takes far longer with lsd than it does with GNU ls.

lsd --color never 1.39s user 0.12s system 99% cpu 1.521 total lsd 1.42s user 0.12s system 98% cpu 1.559 total ls 0.01s user 0.02s system 91% cpu 0.034 total

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 20
  • Comments: 17 (2 by maintainers)

Most upvoted comments

This is how I looked into the issue of lsd’s bad performance.

I created a directory in /tmp with 10,000 empty files. On my Linux system, /tmp is a tmpfs, and bash was the shell I used.

mkdir /tmp/1E4
cd /tmp/1E4
touch $(seq 1E4)

Then I compared the execution time of exa, lsd, ls, and uu-ls on that directory.

The options used were intended to disable features that differ between programs (such as icons and colors) but still display “long” output.

printf '\n\nexa'   ; time LC_ALL=C exa --color=never --no-icons --long --sort=none > /dev/null
printf '\n\nlsd'   ; time LC_ALL=C lsd --color=never --icon=never --long --sort=none --ignore-config > /dev/null
printf '\n\nls'    ; time LC_ALL=C ls --color=never -l --sort=none > /dev/null
printf '\n\nuu-ls' ; time LC_ALL=C uu-ls --color=never -l --sort=none > /dev/null
exa
real    0m0.082s
user    0m0.064s
sys     0m0.045s


lsd
real    0m4.467s
user    0m1.506s
sys     0m1.913s


ls
real    0m0.028s
user    0m0.010s
sys     0m0.016s


uu-ls
real    0m0.036s
user    0m0.025s
sys     0m0.010s

Next I logged the system calls made by each program.

LC_ALL=C strace exa --color=never --no-icons --long --sort=none > /dev/null 2> ~/strace.exa.txt
LC_ALL=C strace lsd --color=never --icon=never --long --sort=none --ignore-config > /dev/null 2> ~/strace.lsd.txt
LC_ALL=C strace ls --color=never -l --sort=none > /dev/null 2> ~/strace.ls.txt
LC_ALL=C strace uu-ls --color=never -l --sort=none > /dev/null 2> ~/strace.uu-ls.txt
cd
wc --lines --total=never strace.*.txt
    21320 strace.exa.txt
  2777707 strace.lsd.txt
    20512 strace.ls.txt
    10499 strace.uu-ls.txt

You can see that lsd had many more system calls than the others. It’s strace output was about 176M, so keep that in mind if you test on a directory with more files.

Here’s a count of how many times each system call was made for each program.

printf '\n\nexa\n'   ; sed -n -E -e 's/\(.*//gp' strace.exa.txt   | sort | uniq -c
printf '\n\nlsd\n'   ; sed -n -E -e 's/\(.*//gp' strace.lsd.txt   | sort | uniq -c
printf '\n\nls\n'    ; sed -n -E -e 's/\(.*//gp' strace.ls.txt    | sort | uniq -c
printf '\n\nuu-ls\n' ; sed -n -E -e 's/\(.*//gp' strace.uu-ls.txt | sort | uniq -c
exa
      1 access
      2 arch_prctl
     34 brk
      4 clone3
     17 close
      1 execve
      1 exit_group
   1059 futex
     11 getdents64
      2 getrandom
      1 ioctl
     56 mmap
     19 mprotect
      5 mremap
      5 munmap
     16 newfstatat
     19 openat
      1 poll
      2 pread64
      2 prlimit64
     36 read
      1 rseq
      6 rt_sigaction
      9 rt_sigprocmask
      2 sched_getaffinity
      1 set_robust_list
      1 set_tid_address
      3 sigaltstack
  10001 statx
  10001 write


lsd
      1 access
      2 arch_prctl
  34008 brk
 800395 close
  20306 connect
  20002 epoll_create1
 120012 epoll_ctl
  80008 epoll_pwait2
    989 epoll_wait
      1 execve
      1 exit_group
      2 futex
  40013 getdents64
      1 getpid
      3 getrandom
      2 ioctl
  30003 lgetxattr
  30003 lseek
     30 mmap
      8 mprotect
      9 mremap
      7 munmap
 470059 newfstatat
 940106 openat
      1 poll
      6 prctl
      2 pread64
      2 prlimit64
  30015 read
  10001 readlink
  20991 recvfrom
      1 rseq
      5 rt_sigaction
  40004 rt_sigprocmask
      1 sched_getaffinity
  20002 sendto
      1 set_robust_list
      1 set_tid_address
      3 sigaltstack
  20306 socket
  10001 statx
  20002 timerfd_create
  20389 timerfd_settime
      1 write


ls
      1 access
      2 arch_prctl
      5 brk
     96 close
      6 connect
      2 epoll_create1
     12 epoll_ctl
      8 epoll_pwait2
      1 execve
      1 exit_group
      2 futex
     13 getdents64
      1 getpid
      2 getrandom
  10000 getxattr
      2 ioctl
      3 lseek
     28 mmap
      7 mprotect
      4 mremap
      3 munmap
     59 newfstatat
    105 openat
      6 prctl
      2 pread64
      1 prlimit64
     12 read
      2 recvfrom
      1 rseq
      4 rt_sigprocmask
      2 sendto
      1 set_robust_list
      1 set_tid_address
      6 socket
  10000 statx
      2 timerfd_create
      3 timerfd_settime
    105 write


uu-ls
      1 access
      2 arch_prctl
     11 brk
     95 close
      6 connect
      2 epoll_create1
     12 epoll_ctl
      8 epoll_pwait2
      1 execve
      1 exit_group
      2 futex
     13 getdents64
      1 getpid
      3 getrandom
      2 ioctl
      3 lseek
     28 mmap
      8 mprotect
      4 mremap
      5 munmap
     60 newfstatat
    108 openat
      1 poll
      6 prctl
      2 pread64
      2 prlimit64
     15 read
      2 recvfrom
      1 rseq
      5 rt_sigaction
      4 rt_sigprocmask
      1 sched_getaffinity
      2 sendto
      1 set_robust_list
      1 set_tid_address
      3 sigaltstack
      6 socket
  10001 statx
      2 timerfd_create
      2 timerfd_settime
     65 write

I used a spreadsheet to put the strace results in a table for easier comparison.

System call exa lsd ls uu-ls
access 1 1 1 1
arch_prctl 2 2 2 2
brk 34 34008 5 11
clone3 4
close 17 800395 96 95
connect 20306 6 6
epoll_create1 20002 2 2
epoll_ctl 120012 12 12
epoll_pwait2 80008 8 8
epoll_wait 989
execve 1 1 1 1
exit_group 1 1 1 1
futex 1059 2 2 2
getdents64 11 40013 13 13
getpid 1 1 1
getrandom 2 3 2 3
getxattr 10000
ioctl 1 2 2 2
lgetxattr 30003
lseek 30003 3 3
mmap 56 30 28 28
mprotect 19 8 7 8
mremap 5 9 4 4
munmap 5 7 3 5
newfstatat 16 470059 59 60
openat 19 940106 105 108
poll 1 1 6 1
prctl 6 6
pread64 2 2 2 2
prlimit64 2 2 1 2
read 36 30015 12 15
readlink 10001
recvfrom 20991 2 2
rseq 1 1 1 1
rt_sigaction 6 5 5
rt_sigprocmask 9 40004 4 4
sched_getaffinity 2 1 1
sendto 20002 2 2
set_robust_list 1 1 1 1
set_tid_address 1 1 1 1
sigaltstack 3 3 3
socket 20306 6 6
statx 10001 10001 10000 10001
timerfd_create 20002 2 2
timerfd_settime 20389 3 2
write 10001 1 105 65

Here are some of the calls that stood out to me.

grep -F 'readlink(' strace.lsd.txt

  • readlink was called for every file, even though they aren’t symbolic links.

grep -E '^(socket|connect)\(' strace.lsd.txt | sort | uniq -c | sort -n | tail

  • 20,000 sockets were created and connected to “/run/systemd/userdb/io.systemd.DynamicUser”.

grep -F '/etc/nsswitch.conf' strace.lsd.txt | sort | uniq -c | sort -n | tail

  • “/etc/nsswitch.conf” was stat’d 30,000 times.

grep -E '^openat\(.+"/' strace.lsd.txt | sort | uniq -c | sort -n | tail

  • “/etc/passwd” was opened and read 10,000 times.
  • “/etc/group” was opened and read 20,000 times.
  • “/run/systemd/userdb/” was opened 20,000 times.
  • “/” was opened 150,000 times!

tl;dr: WTH?!

+1 to this. I’ve just did the other test: compared lsd and exa when listing long output and tree view: exa -l -T 2.54s user 6.14s system 47% cpu 18.298 total lsd -l --tree 332.53s user 96.61s system 50% cpu 14:13.04 total (obviously no info from vanilla ls, as it doesn’t have tree functionality).

I did it on my 13GB home directory (macOS, so many small files in ~/Library).

I just set total-size: false and it performs fast and snappy again. Maybe it’s a clearer test to remove --ignore-config from the lsd command?

No. In my example there were two calls of lsd: one that uses the config file, and one that ignores it. The intent was to show that neither method has acceptable performance.

I added total-size: false to my config.yaml, but I didn’t see a difference in performance.

Commands:

mkdir -p /tmp/1E4 ; cd /tmp/1E4 ; touch $(seq 1E4)
printf '\neza'          ; time LC_ALL=C command eza   --color=never --sort=none -l --no-icons --group > /dev/null
printf '\nlsd'          ; time LC_ALL=C command lsd   --color=never --sort=none -l --icon=never --ignore-config > /dev/null
printf '\nlsd (config)' ; time LC_ALL=C command lsd   --color=never --sort=none -l --icon=never > /dev/null
printf '\nls'           ; time LC_ALL=C command ls    --color=never --sort=none -l > /dev/null
printf '\nuu-ls'        ; time LC_ALL=C command uu-ls --color=never --sort=none -l > /dev/null

Output:

eza real 0m0.093s user 0m0.060s sys 0m0.072s

lsd real 0m5.200s user 0m1.325s sys 0m1.688s

lsd (config) real 0m5.974s user 0m1.165s sys 0m1.659s

ls real 0m0.064s user 0m0.034s sys 0m0.029s

uu-ls real 0m0.056s user 0m0.041s sys 0m0.014s

as a workaround for now i turned off total-size which resulted in a 3632x speedup.

Here is how lsd compares to exa and ls at the moment. Test cases is a directory with 10,376 files (a fuzzing corpus).

$ hyperfine ls exa lsd
Benchmark 1: ls
  Time (mean ± σ):      16.6 ms ±   1.9 ms    [User: 12.4 ms, System: 4.1 ms]
  Range (min … max):    13.1 ms …  20.3 ms    144 runs

Benchmark 2: exa
  Time (mean ± σ):      47.9 ms ±   5.7 ms    [User: 22.5 ms, System: 25.0 ms]
  Range (min … max):    38.8 ms …  61.5 ms    57 runs

Benchmark 3: lsd
  Time (mean ± σ):      7.213 s ±  0.310 s    [User: 0.897 s, System: 1.699 s]
  Range (min … max):    7.043 s …  8.060 s    10 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Summary
  'ls' ran
    2.89 ± 0.47 times faster than 'exa'
  434.83 ± 52.45 times faster than 'lsd'

So plain-old ls is 400 times faster than lsd.

$ ls --version
ls (GNU coreutils) 9.2
Copyright (C) 2023 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Richard M. Stallman and David MacKenzie.
$ lsd --version
lsd 0.23.1
$ exa --version
exa - list files on the command-line
v0.10.1 [+git]
https://the.exa.website/

Since #441 was closed, is there any indication whether this issue will be fixed?

Same here. The time of listing 287188 files:

❯ ls | wc -l     
287188
❯ time lsd 1>/dev/null
lsd > /dev/null  6.50s user 2.60s system 99% cpu 9.131 total
❯ time ls 1>/dev/null
ls > /dev/null  0.42s user 0.10s system 99% cpu 0.525 total

Whyt lsd is much slower than ls? Is there anything we can do?

Use strace with ls, exa, and lsd in a large directory, and notice that lsd is making many more system calls that the other 2.

strace ls --color=never -l --sort=none 2> ~/strace.ls.txt strace exa --color=never --no-icons --long --sort=none 2> ~/strace.exa.txt strace lsd --color=never --icon=never --long --sort=none 2> ~/strace.lsd.txt

wc --lines --total=never strace.*.txt

3183 strace.exa.txt 4900 strace.ls.txt 408402 strace.lsd.txt