A few weeks ago, I noticed that removing a directory with very many
entries made rm -rf use an inordinate amount of memory.  I've fixed the
underlying fts module to batch its readdir calls in the common case,
thus to imposing a reasonable ceiling on memory usage.  The ceiling of
~30MB is reached for any directory containing 100,000 or more entries.

The way this change is designed, it induces no semantic change unless
fts processes a directory with 100,000 entries or more, so normal
testing would be unlikely to get good coverage.  In testing, I lowered
the threshold of 100,000 first to 5, and then to 2.  In both cases,
coreutils tools (modified to use this test version of fts) passed
all of their regression tests.

I did the same with find (find.git v4.5.10-93-gd0c939a), but with
 a threshold of just 10.

I mem-profiled that, just for fun and got this:
(find /t/dir > /dev/null vs. 1M files in a tmpfs directory)

    KB
41.88^#               ::                        :
     |#:   :::   :: : : :::::::::::  ::  : ::  ::::::  : ::@:::    ::: :: :::@
     |# :::::::::: :::: : ::: :::::::::::::: ::::::: :@::: @::::::::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
     |# :: ::::: : :::: : ::: :::::: ::: ::: ::::::: :@::: @:::: :::::@::::::@
   0 +----------------------------------------------------------------------->Gi
     0                                                                   4.530

Note that the maximum memory usage was barely 40KB (note, that's K as in kilo)
and it ran for 4.5 Gi.  Note that that is using a ridiculously small test
threshold of just 10.

Compare with find-4.5.9, which consumed over 1GB and required a few
*more* instructions.

    GB
1.043^                  ##
     |                  # :
     |                 :# :::::
     |                ::# :::::::
     |               :::# ::::::::@
     |              ::::# ::::::::@::::
     |              ::::# ::::::::@::: :@
     |            @:::::# ::::::::@::: :@::@
     |           :@:::::# ::::::::@::: :@::@::
     |           :@:::::# ::::::::@::: :@::@:::::
     |          ::@:::::# ::::::::@::: :@::@:::::@::
     |        ::::@:::::# ::::::::@::: :@::@:::::@:::@
     |       :: ::@:::::# ::::::::@::: :@::@:::::@:::@:::
     |       :: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@
     |      ::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@::
     |     :::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@:::::
     |   :::::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@:::::@::
     |  :: :::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@:::::@::::
     |  :: :::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@:::::@:::::@:
     | ::: :::: ::@:::::# ::::::::@::: :@::@:::::@:::@:::::@:::::@:::::@:::
   0 +----------------------------------------------------------------------->Gi
     0                                                                   4.683

While it is tempting to use a much lower-than-100,000 threshold,
realize that doing so has a cost: a saved DIR* pointer (including a
file descriptor) for each level in a hierarchy that has that many entries.

With the default threshold of 100,000, the maximum is under 30MB
and slightly faster than the first run:

    MB
28.27^#
     |#                        ::  :        :            :    :     :
     |#            :  :        : :::      :::     :      :    :     :  :
     |#            :  :        : : :     :: : :   :      :    :     :  :
     |#            :  : ::     : : :     :: : :   :      :    :     :  :   : :
     |#            : :: :      : : :     :: : :   :      :    :     :  : : : :
     |#            : :: :      : : :     :: : :   :      :    :     : :: : : :
     |#        :   : :: :      : : :     :: : :   :      :   ::     : :: : : :
     |#    :   :   : :: :      : : :     :: : :   :      :   :: :   : :: : : :
     |# :  :   :   : :: :     :: : :     :: : : :::      :   :: :   : :: : : :
     |# :  :   :   : :: :     :: : :     :: : : : :      :   :: :   : :: : @ :
     |# :  :   :   : :: :     :: : :   :::: : : : :      :   :: :   @ :::: @ :
     |# ::::   :  :: :: :     :: : :  :: :: : : : :      :   :: :   @ :::: @ :
     |# :: :   :  :: :: :     :: : :  :: :: : : : :      :   :: :   @ :::: @ :
     |# :: :  ::  :: :: :  :: :: : :  :: :: : : : :      : ::::::  :@ :::: @ :
     |# :: :  ::::::::: : ::  :: : :  :: :: : ::: :::::::: : ::::  :@ :::::@ :
     |# :: :::::: ::::: : ::  :: : ::::: :: ::::: ::: : :: : ::::  :@::::::@ :
     |# :: :: ::: ::::::: ::  :: : :: :: :: ::::: ::: : :: : ::::  :@::::::@ :
     |#::: :: ::: ::::::: ::  :: : :: :: :: ::::: ::: : :: : ::::  :@::::::@ :
     |#::: :: ::: ::::::: ::  :: : :: :: :: ::::: ::: : :::: :::::::@::::::@ :
   0 +----------------------------------------------------------------------->Gi
     0                                                                   4.481


Down to business.  The first few patches clean up fts.c, making it
a little more compact and fixing some minor readability/maintainability
issues.

While the logs do contain ChangeLog entries,
there are none in the ChangeLog file.
I'll add those before pushing, which won't be for at
least a day or two.

[PATCH 1/8] maint: fts.c: remove #if-0'd FTS_WHITEOUT code
[PATCH 2/8] maint: fts.c: move __opendir2 #define "up" out of
[PATCH 3/8] maint: fts.c: correct off-by-one indentation
[PATCH 4/8] maint: fts.c (__opendir2): Remove unused parameter,
[PATCH 5/8] maint: fts: give __opendir2 a new parameter
[PATCH 6/8] fts: add/use new struct member, fts_dirp
[PATCH 7/8] fts: move decl of "dp" into while loop; split long line
[PATCH 8/8] fts: do not exhaust memory when processing million-entry

Reply via email to