Multi-threaded FTS-based disk usage calculation

Implements pthreads based du using FTS. Currently 4 threads are used.
NFTW is dropped as there is no way to pass any custom values to fn().

FTS does not require any per entry function callback.
The performance numbers are best with FTS:

./nnn-fts -T d /  5.29s user 0.94s system 116% cpu 5.335 total
1/21 du:102.402G free:8.476G files:397644 101132550144B

./nnn-ftw -T d /  5.52s user 0.94s system 116% cpu 5.534 total
1/21 du:102.400G free:8.474G files:397653 101133148160B

./nnn-4.0 -T d /  0.20s user 2.27s system 24% cpu 10.241 total
1/21 du:102.400G free:8.475G files:397654 101133299712B
This commit is contained in:
Arun Prakash Jana 2021-05-26 21:56:59 +05:30
parent 025b570e05
commit 284a3c4866
No known key found for this signature in database
GPG key ID: A75979F35C080412
3 changed files with 146 additions and 66 deletions

View file

@ -119,7 +119,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wshadow
CFLAGS += $(CFLAGS_OPTIMIZATION) CFLAGS += $(CFLAGS_OPTIMIZATION)
CFLAGS += $(CFLAGS_CURSES) CFLAGS += $(CFLAGS_CURSES)
LDLIBS += $(LDLIBS_CURSES) LDLIBS += $(LDLIBS_CURSES) -lpthread
# static compilation needs libgpm development package # static compilation needs libgpm development package
ifeq ($(strip $(O_STATIC)),1) ifeq ($(strip $(O_STATIC)),1)

View file

@ -124,8 +124,7 @@ CFLAGS += -std=c11 -Wall -Wextra -Wshadow
CFLAGS += $(CFLAGS_OPTIMIZATION) CFLAGS += $(CFLAGS_OPTIMIZATION)
CFLAGS += $(CFLAGS_CURSES) CFLAGS += $(CFLAGS_CURSES)
LDLIBS += $(LDLIBS_CURSES) $(LDLIBS_HAIKU) LDLIBS += $(LDLIBS_CURSES) -lpthread $(LDLIBS_HAIKU)
# static compilation needs libgpm development package # static compilation needs libgpm development package
ifeq ($(strip $(O_STATIC)),1) ifeq ($(strip $(O_STATIC)),1)
LDFLAGS += -static LDFLAGS += -static

207
src/nnn.c
View file

@ -74,11 +74,13 @@
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <fts.h>
#include <libgen.h> #include <libgen.h>
#include <limits.h> #include <limits.h>
#ifndef NOLC #ifndef NOLC
#include <locale.h> #include <locale.h>
#endif #endif
#include <pthread.h>
#include <stdio.h> #include <stdio.h>
#ifndef NORL #ifndef NORL
#include <readline/history.h> #include <readline/history.h>
@ -336,7 +338,8 @@ typedef struct {
uint_t dirctx : 1; /* Show dirs in context color */ uint_t dirctx : 1; /* Show dirs in context color */
uint_t uidgid : 1; /* Show owner and group info */ uint_t uidgid : 1; /* Show owner and group info */
uint_t prstssn : 1; /* Persistent session */ uint_t prstssn : 1; /* Persistent session */
uint_t reserved : 8; /* Adjust when adding/removing a field */ uint_t duinit : 1; /* Initialize disk usage */
uint_t reserved : 7; /* Adjust when adding/removing a field */
} runstate; } runstate;
/* Contexts or workspaces */ /* Contexts or workspaces */
@ -424,9 +427,7 @@ static char *fifopath;
#endif #endif
static unsigned long long *ihashbmp; static unsigned long long *ihashbmp;
static struct entry *pdents; static struct entry *pdents;
static blkcnt_t ent_blocks;
static blkcnt_t dir_blocks; static blkcnt_t dir_blocks;
static ulong_t num_files;
static kv *bookmark; static kv *bookmark;
static kv *plug; static kv *plug;
static uchar_t tmpfplen, homelen; static uchar_t tmpfplen, homelen;
@ -440,6 +441,28 @@ static pcre *archive_pcre;
static regex_t archive_re; static regex_t archive_re;
#endif #endif
/* pthread related */
#define NUM_DU_THREADS (4) /* Can use sysconf(_SC_NPROCESSORS_ONLN) */
#define DU_TEST (((node->fts_info & FTS_F) && \
(sb->st_nlink <= 1 || test_set_bit((uint_t)sb->st_ino))) || node->fts_info & FTS_DP)
static int threadbmp = -1; /* Has 1 in the bit position for idle threads */
static volatile int active_threads = 0;
static pthread_mutex_t running_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t hardlink_mutex = PTHREAD_MUTEX_INITIALIZER;
static ulong_t *core_files;
static blkcnt_t *core_blocks;
static _Atomic volatile ulong_t num_files;
typedef struct {
char path[PATH_MAX];
int entnum;
ushort_t core;
bool mntpoint;
} thread_data;
static thread_data *core_data;
/* Retain old signal handlers */ /* Retain old signal handlers */
static struct sigaction oldsighup; static struct sigaction oldsighup;
static struct sigaction oldsigtstp; static struct sigaction oldsigtstp;
@ -777,7 +800,6 @@ static haiku_nm_h haiku_hnd;
/* Forward declarations */ /* Forward declarations */
static void redraw(char *path); static void redraw(char *path);
static int spawn(char *file, char *arg1, char *arg2, char *arg3, uchar_t flag); static int spawn(char *file, char *arg1, char *arg2, char *arg3, uchar_t flag);
static int (*nftw_fn)(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf);
static void move_cursor(int target, int ignore_scrolloff); static void move_cursor(int target, int ignore_scrolloff);
static char *load_input(int fd, const char *path); static char *load_input(int fd, const char *path);
static int set_sort_flags(int r); static int set_sort_flags(int r);
@ -853,12 +875,16 @@ static bool test_set_bit(uint_t nr)
{ {
nr &= HASH_BITS; nr &= HASH_BITS;
unsigned long long *m = ((unsigned long long *)ihashbmp) + (nr >> 6); pthread_mutex_lock(&hardlink_mutex);
ulong_t *m = ((ulong_t *)ihashbmp) + (nr >> 6);
if (*m & (1 << (nr & 63))) if (*m & (1 << (nr & 63))) {
pthread_mutex_unlock(&hardlink_mutex);
return FALSE; return FALSE;
}
*m |= 1 << (nr & 63); *m |= 1 << (nr & 63);
pthread_mutex_unlock(&hardlink_mutex);
return TRUE; return TRUE;
} }
@ -4954,36 +4980,63 @@ static bool handle_cmd(enum action sel, const char *current, char *newpath)
return TRUE; return TRUE;
} }
static int sum_bsize(const char *UNUSED(fpath), const struct stat *sb, int typeflag, struct FTW *UNUSED(ftwbuf))
{
if (sb->st_blocks
&& ((typeflag == FTW_F && (sb->st_nlink <= 1 || test_set_bit((uint_t)sb->st_ino)))
|| typeflag == FTW_D))
ent_blocks += sb->st_blocks;
++num_files;
return 0;
}
static int sum_asize(const char *UNUSED(fpath), const struct stat *sb, int typeflag, struct FTW *UNUSED(ftwbuf))
{
if (sb->st_size
&& ((typeflag == FTW_F && (sb->st_nlink <= 1 || test_set_bit((uint_t)sb->st_ino)))
|| typeflag == FTW_D))
ent_blocks += sb->st_size;
++num_files;
return 0;
}
static void dentfree(void) static void dentfree(void)
{ {
free(pnamebuf); free(pnamebuf);
free(pdents); free(pdents);
free(mark); free(mark);
/* Thread data cleanup */
free(core_blocks);
free(core_data);
free(core_files);
} }
static blkcnt_t dirwalk(char *path, struct stat *psb) static void *du_thread(void *p_data)
{
thread_data *pdata = (thread_data *)p_data;
char *path[2] = {pdata->path, NULL};
ulong_t tfiles = 0;
blkcnt_t tblocks = 0;
struct stat *sb;
FTS *tree = fts_open(path, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR, 0);
FTSENT *node;
while ((node = fts_read(tree))) {
if (node->fts_info & FTS_D)
continue;
sb = node->fts_statp;
if (cfg.apparentsz) {
if (sb->st_size && DU_TEST)
tblocks += sb->st_size;
} else if (sb->st_blocks && DU_TEST)
tblocks += sb->st_blocks;
++tfiles;
}
fts_close(tree);
if (pdata->entnum >= 0)
pdents[pdata->entnum].blocks = tblocks;
if (!pdata->mntpoint) {
core_blocks[pdata->core] += tblocks;
core_files[pdata->core] += tfiles;
} else
core_files[pdata->core] += 1;
pthread_mutex_lock(&running_mutex);
threadbmp |= (1 << pdata->core);
--active_threads;
pthread_mutex_unlock(&running_mutex);
return NULL;
}
static void dirwalk(char *dir, char *path, int entnum, bool mountpoint)
{ {
#ifndef __APPLE__ #ifndef __APPLE__
static uint_t open_max; static uint_t open_max;
@ -4993,22 +5046,49 @@ static blkcnt_t dirwalk(char *path, struct stat *psb)
open_max = max_openfds(); open_max = max_openfds();
#endif #endif
ent_blocks = 0; /* Loop till any core is free */
while (active_threads == NUM_DU_THREADS){}
if (g_state.interrupt)
return;
pthread_mutex_lock(&running_mutex);
++active_threads;
int core = ffs(threadbmp) - 1;
threadbmp &= ~(1 << core);
pthread_mutex_unlock(&running_mutex);
xstrsncpy(core_data[core].path, path, PATH_MAX);
core_data[core].entnum = entnum;
core_data[core].core = (ushort_t)core;
core_data[core].mntpoint = mountpoint;
pthread_t tid = 0;
pthread_create(&tid, NULL, du_thread, (void *)&(core_data[core]));
redraw(dir);
tolastln(); tolastln();
addstr(xbasename(path));
addstr(" [^C aborts]\n"); addstr(" [^C aborts]\n");
refresh(); refresh();
#ifndef __APPLE__
if (nftw(path, nftw_fn, open_max, FTW_MOUNT | FTW_PHYS) < 0) {
#else
if (nftw(path, nftw_fn, OPEN_MAX, FTW_MOUNT | FTW_PHYS) < 0) {
#endif
DPRINTF_S("nftw failed");
return cfg.apparentsz ? psb->st_size : psb->st_blocks;
} }
return ent_blocks; static void prep_threads(void)
{
if (!g_state.duinit) {
/* drop MSB 1s */
threadbmp >>= (32 - NUM_DU_THREADS);
core_blocks = calloc(NUM_DU_THREADS, sizeof(blkcnt_t));
core_data = calloc(NUM_DU_THREADS, sizeof(thread_data));
core_files = calloc(NUM_DU_THREADS, sizeof(ulong_t));
g_state.duinit = TRUE;
} else {
memset(core_blocks, 0, NUM_DU_THREADS * sizeof(blkcnt_t));
memset(core_data, 0, NUM_DU_THREADS * sizeof(thread_data));
memset(core_files, 0, NUM_DU_THREADS * sizeof(ulong_t));
}
} }
/* Skip self and parent */ /* Skip self and parent */
@ -5020,8 +5100,7 @@ static bool selforparent(const char *path)
static int dentfill(char *path, struct entry **ppdents) static int dentfill(char *path, struct entry **ppdents)
{ {
uchar_t entflags = 0; uchar_t entflags = 0;
int n = 0, flags = 0; int flags = 0;
ulong_t num_saved;
struct dirent *dp; struct dirent *dp;
char *namep, *pnb, *buf = NULL; char *namep, *pnb, *buf = NULL;
struct entry *dentp; struct entry *dentp;
@ -5029,6 +5108,8 @@ static int dentfill(char *path, struct entry **ppdents)
struct stat sb_path, sb; struct stat sb_path, sb;
DIR *dirp = opendir(path); DIR *dirp = opendir(path);
ndents = 0;
DPRINTF_S(__func__); DPRINTF_S(__func__);
if (!dirp) if (!dirp)
@ -5053,6 +5134,8 @@ static int dentfill(char *path, struct entry **ppdents)
} else } else
memset(ihashbmp, 0, HASH_OCTETS << 3); memset(ihashbmp, 0, HASH_OCTETS << 3);
prep_threads();
attron(COLOR_PAIR(cfg.curctx + 1)); attron(COLOR_PAIR(cfg.curctx + 1));
} }
@ -5095,13 +5178,11 @@ static int dentfill(char *path, struct entry **ppdents)
if (S_ISDIR(sb.st_mode)) { if (S_ISDIR(sb.st_mode)) {
if (sb_path.st_dev == sb.st_dev) { // NOLINT if (sb_path.st_dev == sb.st_dev) { // NOLINT
mkpath(path, namep, buf); mkpath(path, namep, buf);
dirwalk(path, buf, -1, FALSE);
dir_blocks += dirwalk(buf, &sb);
if (g_state.interrupt) if (g_state.interrupt)
goto exit; goto exit;
ndents = n;
redraw(path);
} }
} else { } else {
/* Do not recount hard links */ /* Do not recount hard links */
@ -5128,7 +5209,10 @@ static int dentfill(char *path, struct entry **ppdents)
entflags = SYM_ORPHAN; entflags = SYM_ORPHAN;
} }
if (n == total_dents) { if (ndents == total_dents) {
if (cfg.blkorder)
while (active_threads) {}
total_dents += ENTRY_INCR; total_dents += ENTRY_INCR;
*ppdents = xrealloc(*ppdents, total_dents * sizeof(**ppdents)); *ppdents = xrealloc(*ppdents, total_dents * sizeof(**ppdents));
if (!*ppdents) { if (!*ppdents) {
@ -5157,13 +5241,13 @@ static int dentfill(char *path, struct entry **ppdents)
dentp = *ppdents; dentp = *ppdents;
dentp->name = pnamebuf; dentp->name = pnamebuf;
for (int count = 1; count < n; ++dentp, ++count) for (int count = 1; count < ndents; ++dentp, ++count)
/* Current file name starts at last file name start + length */ /* Current file name starts at last file name start + length */
(dentp + 1)->name = (char *)((size_t)dentp->name + dentp->nlen); (dentp + 1)->name = (char *)((size_t)dentp->name + dentp->nlen);
} }
} }
dentp = *ppdents + n; dentp = *ppdents + ndents;
/* Selection file name */ /* Selection file name */
dentp->name = (char *)((size_t)pnamebuf + off); dentp->name = (char *)((size_t)pnamebuf + off);
@ -5221,21 +5305,13 @@ static int dentfill(char *path, struct entry **ppdents)
if (cfg.blkorder) { if (cfg.blkorder) {
if (S_ISDIR(sb.st_mode)) { if (S_ISDIR(sb.st_mode)) {
num_saved = num_files + 1;
mkpath(path, namep, buf); mkpath(path, namep, buf);
/* Need to show the disk usage of this dir */ /* Need to show the disk usage of this dir */
dentp->blocks = dirwalk(buf, &sb); dirwalk(path, buf, ndents, (sb_path.st_dev != sb.st_dev)); // NOLINT
if (sb_path.st_dev == sb.st_dev) // NOLINT
dir_blocks += dentp->blocks;
else
num_files = num_saved;
if (g_state.interrupt) if (g_state.interrupt)
goto exit; goto exit;
ndents = n;
redraw(path);
} else { } else {
dentp->blocks = (cfg.apparentsz ? sb.st_size : sb.st_blocks); dentp->blocks = (cfg.apparentsz ? sb.st_size : sb.st_blocks);
/* Do not recount hard links */ /* Do not recount hard links */
@ -5260,18 +5336,25 @@ static int dentfill(char *path, struct entry **ppdents)
#endif #endif
} }
++n; ++ndents;
} while ((dp = readdir(dirp))); } while ((dp = readdir(dirp)));
exit: exit:
if (cfg.blkorder) if (cfg.blkorder) {
while (active_threads) {}
attroff(COLOR_PAIR(cfg.curctx + 1)); attroff(COLOR_PAIR(cfg.curctx + 1));
for (int i = 0; i < NUM_DU_THREADS; ++i) {
num_files += core_files[i];
dir_blocks += core_blocks[i];
}
}
/* Should never be null */ /* Should never be null */
if (closedir(dirp) == -1) if (closedir(dirp) == -1)
errexit(); errexit();
return n; return ndents;
} }
static void populate(char *path, char *lastname) static void populate(char *path, char *lastname)
@ -5525,7 +5608,6 @@ static int set_sort_flags(int r)
case 'a': /* Apparent du */ case 'a': /* Apparent du */
cfg.apparentsz ^= 1; cfg.apparentsz ^= 1;
if (cfg.apparentsz) { if (cfg.apparentsz) {
nftw_fn = &sum_asize;
cfg.blkorder = 1; cfg.blkorder = 1;
blk_shift = 0; blk_shift = 0;
} else } else
@ -5535,7 +5617,6 @@ static int set_sort_flags(int r)
if (r == 'd') { if (r == 'd') {
if (!cfg.apparentsz) if (!cfg.apparentsz)
cfg.blkorder ^= 1; cfg.blkorder ^= 1;
nftw_fn = &sum_bsize;
cfg.apparentsz = 0; cfg.apparentsz = 0;
blk_shift = ffs(S_BLKSIZE) - 1; blk_shift = ffs(S_BLKSIZE) - 1;
} }