Files written at the same time take blocks in turn, so each file's
fragments were at most the 8-block window long.  Eight files appended
to in turn got 1949 fragments per 64 MB file, and four parallel dd of
256 MB got about 6400.

When a file has used up its window and keeps writing sequentially,
give it a window twice as big, up to 1024 blocks; any other miss starts
again from the base size.  The window is no longer dropped whenever the
inode is written, which would undo the growth, but at the last close,
so that a closed file leaves no reservation behind.

The appended files now get 23 fragments each, and the dd files about
55 to 70.  Kills of the translator during rm -rf and tar -xf, and
filling the disk with several writers, left e2fsck -f clean.  With two
concurrent tar -xf of a Linux tree, half as many files are fragmented
(0.4% against 0.8%), but about 200 more holes of 4 to 64K stay in the
free space of a 4 GB disk, 1% of it.  Most are the size of what a small
file leaves of its 8-block window, likely given back only after the
next file was placed, as the last close comes in with a no-senders
notification.
---
 ext2fs/ext2_fs.h |  3 +++
 ext2fs/getblk.c  | 39 ++++++++++++++++++++++++++-------------
 ext2fs/ialloc.c  |  1 +
 ext2fs/inode.c   | 16 +++++++++++++---
 4 files changed, 43 insertions(+), 16 deletions(-)

diff --git a/ext2fs/ext2_fs.h b/ext2fs/ext2_fs.h
index 5712d5d01..f7c3b9b2d 100644
--- a/ext2fs/ext2_fs.h
+++ b/ext2fs/ext2_fs.h
@@ -30,6 +30,8 @@
  */
 #define EXT2_PREALLOCATE
 #define EXT2_DEFAULT_PREALLOC_BLOCKS   8
+/* A file that keeps using up its window gets one twice as big, up to this.  */
+#define EXT2_MAX_PREALLOC_BLOCKS       1024
 
 /*
  * The second extended file system version
@@ -591,6 +593,7 @@ struct ext2_inode_info {
        __u32   i_next_alloc_goal;
        __u32   i_prealloc_block;
        __u32   i_prealloc_count;
+       __u32   i_prealloc_size;        /* Size of the last window */
        int     i_new_inode:1;  /* Is a freshly allocated inode */
 };
 
diff --git a/ext2fs/getblk.c b/ext2fs/getblk.c
index 9d4d2e247..43d511cd9 100644
--- a/ext2fs/getblk.c
+++ b/ext2fs/getblk.c
@@ -71,9 +71,18 @@ ext2_alloc_block (struct node *node, block_t goal, int zero)
 
 #ifdef EXT2_PREALLOCATE
   struct ext2_inode_info *info = &diskfs_node_disknode (node)->info;
-
-  if (info->i_prealloc_count &&
-      (goal == info->i_prealloc_block || goal + 1 == info->i_prealloc_block))
+  int sequential = (goal == info->i_prealloc_block
+                   || goal + 1 == info->i_prealloc_block);
+  block_t base =
+    S_ISREG (node->dn_stat.st_mode)
+    ? (sblock->s_prealloc_blocks ?: EXT2_DEFAULT_PREALLOC_BLOCKS)
+    : (S_ISDIR (node->dn_stat.st_mode)
+       && EXT2_HAS_COMPAT_FEATURE(sblock,
+                                 EXT2_FEATURE_COMPAT_DIR_PREALLOC))
+    ? sblock->s_prealloc_dir_blocks
+    : 0;
+
+  if (info->i_prealloc_count && sequential)
     {
       goal = info->i_prealloc_block;
       ext2_debug ("preallocation hit (%lu/%lu) => %u",
@@ -84,17 +93,21 @@ ext2_alloc_block (struct node *node, block_t goal, int zero)
       ext2_debug ("preallocation miss (%lu/%lu)",
                  alloc_hits, ++alloc_attempts);
       ext2_discard_prealloc (node);
+      /* A regular file that used up its window and goes on writing
+        sequentially gets a window twice as big, so that a file written
+        alongside others is interleaved with them in ever larger runs.
+        Anything else starts again from the base size.  */
+      if (S_ISREG (node->dn_stat.st_mode) && sequential
+         && info->i_prealloc_size)
+       info->i_prealloc_size = (info->i_prealloc_size * 2
+                                < EXT2_MAX_PREALLOC_BLOCKS
+                                ? info->i_prealloc_size * 2
+                                : EXT2_MAX_PREALLOC_BLOCKS);
+      else
+       info->i_prealloc_size = base;
     }
-  result = ext2_new_block
-    (goal,
-     S_ISREG (node->dn_stat.st_mode)
-     ? (sblock->s_prealloc_blocks ?: EXT2_DEFAULT_PREALLOC_BLOCKS)
-     : (S_ISDIR (node->dn_stat.st_mode)
-       && EXT2_HAS_COMPAT_FEATURE(sblock,
-                                  EXT2_FEATURE_COMPAT_DIR_PREALLOC))
-     ? sblock->s_prealloc_dir_blocks
-     : 0,
-     &info->i_prealloc_count, &info->i_prealloc_block);
+  result = ext2_new_block (goal, info->i_prealloc_size,
+                          &info->i_prealloc_count, &info->i_prealloc_block);
 
   /* Trap trying to allocate superblock, block group descriptor table, or 
beyond the end */
   assert_backtrace (result == 0 ||
diff --git a/ext2fs/ialloc.c b/ext2fs/ialloc.c
index b5e4b004d..d089a1d24 100644
--- a/ext2fs/ialloc.c
+++ b/ext2fs/ialloc.c
@@ -354,6 +354,7 @@ diskfs_alloc_node (struct node *dir, mode_t mode, struct 
node **node)
   diskfs_node_disknode (np)->info.i_next_alloc_goal = 0;
   diskfs_node_disknode (np)->info.i_prealloc_block = 0;
   diskfs_node_disknode (np)->info.i_prealloc_count = 0;
+  diskfs_node_disknode (np)->info.i_prealloc_size = 0;
   diskfs_node_disknode (np)->on_orphan_list = 0;
   diskfs_node_disknode (np)->orphan_prev = NULL;
   diskfs_node_disknode (np)->orphan_next = NULL;
diff --git a/ext2fs/inode.c b/ext2fs/inode.c
index 00a89f6c6..2f35ab3fd 100644
--- a/ext2fs/inode.c
+++ b/ext2fs/inode.c
@@ -103,6 +103,18 @@ diskfs_user_try_dropping_softrefs (struct node *np)
 void
 diskfs_lost_hardrefs (struct node *np)
 {
+  struct disknode *dn = diskfs_node_disknode (np);
+
+  /* The file is closed: give back the rest of its preallocation window,
+     so that it does not leave a reserved gap behind it while it stays in
+     the cache.  NP->lock is held, and diskfs_grow and diskfs_truncate
+     take alloc_lock under it too.  Most files closed have no window;
+     one made after this check is dropped with the node.  */
+  if (!dn->info.i_prealloc_count)
+    return;
+  pthread_rwlock_wrlock (&dn->alloc_lock);
+  ext2_discard_prealloc (np);
+  pthread_rwlock_unlock (&dn->alloc_lock);
 }
 
 /* A new hard reference to a node has been created; it's now OK to
@@ -275,6 +287,7 @@ diskfs_user_read_node (struct node *np, struct 
lookup_context *ctx)
   info->i_next_alloc_block = 0;
   info->i_next_alloc_goal = 0;
   info->i_prealloc_count = 0;
+  info->i_prealloc_size = 0;
 
   /* Set to a conservative value.  */
   dn->last_page_partially_writable = 0;
@@ -413,9 +426,6 @@ write_node (struct node *np)
 
   ext2_debug ("(%llu)", np->cache_id);
 
-  if (diskfs_node_disknode (np)->info.i_prealloc_count)
-    ext2_discard_prealloc (np);
-
   if (np->dn_stat_dirty)
     {
       struct ext2_inode_info *info = &diskfs_node_disknode (np)->info;
-- 
2.56.0


Reply via email to