commit 2c78f1b4e1454331dce5c87afe5a196ddd85c538
parent ee443f16950617658d4195c4a977b8997b3c6f00
Author: Arun Prakash Jana <engineerarun@gmail.com>
Date: Sun, 1 Feb 2026 11:14:55 +0530
Walk a directory tree using readdir to reduce FTS overhead
Diffstat:
| M | src/nnn.c | | | 154 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- |
1 file changed, 123 insertions(+), 31 deletions(-)
diff --git a/src/nnn.c b/src/nnn.c
@@ -501,8 +501,6 @@ static char curssn[NAME_MAX + 1];
/* pthread related */
#define NUM_DU_THREADS_MAX 32
-#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 num_du_threads; /* Set from sysconf(_SC_NPROCESSORS_ONLN), 2..NUM_DU_THREADS_MAX */
static unsigned int threadbmp; /* Has 1 in the bit position for idle threads */
@@ -6219,6 +6217,124 @@ static void dentfree(void)
free(core_files);
}
+static inline bool selforparent(const char *path);
+
+/* Walk a directory tree using readdir to reduce FTS overhead */
+static void du_walk_dir(const char *root, ullong_t *tfiles, blkcnt_t *tblocks)
+{
+ struct stat sb_root;
+ if (fstatat(AT_FDCWD, root, &sb_root, AT_SYMLINK_NOFOLLOW) == -1)
+ return;
+ if (!S_ISDIR(sb_root.st_mode))
+ return;
+
+ const dev_t root_dev = sb_root.st_dev;
+
+ /* Count root dir itself */
+ if (cfg.apparentsz)
+ *tblocks += sb_root.st_size;
+ else if (sb_root.st_blocks)
+ *tblocks += sb_root.st_blocks;
+ ++(*tfiles);
+
+ size_t cap = 64;
+ size_t len = 0;
+ char **stack = malloc(cap * sizeof(char *));
+ if (!stack)
+ return;
+ stack[len++] = xstrdup(root);
+
+ while (len && !g_state.interrupt) {
+ char *dirpath = stack[--len];
+ DIR *dirp = opendir(dirpath);
+ if (!dirp) {
+ free(dirpath);
+ continue;
+ }
+
+ const int dfd = dirfd(dirp);
+ struct dirent *dp;
+ while ((dp = readdir(dirp)) && !g_state.interrupt) {
+ if (selforparent(dp->d_name))
+ continue;
+
+ const unsigned char dtype = dp->d_type;
+ bool is_dir = (dtype == DT_DIR);
+ bool is_reg = (dtype == DT_REG);
+ bool sb_valid = false;
+
+ struct stat sb;
+ if (dtype == DT_UNKNOWN) {
+ if (fstatat(dfd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1)
+ continue;
+ sb_valid = true;
+ is_dir = S_ISDIR(sb.st_mode);
+ is_reg = S_ISREG(sb.st_mode);
+ }
+
+ /* Count blocks for directories and regular files */
+ if (is_dir) {
+ /* Stat if we haven't already */
+ if (!sb_valid) {
+ if (fstatat(dfd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1)
+ continue;
+ sb_valid = true;
+ }
+ if (cfg.apparentsz)
+ *tblocks += sb.st_size;
+ else if (sb.st_blocks)
+ *tblocks += sb.st_blocks;
+ } else if (is_reg) {
+ /* Stat if we haven't already */
+ if (!sb_valid) {
+ if (fstatat(dfd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1)
+ continue;
+ sb_valid = true;
+ }
+ /* Do not recount hard links */
+ if (sb.st_size && (sb.st_nlink <= 1 || test_set_bit((uint_t)sb.st_ino))) {
+ if (cfg.apparentsz)
+ *tblocks += sb.st_size;
+ else if (sb.st_blocks)
+ *tblocks += sb.st_blocks;
+ }
+ }
+
+ ++(*tfiles);
+
+ /* Recurse into directories on same device only */
+ if (is_dir) {
+ /* Stat if we haven't already to get device for xdev check */
+ if (!sb_valid) {
+ if (fstatat(dfd, dp->d_name, &sb, AT_SYMLINK_NOFOLLOW) == -1)
+ continue;
+ }
+ if (sb.st_dev == root_dev) {
+ if (len == cap) {
+ cap <<= 1;
+ char **tmp = realloc(stack, cap * sizeof(char *));
+ if (!tmp)
+ break;
+ stack = tmp;
+ }
+ char childbuf[PATH_MAX];
+ mkpath(dirpath, dp->d_name, childbuf);
+ char *child = xstrdup(childbuf);
+ if (child)
+ stack[len++] = child;
+ }
+ }
+ }
+
+ closedir(dirp);
+ free(dirpath);
+ }
+
+ while (len)
+ free(stack[--len]);
+ free(stack);
+}
+
static void *du_worker_loop(void *p_data)
{
thread_data *pdata = (thread_data *)p_data;
@@ -6241,37 +6357,11 @@ static void *du_worker_loop(void *p_data)
const bool no_aggregate = pdata->no_aggregate;
pthread_mutex_unlock(&running_mutex);
- /* Run FTS on this directory (same logic as former du_thread) */
- char *path[2] = {pathbuf, NULL};
ullong_t tfiles = 0;
blkcnt_t tblocks = 0;
- struct stat *sb;
- FTS *tree = fts_open(path, FTS_PHYSICAL | FTS_XDEV | FTS_NOCHDIR, 0);
- FTSENT *node;
+ du_walk_dir(pathbuf, &tfiles, &tblocks);
- if (tree) {
- while ((node = fts_read(tree))) {
- if (node->fts_info & FTS_D) {
- if (g_state.interrupt)
- break;
- 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;
-
- /* Count files and directories (via FTS_DP) */
- ++tfiles;
- }
- fts_close(tree);
- }
-
- pthread_mutex_lock(&running_mutex);
+ /* Update results with minimal lock time */
if (entnum >= 0)
pdents[entnum].blocks = tblocks;
@@ -6283,9 +6373,10 @@ static void *du_worker_loop(void *p_data)
core_files[core] += 1;
}
+ pthread_mutex_lock(&running_mutex);
threadbmp |= (1U << core);
--active_threads;
- pthread_cond_broadcast(&du_cond);
+ pthread_cond_signal(&du_cond); /* signal instead of broadcast for better performance */
pthread_mutex_unlock(&running_mutex);
}
/* not reached */
@@ -6334,6 +6425,7 @@ static bool prep_threads(void)
{
if (!g_state.duinit) {
long n = sysconf(_SC_NPROCESSORS_ONLN);
+
num_du_threads = (n > 0 && n <= NUM_DU_THREADS_MAX) ? (int)n : 4;
if (num_du_threads < 2)
num_du_threads = 2;