lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Peter Odding wrote:
Rici Lake wrote:
 > You might want to reconsider that. It is certainly a clean interface,
 > but it might not be particularly useful in common cases. Off the top of
 > my head, I'd say the common cases are: checking one or two permissions,
 > and reporting all the permissions in some string representation.
 > Now, consider these possibilities:
 >
 > local stat = apr.stat(filename)
 > if stat.perms.world.readable then ... end
 > -- or
 > if stat.worldperms:match"r" then ... end
 >
 > Just a thought.

My current interface is apr.stat(...).permissions.user.readable, like your first example. Which means my 1000/4000 reasoning was wrong; it's actually 5000 tables: 1000 result tables, 1000 (container) permission tables and then 1000 each for user/group/world. Are you suggesting the latter of your examples is better (sorry, it's late :P)?

I don't know. I don't think it's bad, and it's certainly less creation of tables. (But see below.) My only point was that there was an alternative, which works reasonably well with Lua.

The part I didn't write was turning both of those representations into something printable, which I think would be a lot simpler with the string representation. Deeply nested tables are often awkward.


 > I don't like functions which sometimes return tables and sometimes not;
 > that's error-prone and confusing to the user.

This seems to be relatively common in Lua. Specifically in the reference implementation, for example file:read and os.date. But you mention tables specifically. Is this because of indexing errors when a scalar is returned instead of a table, without the user expecting so?


Yes, precisely. It's like CGI interfaces which return scalars when there is only one instance of a key and arrays when there are multiple instances. That means you *always* need to check the type of the value, unless you're being sloppy in which case you're going to get slammed at some point. So it would actually have been better to have always returned a table, despite the apparent "inefficiency" of creating the table.

Admittedly, creating (and then gc'ing) tables is not free, but neither are bugs or incessant calls to type(). Obsessing over the cost of creating tables often leads to annoying interfaces and more complicated calling sequences.

The point of using a language like Lua, in my opinion, is: ease of use, avoidance of bugs, and rapidity of development. Fast execution is way down the list (my list, anyway). So a good interface, in my view, should promote those virtues, and only then try to do so as efficiently as possible.

Mike's suggestion is not bad, but it will prove to be awkward when you want to save the entire stat structure, in which case a table will prove to have been better.

Now let's put this into perspective.

Here's a quick test. In the first line, I call stat 100,000 times on each of two files. That takes 4.5 seconds. In the second test, I create and discard five tables 200,000 times -- roughly the nested format you suggest, although I didn't add all the fields. That takes 1.1 seconds. In the third test, I create and discard two tables 200,000 times, using something closer to the less nested format I suggested. That takes 0.5 seconds.

Now, the test is biased: I'm statting the same files repeatedly, so presumably the inodes are cached. In a real world example, the stat test would probably take longer. Even so, it shows a couple of things:

1) 1000 stats will take a few milliseconds, regardless
2) The cost of building five useless tables is at most 25% of the cost of the call to stat, so reducing it to zero tables will save at most 20%.

Test results:

rlake@freeb:~/src/stattest$ time ./stat 100000 *
stat: mtime 2007-03-14 02:33:22
stat.c: mtime 2007-03-14 02:33:21

real    0m4.483s
user    0m0.086s
sys     0m4.384s

rlake@freeb:~/src/stattest$ time lua -e \
  'for i = 1, 200000 do \
     local t = {mtime=7, \
       perms = {user = {read = true, write = true, exec = true}, \
                group = {read = true}, world = {read = true}}} \
   end'

real    0m1.153s
user    0m1.134s
sys     0m0.018s

rlake@freeb:~/src/stattest$ time lua -e \
  'for i = 1, 200000 do \
     local t = {mtime=7, \
       perms = {user = "rwx", group = "r", world = "r"}} \
   end'

real    0m0.503s
user    0m0.491s
sys     0m0.010s


Here's the stat.c benchmark:

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

int main (int argc, char **argv) {
  struct stat sb[1];
  int n = atoi(argv[1]);
  int i;
  while (n--) {
    for (i = 2; i < argc; i++) {
      int rc = stat(argv[i], sb);
      if (n == 0) {
        if (rc != 0)
          printf("%s: error %s\n", argv[i], strerror(errno));
        else {
          char tmbuf[100];
          struct tm mtime[1];
          strftime(tmbuf, sizeof tmbuf - 1, "%F %T",
                   gmtime_r(&sb->st_mtime, mtime));
          printf("%s: mtime %s\n", argv[i], tmbuf);
        }
      }
    }
  }
  return 0;
}