You can list a directory containing 8 million files But not with ls..
olark.com
olark.com
Doing this: #define BUF_SIZE 1024 * 1024 * 5
to define a numerical constant is a bit scary, since depending on how the symbol is used it can break due to dependencies. It's better to enclose the value in parenthesis. Personally I would probably write the right hand side as (5 << 20).
The first time the modification to skip entries with inode 0 is mentioned, the code is wrong:
I did this by adding if (dp->d_ino == 0) printf(...);
This should use !=, not == (it's correct the second time, but this adds confusion).
And I definitely prefer defined constants for two reasons. One, it's likely you'll have to declare multiple such buffers in different places. Two, if I want to tune the parameter, I'd rather do it at the top of a source file with other such defined constants than hunting for the declaration in the code. I do agree that sizeof() is preferable when it's an option.
To test this, he should try "ls | cat". On large directories that often runs many orders of magnitude faster than "ls". This is because, I believe, ls by default on most people's systems, want to display information such as file modes or type via coloring or otherwise decorating the file names, and getting that information requires looking at the inode for each file.
It's all those inode lookups that slow things down, as the inodes are likely to be scattered all over the place. When you do "ls | cat", ls noticed output is not a terminal, and so turns off the fancy stuff, and just lists the file names. The names can be determined entirely from the directory, and so performance is much better.
I was surprised that the 32K reads were taking so long. It's possible since it was on a virtualized disk ("in the cloud") that something else was slowing down disk IO (like Xen).
But I can assure you that a larger read buffer performed much better in this given scenario.
I'd welcome more tests though.
I'll try an ext3 file system just for giggles and post the results.
Edit:
Didn't have an ext3 file system with enough free inodes handy so I used an ext4 file system. It takes too long to create 10 million files so I cut the test off early. It took about 7 seconds to complete a "\ls -f | wc -l" with 6.2 million files in a single directory.
[just noticed it was 500M (oh wow), but same difference]
Running /bin/ls will bypass the alias.
Putting eight million files in one directory level aside, the whole basis for this event - using the filesystem as a storage layer for a k/v 'database' - is just twisted.
Happy not to be working with devs like this.
See this talk by Jonathan Blow, the designer and programmer behind Braid: http://news.ycombinator.com/item?id=2689999
Sure, you can find out a lot by doing things you really shouldn't do, like using the directory system as k/v store.
But in the end, you should still learn the lesson that it is really a bad idea.
And remember to put all your variable declarations at the top of each block, so the compiler can handle it all in a single pass :)
"Perhaps the buffer should be dynamically set based on the size of the directory entry file"
This would eliminate the readdir() bottleneck.
I was a bit thrown by this advice. Are there folks out there that are afraid to compile code and modify it?
Software is "packages" that you install with "synaptic".
Source code? Compiler? What's that?
Ubuntu accelerated the process. Despite being a long time Linux user and programmer, I'd rather know the machine is goin' to work when I haven't done anything to mangle the beast.
I've spent hundreds, if not thousands of hours in the past just getting networking drivers to function. The brave new world of Linux is a good thing, the Interp-Only volken serve only to bolster the ecosystem, not harm it.
Nothing against Ubuntu (RedHat, SuSE, Debian, Arch, et. al.), but source compilation is something they all have been letting their users avoid for a long time. The target audience is different.
If your server had a network connection problem and you needed to open up a port, we're already talking about network ports, not software, so you would try to diagnose your network.
If you asked me how you could get the old game `rogue` on your system, I would tell you to go install the freebsd-games port, which has nothing to do with networking, and so it clearly means that you need to look in your ports tree.
Some developers (myself included) may have a general preference for sticking with the "official" packages to avoid extra work when bringing up a new machine or migrating to a new distro version.
find . -maxdepth 1 -mindepth 1
Those arguments will remove the need for find to stat each directory entry. Regardless, this is a nice walk through of low level details often overlooked. open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 5
fcntl(5, F_GETFD) = 0x1 (flags FD_CLOEXEC)
fchdir(5) = 0
getdents(5, /* 2 entries */, 32768) = 48
getdents(5, /* 0 entries */, 32768) = 0
close(5) = 0
It's only reading 32k at a time, but the author had 500MB of data to be fetched.Many of the standard-tools that most people would intuitively expect to be rather optimized (find, rsync, gzip) are embarrassingly inefficient under the hood and turn belly up when confronted with data of any significant size.
That probably stems from the fact that most of the development on these tools took place during a time when 1GB harddrives were "huge" and SMP was "high end".
Maybe because listing 8M files is not a common use case, and there just isn't the motivation to update otherwise perfectly working code. It's not an itchy problem.
Certainly find . would have been faster without calling stat().
Does os.listdir() stat?
Often, when ls is being slow, you can speed things up drastically by disabling the things that require a stat (colors, -F, etc.) that are often added by distro's shell files (invoking ls with a full path is an easy way to disable shell aliases.) Also, sorting is on by default, which slows things for obvious reasons.
When all you need to do is kill the stat overhead for small dirs on slow file systems, "echo *" is beautifully simple.
> invoking ls with a full path is an easy way to
> disable shell aliases
Or doing this: > 'ls'\ls -f
env ls
Notes:* IIRC 'env' is a built-in on csh/tcsh though, and doesn't behave like I would expect it to. You may want to read that manpage in that case.
* This is how the following works:
#!/usr/bin/env pythonThe native aio interface has advantages over the posix wrapper too, and some of the other interfaces. On the other hand the futex interface is not nice to use, as it requires architecture specific assembly which seems undocumented...
Also to get the NTFS streams (metadata).
http://msdn.microsoft.com/en-us/library/aa364226(v=vs.85).as...
Just in case you're curious I left it run and it took about 25 hours to get all of the directory entries. One with about 3 million files took 12 hours, so i'm not sure how long it would have taken if you'd have let ls run on its own accord.
Only 200k files afterwards though. :D
I think i'll poke around with this after I get a coffee, I remember stracing the interpreter and getting annoyed at its 32k or bust behavior. But since I didn't have a time limit I didn't much care about runtime. Thanks for the write up!
Perhaps
ls -f1 # (one) disables sorting and just prints filenames
would be fast enough?Here's an example. The .git/objects/ directory can grow to have up to 256 sub-directories. If an object hashes to 0a1b2c3d then it gets written to objects/0a/b2c3d. Lookups are still fast and navigating can be done without resorting to writing an 'ls' replacement.
http://blogs.perl.org/users/randal_l_schwartz/2011/03/perl-t...
The shell cannot handle long argument lists, so this will fail rather quickly.
http://webcache.googleusercontent.com/search?q=cache:KBsyzf3...
The premise of the article is a bad precedent for stable environments: let's bend the OS so that it plays nicely with what's clearly misuse and misunderstanding of filesystems.
The only way eight million files should ever end up in a single directory level is by accident, and that's not the case in the scenario outlined here.