[comp.unix.questions] Computational complexity of rm & ls

wsmith@m.cs.uiuc.edu (03/09/89)

I did a test of the computational complexity of the ls and rm programs.

I created directories with a large number of files in them (large = 1000-2000).

Then, I timed ls commands in these directories.   It appeared that the
program was quadratic in the amount of system time that it took and n*log n
in user time.  (This is on a encore 4.2 berkeley Unix.)  When I tried it
under 4.3 Berkeley Unix on a VAX, it appeared to be order n in system time
and n log n user time.   The 4.3 version is obviously better.
How do other versions of unix perform on large directories?

To clean up my mess, I removed the files in two steps.  First deleting
half with `rm *a` rmand then deleting the rest with `rm *` The 4.3 Unix 
appeared to be quadratic in system time for this operation, while the 4.2 Unix
was cubic or worse in the amount of system time required!  
(I'm only measuring the curve at 2 points, so it is hard to get a good 
estimate of the complexity.)

Surely rm can do better than cubic, can't it?   "rm *" seems to be a common
enough idiom that rm could be optimized to handle that case better.   I would
like rm to be linear in system time.  Is that possible?

Bill Smith
wsmith@cs.uiuc.edu
uiucdcs!wsmith

arnold@mathcs.emory.edu (Arnold D. Robbins {EUCC}) (03/11/89)

In article <9000012@m.cs.uiuc.edu> wsmith@m.cs.uiuc.edu writes:
>The 4.3 Unix 
>appeared to be quadratic in system time for this operation, while the 4.2 Unix
>was cubic or worse in the amount of system time required!  
>
>Surely rm can do better than cubic, can't it?   "rm *" seems to be a common
>enough idiom that rm could be optimized to handle that case better.   I would
>like rm to be linear in system time.  Is that possible?

This is not really an 'rm' issue, it's a file system issue. The shell (pick
a shell, any shell, sh, csh, ksh) sorts the expansion of *; the order the
filenames are in the directory is irrelevant to rm. Rm then proceeds to
unlink each file in turn, probably randomly deleting entries from the
middle of the directory.  4.3 made a number of changes to directory
handling over 4.2, one of which was to perform compaction of the blocks
in a directory when possible, as entries are deleted.

I'll bet that if you were to 

	rm `ls -f`		and
	rm `ls -f | tac`	

you'd see very interesting behavior (i.e. remove files starting with
the first and last files in the directory, respectively), since each
unlink has to search the directory from scratch to find the entry.
(Actually, on 4.3 the last one would be pathologically bad, while the
first would be really quick, due to the directory caching in namei.)

I guess the point here is to know where to point the finger; since it
was all system time, it's not something that can really be fixed in rm.
Your 4.2 vendor should upgrade their filesystem code to 4.3.
-- 
Unix is a Registered   | Arnold Robbins -- Emory University Computing Center
Bell of AT&T Trademark | DOMAIN: arnold@unix.cc.emory.edu		
Laboratories.          | UUCP: gatech!emory!arnold	PHONE:	+1 404 727-7636
        -- Donn Seeley | BITNET: arnold@emoryu1		FAX:	+1 404 727-2599

marcoz@MARCOZ.BOLTZ.CS.CMU.EDU (Marco Zagha) (03/11/89)

In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
> [...]   "rm *" seems to be a common
> enough idiom that rm could be optimized to handle that case better.

Don't forget that the shell expands the "*", not rm.  All rm sees
is a huge argv with one name for every file (except .* files).

== Marco (marcoz@cs.cmu.edu)


-- 

chris@mimsy.UUCP (Chris Torek) (03/11/89)

In article <3804@emory.mathcs.emory.edu> arnold@mathcs.emory.edu
(Arnold D. Robbins {EUCC}) writes:
>I'll bet that if you were to 
>
>	rm `ls -f`		and
>	rm `ls -f | tac`	
>
>you'd see very interesting behavior (i.e. remove files starting with
>the first and last files in the directory, respectively), since each
>unlink has to search the directory from scratch to find the entry.
>(Actually, on 4.3 the last one would be pathologically bad, while the
>first would be really quick, due to the directory caching in namei.)

This did not match what I remembered, so I just checked.  You cannot
cache the result of a delete operation (it is senseless), so the only
thing that would affect the time is the offset cache.  Around line 370
of ufs_namei.c, one finds the following comment:

	/*
	 * If this is the same directory that this process
	 * previously searched, pick up where we last left off.
	 * We cache only lookups as these are the most common
	 * and have the greatest payoff. Caching CREATE has little
	 * benefit as it usually must search the entire directory
	 * to determine that the entry does not exist. Caching the
	 * location of the last DELETE has not reduced profiling time
	 * and hence has been removed in the interest of simplicity.
	 */

The effect unlink order would have, then, is that a forward remove
would aid each lookup by reducing the number of entries scanned.  The
effect should certainly be measurable, but probably not extreme.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

wsmith@m.cs.uiuc.edu (03/12/89)

>To clean up my mess, I removed the files in two steps.  First deleting
>half with `rm *a` rmand then deleting the rest with `rm *` The 4.3 Unix 
>appeared to be quadratic in system time for this operation, while the 4.2 Unix
>was cubic or worse in the amount of system time required!  
>(I'm only measuring the curve at 2 points, so it is hard to get a good 
>estimate of the complexity.)
>
>Surely rm can do better than cubic, can't it?   "rm *" seems to be a common
>enough idiom that rm could be optimized to handle that case better.   I would
>like rm to be linear in system time.  Is that possible?

Please ignore these two paragraphs.   My method of testing rm was invalid.

The reason is that on some systems, the way directories work causes
the amount of time to remove a several files to depend on the order that
they were created in the directory.   It is more work to delete the
most recently created file than it is to delete the first file that was
created.   (There is probably some imprecision in the previous
sentence because of the possibility of alternating the creation and deletion
of files.  I meant only to cover the case of a single file creation phase
followed by a single file deletion phase.)

(It has also been pointed out that I am not really measuring the complexity 
of rm or ls at all, but am instead measuring the kernel name-to-inode 
translation behavior.)  

What brought this up in the first place was an attempt to decide whether 
it was better to have 10000 or more files in one directory, to have a 
fraction of the files in several directories, or at the extreme case, 
to have 100 of the files in each of 100 directories if I wanted to be 
able open() individual files quickly.

Bill Smith
wsmith@cs.uiuc.edu
uiucdcs!wsmith

frank@zen.co.uk (Frank Wales) (03/13/89)

In article <4461@pt.cs.cmu.edu> marcoz@MARCOZ.BOLTZ.CS.CMU.EDU (Marco Zagha) writes:
>In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
>> [...]   "rm *" seems to be a common
>> enough idiom that rm could be optimized to handle that case better.
>
>Don't forget that the shell expands the "*", not rm.  All rm sees
>is a huge argv with one name for every file (except .* files).

This was my first thought upon reading what Bill posted, and then I thought:
"maybe he means the act of deleting all the files in a directory is a
common one, and should be handled specially."  Certainly, rm never sees
the '*', only the results of its (sorted, hidden-file-less) expansion.

Maybe adding a '-A' (for All, harder to type than '-a') option to rm would
be justified.  No doubt doing so would break jillions of scripts.  :-)
--
Frank Wales, Systems Manager,        [frank@zen.co.uk<->mcvax!zen.co.uk!frank]
Zengrange Ltd., Greenfield Rd., Leeds, ENGLAND, LS9 8DB. (+44) 532 489048 x217 

les@chinet.chi.il.us (Leslie Mikesell) (03/13/89)

In article <9000014@m.cs.uiuc.edu> wsmith@m.cs.uiuc.edu writes:

>What brought this up in the first place was an attempt to decide whether 
>it was better to have 10000 or more files in one directory, to have a 
>fraction of the files in several directories, or at the extreme case, 
>to have 100 of the files in each of 100 directories if I wanted to be 
>able open() individual files quickly.

The maximum optimal size probably varies with the OS version.  I've
been told that directory access becomes much less efficient when
the directory inode goes to triple indirect blocks (300 files?).
Under SysV directories never shrink by themselves, so access efficiency
depends on how many files have ever been in that directory at once
instead of how many are currently there.  If you have a scheme to
determine which of 100 directories a particular file will be stored
under, that calculation is almost certain to be faster than a search
of many hundreds of extra files in the same directory.

Les Mikesell

chris@mimsy.UUCP (Chris Torek) (03/13/89)

In article <7919@chinet.chi.il.us> les@chinet.chi.il.us (Leslie Mikesell)
writes:
>The maximum optimal size probably varies with the OS version.  I've
>been told that directory access becomes much less efficient when
>the directory inode goes to triple indirect blocks (300 files?).

300?!?  (Maybe 300! :-) [read `300 factorial'])

Assuming the SysV file system is unchanged from the V7 file system of
1976 or so (which, for the most part, it is), an inode remembers 13
blocks maximum.  Of these, the first 10 are direct, the 11th is single
indirect, the 12th is double indirect, and the 13th is triple indirect.
(All this from memory of 4.1BSD, and correspondingly suspect.)  If
the block size is 512 bytes (it is either 512 or 1024), and a directory
entry is 16 bytes (it is), the directory first switches to *single*
indirects at 5120 bytes, or 5120/16=320 entries.  Using 1 kbyte blocks
this doubles to 640.

A single indirect block points to up to 170 (I think---this is 512/3)
more blocks (512 bytes each again), so to reach double indirects you
need (10*512 + 170*512)/16 = 5760 entries.  By then you will have run
out of inodes due to the SysV inode eater bug :-) .  With 1024 byte
blocks a single indirect should handle 341 more blocks, for a total
of (10*1024 + 341*1024)/16 = 22464 entries.

4.[23]BSD uses a block size of 4096 or 8192 bytes, and has 12 direct
blocks, so while its directory entries can be larger (but probably
average about the same---an 8 character file name uses roundup(8,4) +
4 + 4 = 16 bytes; 9 to 12 character names use 20 bytes), it takes
either 12*4096=49152 bytes or 12*8192=98304 bytes, or more than 2450
20-byte entries (more than 4950 with 8 kbyte blocks) before it needs
to go to indirects.  (The same, of course, goes for plain files.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

tale@pawl.rpi.edu (David C Lawrence) (03/14/89)

In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) writes:
frank> Maybe adding a '-A' (for All, harder to type than '-a') option
frank> to rm would be justified.  No doubt doing so would break
frank> jillions of scripts.

I realize that there is a smiley there, but why should adding -A or -a
break any scripts at all?  Doesn't seem that anyone should have it as
a flag to rm in a script.

        
--
      tale@rpitsmts.bitnet, tale%mts@rpitsgw.rpi.edu, tale@pawl.rpi.edu

cmf@cisunx.UUCP (Carl M. Fongheiser) (03/14/89)

In article <7919@chinet.chi.il.us> les@chinet.chi.il.us (Leslie Mikesell) writes:
>The maximum optimal size probably varies with the OS version.  I've
>been told that directory access becomes much less efficient when
>the directory inode goes to triple indirect blocks (300 files?).

Yikes.  I'm sure you mean double indirect or even just indirect blocks.
A file with triple indirect blocks in it is unthinkably large; a directory
containing triple indirect blocks is even more unthinkable!

If you do manage a directory with triple indirect blocks, yes, your directory
access will be *very* slow.

>Under SysV directories never shrink by themselves, so access efficiency
>depends on how many files have ever been in that directory at once
>instead of how many are currently there.  If you have a scheme to
>determine which of 100 directories a particular file will be stored
>under, that calculation is almost certain to be faster than a search
>of many hundreds of extra files in the same directory.

Nonsense.  System V directories don't shrink, but they don't grow unless
they need to, either.  System V can and will fill in holes in directories.

				Carl Fongheiser
				University of Pittsburgh

karish@forel.stanford.edu (Chuck Karish) (03/14/89)

In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) wrote:
>In article <4461@pt.cs.cmu.edu> marcoz@MARCOZ.BOLTZ.CS.CMU.EDU (Marco Zagha) writes:
>>In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
>>> [...]   "rm *" seems to be a common
>>> enough idiom that rm could be optimized to handle that case better.

>Maybe adding a '-A' (for All, harder to type than '-a') option to rm would
>be justified.

Great, another feature!  How about using an alias instead:

	alias rmall 'ls -f | xargs rm -f'

This avoids the overhead of sorting the names in the directory.
It also suppresses those annoying queries from rm.

	Chuck Karish	hplabs!hpda!mindcrf!karish	(415) 493-7277
			karish@forel.stanford.edu

tale@pawl.rpi.edu (David C Lawrence) (03/14/89)

In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
wsmith> [...]   "rm *" seems to be a common
wsmith> enough idiom that rm could be optimized to handle that case better.

In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) wrote:
FW>Maybe adding a '-A' (for All, harder to type than '-a') option to rm would
FW>be justified.

In article <871@Portia.Stanford.EDU> karish@forel.stanford.edu
(Chuck Karish) writes:
CK> Great, another feature!  How about using an alias instead:
CK> 
CK> 	alias rmall 'ls -f | xargs rm -f'
CK> 
CK> This avoids the overhead of sorting the names in the directory.
CK> It also suppresses those annoying queries from rm.

By your own quoting, Chuck, the reason Frank suggested -A was for
optimization.  Invoking ls to pipe to xargs which calls rm is not the
optimum strategy.
--
      tale@rpitsmts.bitnet, tale%mts@rpitsgw.rpi.edu, tale@pawl.rpi.edu

gwyn@smoke.BRL.MIL (Doug Gwyn ) (03/14/89)

In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) writes:
>Maybe adding a '-A' (for All, harder to type than '-a') option to rm would
>be justified.  No doubt doing so would break jillions of scripts.  :-)

New options aren't likely to break existing scripts.
However, I don't understand the problem.  "rm -r ." seems sufficient.

terryl@tekcrl.LABS.TEK.COM (Terry Laskodi) (03/14/89)

In article <1541@zen.UUCP+ frank@zen.co.uk (Frank Wales) writes:
+In article <4461@pt.cs.cmu.edu> marcoz@MARCOZ.BOLTZ.CS.CMU.EDU (Marco Zagha) writes:
+>In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
+>> [...]   "rm *" seems to be a common
+>> enough idiom that rm could be optimized to handle that case better.
+>
+>Don't forget that the shell expands the "*", not rm.  All rm sees
+>is a huge argv with one name for every file (except .* files).
+
+This was my first thought upon reading what Bill posted, and then I thought:
+"maybe he means the act of deleting all the files in a directory is a
+common one, and should be handled specially."  Certainly, rm never sees
+the '*', only the results of its (sorted, hidden-file-less) expansion.
+
+Maybe adding a '-A' (for All, harder to type than '-a') option to rm would
+be justified.  No doubt doing so would break jillions of scripts.  :-)

     There already is an option like that; try "rm -r <directory-name>"
(and, yes I know "*" won't match . files....).

frank@zen.co.uk (Frank Wales) (03/14/89)

In article <TALE.89Mar13132118@imagine.pawl.rpi.edu> tale@pawl.rpi.edu writes:
>In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) writes:
>frank> Maybe adding a '-A' (for All, harder to type than '-a') option
>frank> to rm would be justified.  No doubt doing so would break
>frank> jillions of scripts.
>
>I realize that there is a smiley there, but why should adding -A or -a
>break any scripts at all?  Doesn't seem that anyone should have it as
>a flag to rm in a script.

No reason I can think of, but then I've seen scripts, programs and
entire systems break for the most ridiculous reasons imaginable that
nothing really surprises me any more.  You'd be amazed at the assumptions
people make when they write things.  Just being my usual cynical
self, that's all.  ;-)

--
Frank Wales, Systems Manager,        [frank@zen.co.uk<->mcvax!zen.co.uk!frank]
Zengrange Ltd., Greenfield Rd., Leeds, ENGLAND, LS9 8DB. (+44) 532 489048 x217 

karish@forel.stanford.edu (Chuck Karish) (03/14/89)

In article <TALE.89Mar13170500@imagine.pawl.rpi.edu> tale@pawl.rpi.edu wrote:
>In article <9000012@m.cs.uiuc.edu>, wsmith@m.cs.uiuc.edu writes:
>wsmith> [...]   "rm *" seems to be a common
>wsmith> enough idiom that rm could be optimized to handle that case better.

>In article <1541@zen.UUCP> frank@zen.co.uk (Frank Wales) wrote:
>FW>Maybe adding a '-A' ... option to rm would be justified.

>In article <871@Portia.Stanford.EDU> karish@forel.stanford.edu
>(Chuck Karish) writes:
>CK> Great, another feature!  How about using an alias instead:
>CK> 
>CK> 	alias rmall 'ls -f | xargs rm -f'

>By your own quoting, Chuck, the reason Frank suggested -A was for
>optimization.  Invoking ls to pipe to xargs which calls rm is not the
>optimum strategy.

If I understand the problem correctly, my pipeline takes an algorithm
that's exponential in time w.r.t. the number of directory entries, and
recasts it so it's linear.  Is this optimization, or not?

When I type 'rm *' the shell sorts the argument list before passing it
to rm, which does a pretty fair job of maximizing the amount of
directory scanning that rm has to do.  'ls -f' presents the arguments
to rm in directory order, so rm always seeks by exactly one directory
entry to get from one argument to the next.

If a hack is needed, it's to the shell, not to rm.  If it were possible
(is it already?) to do filename globbing without sorting, the 'ls -f'
would be unnecessary.  All accesses of large directories are expensive,
not just 'rm *'; adding a no-sort option to the shell would provide a
single feature that would help many utilities.

The '-A' option would eliminate the need for the xargs call (the
problem posed only arises in cases where the argument list is likely to
exceed ARG_MAX), but would do so only for the one special case.

	Chuck Karish	hplabs!hpda!mindcrf!karish	(415) 493-7277
			karish@forel.stanford.edu

danny@itm.UUCP (Danny) (03/14/89)

In article <TALE.89Mar13170500@imagine.pawl.rpi.edu> tale@pawl.rpi.edu writes:
~Invoking ls to pipe to xargs which calls rm is not the
~optimum strategy.

    The best "stragety" (thanks, Bugs) I've ever heard of is:
        use clri to zap the directory,
        and fsck to clean up the mess.
    
    The story began with a news-passing scheme which saved articles as
files in a directory to be passed to another machine.  When said machine
was down, the directory grew.  Gatech had literally thousands of files
to be sent, so, they either copied them to tape, or said, "Forget it!"
Then came the rm.  It ran overnight, and was still far from done.  One
of the gurus from MSDC (Hi Dan!) suggested the above idea, which took
about 10 minutes.  Ok, ok, you do have to unmount the partition, but,
even so, quadratic it ain't.

    WARNING!  Not for the faint-of-heart!  "Experience and informed
courage count for much." - from someplace in the Version 7 man pages,
dealing with addled file systems.

                                    Danny
-- 
				Daniel S. Cox
				(seismo!gatech!itm!danny)

dhesi@bsu-cs.UUCP (Rahul Dhesi) (03/15/89)

In article <1280@itm.UUCP> danny@itm.UUCP (Danny) writes
[about cleaning up runaway directories]:

>    The best "stragety" (thanks, Bugs) I've ever heard of is:
>        use clri to zap the directory,
>        and fsck to clean up the mess.

VAX/VMS has a consistent command structure and the switch /nodirectory
always, always means "not a directory".

Therein lies a tale.

There is a command "set file" that does many things to files, and it
accepts many options while doing those things.

The manual documented "set file/nodirectory" to mean "act on these
files only if they aren't directories", but the command interpreter
understood it to mean "make sure these aren't directories."  So if you
did

     $ set file/nodirectory *.*

all subdirectories matching *.* suddenly became regular files.  In
effect, we had a command that was (a) accessible to ordinary users and
(b) documented to be harmless but (c) did a near-clri on specified
directories.

Unfortunately, the same unprivileged users couldn't do the equivalent
of fsck (analyze/disk or something like that).

I think they fixed it.  I didn't try it again.
-- 
Rahul Dhesi         UUCP:  <backbones>!{iuvax,pur-ee}!bsu-cs!dhesi
                    ARPA:  dhesi@bsu-cs.bsu.edu

les@chinet.chi.il.us (Leslie Mikesell) (03/16/89)

In article <16364@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>In article <7919@chinet.chi.il.us> les@chinet.chi.il.us (Leslie Mikesell)
>writes:
>>The maximum optimal size probably varies with the OS version.  I've
>>been told that directory access becomes much less efficient when
>>the directory inode goes to triple indirect blocks (300 files?).
>
>300?!?  (Maybe 300! :-) [read `300 factorial'])


OK, OK.. indirect blocks, not triple indirect blocks.  Shows how much
you miss when all you have to go by is the man pages.  Back to the
subject though, a simple-minded test of catting 1000 tiny files
in one directory to /dev/null vs. 100 files in each of 10 directories
using xargs and explicit filenames so shell expansion would not be
a factor showed that the large directory took about twice the sys
time.  This confirmes my more casual observations that having large
directories on a machine hurts performance in general, at least under
SysV.  The worst case seems to be accessing large directories on a
remote machine using RFS.

Les Mikesell

guy@auspex.UUCP (Guy Harris) (03/21/89)

 >>Under SysV directories never shrink by themselves, so access efficiency
 >>depends on how many files have ever been in that directory at once
 >>instead of how many are currently there.  If you have a scheme to
 >>determine which of 100 directories a particular file will be stored
 >>under, that calculation is almost certain to be faster than a search
 >>of many hundreds of extra files in the same directory.
 >
 >Nonsense.  System V directories don't shrink, but they don't grow unless
 >they need to, either.  System V can and will fill in holes in directories.

System V will fill in holes, so will most other UNIX systems. 
Nevertheless, the claim that Les Mikesell makes is not "nonsense";
directory searches go all the way out to the end of the directory, and
if the directory once held 10,000 entries, then unless your system
shrinks directories, the search will have to scan through all 10,000 of
those entries, even if they're empty.  A directory name lookup cache
can alleviate this problem; I think the DNLC in S5R4 will be used both
with the V7 (System V) file system and the Berkeley (UFS) file system
(which, in S5R4, should shrink directories as per 4.3BSD).