From 30454a4ca132ea76851f799546f67f98b074f650 Mon Sep 17 00:00:00 2001 From: Rob Landley Date: Tue, 12 Jul 2016 14:51:51 -0500 Subject: [PATCH] Convert atolx() and friends to use long long internally. Update design.html to use this (and tail) as examples of simplicity of implementation winning and losing. --- lib/lib.c | 37 +++++++++++++++++++------------------ lib/lib.h | 8 ++++---- www/design.html | 23 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/lib/lib.c b/lib/lib.c index b742bd17..56656435 100644 --- a/lib/lib.c +++ b/lib/lib.c @@ -273,16 +273,16 @@ struct string_list *find_in_path(char *path, char *filename) return rlist; } -long estrtol(char *str, char **end, int base) +long long estrtol(char *str, char **end, int base) { errno = 0; - return strtol(str, end, base); + return strtoll(str, end, base); } -long xstrtol(char *str, char **end, int base) +long long xstrtol(char *str, char **end, int base) { - long l = estrtol(str, end, base); + long long l = estrtol(str, end, base); if (errno) perror_exit_raw(str); @@ -291,31 +291,32 @@ long xstrtol(char *str, char **end, int base) // atol() with the kilo/mega/giga/tera/peta/exa extensions. // (zetta and yotta don't fit in 64 bits.) -long atolx(char *numstr) +long long atolx(char *numstr) { - char *c, *suffixes="cbkmgtpe", *end; - long val; + char *c = numstr, *suffixes="cbkmgtpe", *end; + long long val; val = xstrtol(numstr, &c, 0); - if (*c) { - if (c != numstr && (end = strchr(suffixes, tolower(*c)))) { - int shift = end-suffixes-2; - if (shift >= 0) val *= 1024L<<(shift*10); - } else { - while (isspace(*c)) c++; - if (*c) error_exit("not integer: %s", numstr); + if (c != numstr && (end = strchr(suffixes, tolower(*c)))) { + int shift = end-suffixes-2; + + if (shift >= 0) { + if (toupper(*++c)=='d') do val *= 1000; while (shift--); + else val *= 1024LL<<(shift*10); } } + while (isspace(*c)) c++; + if (c==numstr || *c) error_exit("not integer: %s", numstr); return val; } -long atolx_range(char *numstr, long low, long high) +long long atolx_range(char *numstr, long long low, long long high) { - long val = atolx(numstr); + long long val = atolx(numstr); - if (val < low) error_exit("%ld < %ld", val, low); - if (val > high) error_exit("%ld > %ld", val, high); + if (val < low) error_exit("%lld < %lld", val, low); + if (val > high) error_exit("%lld > %lld", val, high); return val; } diff --git a/lib/lib.h b/lib/lib.h index 739548b8..f97940a8 100644 --- a/lib/lib.h +++ b/lib/lib.h @@ -184,10 +184,10 @@ int64_t peek_be(void *ptr, unsigned size); int64_t peek(void *ptr, unsigned size); void poke(void *ptr, uint64_t val, int size); struct string_list *find_in_path(char *path, char *filename); -long estrtol(char *str, char **end, int base); -long xstrtol(char *str, char **end, int base); -long atolx(char *c); -long atolx_range(char *numstr, long low, long high); +long long estrtol(char *str, char **end, int base); +long long xstrtol(char *str, char **end, int base); +long long atolx(char *c); +long long atolx_range(char *numstr, long long low, long long high); int stridx(char *haystack, char needle); char *strlower(char *s); char *strafter(char *haystack, char *needle); diff --git a/www/design.html b/www/design.html index 050c953f..b358d440 100755 --- a/www/design.html +++ b/www/design.html @@ -216,6 +216,22 @@ usage), and avoiding unnecessary work makes code run faster. Smaller code also tends to run faster on modern hardware due to CPU cacheing: fitting your code into L1 cache is great, and staying in L2 cache is still pretty good.

+

But a simple implementation is not always the smallest or fastest, and +balancing simplicity vs the other goals can be difficult. For example, the +atolx_range() function in lib/lib.c uses the always 64 bit "long long" type, +which produces larger and slower code on 32 bit platforms and +often assigned into smaller interger types. Although libc has parallel +implementations for different data sizes (atoi, atol, atoll) we only +used the largest, which can cover all cases.

+ +

On the other hand, the "tail" command has two codepaths, one for seekable +files and one for nonseekable files. Although the nonseekable case can handle +all inputs (and is required when input comes from a pipe or similar, so cannot +be removed), reading through multiple gigabytes of data to reach the end of +seekable files was both a common case and hugely penalized by a nonseekable +approach (half-minute wait vs instant results). This is one example +where performance did outweigh simplicity of implementation.

+

Joel Spolsky argues against throwing code out and starting over, and he has good points: an existing debugged codebase contains a huge amount of baked @@ -243,6 +259,13 @@ the GNU tools were yet another rewrite intended for use in the stillborn were written in Plan 9, uclinux, klibc, sash, sbase, s6, and of course android toolbox...) but maybe toybox can do a better job. :)

+

As Antoine de St. Exupery (author of "The Little Prince" and an early +aircraft designer) said, "Perfection is achieved, not when there +is nothing left to add, but when there is nothing left to take away." +And Ken Thompson (creator of Unix) said "One of my most productive +days was throwing away 1000 lines of code." It's always possible to +come up with a better way to do it.

+

P.S. How could I resist linking to an article about why programmers should strive to be lazy and dumb?

-- 2.11.0