1 <!--#include file="header.html" -->
3 <b><h2>Design goals</h2></b>
5 <p>Toybox should be simple, small, fast, and full featured. Often, these
6 things need to be balanced off against each other. In general, keeping the
7 code simple the most important (and hardest) goal, and small is slightly more
8 important than fast. Features are the reason we write code in the first
9 place but this has all been implemented before so if we can't do a better
10 job why bother? It should be possible to get 80% of the way to each goal
11 before they really start to fight.</p>
13 <p>Here they are in reverse order of importance:</p>
15 <b><h3>Features</h3></b>
17 <p>The <a href=roadmap.html>roadmap</a> has the list of features we're
18 trying to implement, and the reasons for them. After the 1.0 release
19 some of that material may get moved here.</p>
21 <p>Some things are simply outside the scope of the project: even though
22 posix defines commands for compiling and linking, we're not going to include
23 a compiler or linker (and support for a potentially infinite number of hardware
24 targets). And until somebody comes up with a ~30k ssh implementation, we're
25 going to point you at dropbear or polarssl.</p>
27 <p>Environmental dependencies are a type of complexity, so needing other
28 packages to build or run is a big downside. For example, we don't use curses
29 when we can simply output ansi escape sequences and trust all terminal
30 programs written in the past 30 years to be able to support them. (A common
31 use case is to download a statically linked toybox binary to an arbitrary
32 Linux system, and use it in an otherwise unknown environment; being
33 self-contained helps support this.)</p>
37 <p>It's easy to say lots about optimizing for speed (which is why this section
38 is so long), but at the same time it's the optimization we care the least about.
39 The essence of speed is being as efficient as possible, which means doing as
40 little work as possible. A design that's small and simple gets you 90% of the
41 way there, and most of the rest is either fine-tuning or more trouble than
42 it's worth (and often actually counterproductive). Still, here's some
45 <p>First, understand the darn problem you're trying to solve. You'd think
46 I wouldn't have to say this, but I do. Trying to find a faster sorting
47 algorithm is no substitute for figuring out a way to skip the sorting step
48 entirely. The fastest way to do anything is not to have to do it at all,
49 and _all_ optimization boils down to avoiding unnecessary work.</p>
51 <p>Speed is easy to measure; there are dozens of profiling tools for Linux
52 (although personally I find the "time" command a good starting place).
53 Don't waste too much time trying to optimize something you can't measure,
54 and there's no much point speeding up things you don't spend much time doing
57 <p>Understand the difference between throughput and latency. Faster
58 processors improve throughput, but don't always do much for latency.
59 After 30 years of Moore's Law, most of the remaining problems are latency,
60 not throughput. (There are of course a few exceptions, like data compression
61 code, encryption, rsync...) Worry about throughput inside long-running
62 loops, and worry about latency everywhere else. (And don't worry too much
63 about avoiding system calls or function calls or anything else in the name
64 of speed unless you are in the middle of a tight loop that's you've already
65 proven isn't running fast enough.)</p>
67 <p>"Locality of reference" is generally nice, in all sorts of contexts.
68 It's obvious that waiting for disk access is 1000x slower than doing stuff in
69 RAM (and making the disk seek is 10x slower than sequential reads/writes),
70 but it's just as true that a loop which stays in L1 cache is many times faster
71 than a loop that has to wait for a DRAM fetch on each iteration. Don't worry
72 about whether "&" is faster than "%" until your executable loop stays in L1
73 cache and the data access is fetching cache lines intelligently. (To
74 understand DRAM, L1, and L2 cache, read Hannibal's marvelous ram guide at Ars
76 <a href=http://arstechnica.com/paedia/r/ram_guide/ram_guide.part1-2.html>part one</a>,
77 <a href=http://arstechnica.com/paedia/r/ram_guide/ram_guide.part2-1.html>part two</a>,
78 <a href=http://arstechnica.com/paedia/r/ram_guide/ram_guide.part3-1.html>part three</a>,
80 <a href=http://arstechnica.com/articles/paedia/cpu/caching.ars/1>article on
81 cacheing</a>, and this one on
82 <a href=http://arstechnica.com/articles/paedia/cpu/bandwidth-latency.ars>bandwidth
84 And there's <a href=http://arstechnica.com/paedia/index.html>more where that came from</a>.)
85 Running out of L1 cache can execute one instruction per clock cycle, going
86 to L2 cache costs a dozen or so clock cycles, and waiting for a worst case dram
87 fetch (round trip latency with a bank switch) can cost thousands of
88 clock cycles. (Historically, this disparity has gotten worse with time,
89 just like the speed hit for swapping to disk. These days, a _big_ L1 cache
90 is 128k and a big L2 cache is a couple of megabytes. A cheap low-power
91 embedded processor may have 8k of L1 cache and no L2.)</p>
93 <p>Learn how virtual memory and memory managment units work. Don't touch
94 memory you don't have to. Even just reading memory evicts stuff from L1 and L2
95 cache, which may have to be read back in later. Writing memory can force the
96 operating system to break copy-on-write, which allocates more memory. (The
97 memory returned by malloc() is only a virtual allocation, filled with lots of
98 copy-on-write mappings of the zero page. Actual physical pages get allocated
99 when the copy-on-write gets broken by writing to the virtual page. This
100 is why checking the return value of malloc() isn't very useful anymore, it
101 only detects running out of virtual memory, not physical memory. Unless
102 you're using a NOMMU system, where all bets are off.)</p>
104 <p>Don't think that just because you don't have a swap file the system can't
105 start swap thrashing: any file backed page (ala mmap) can be evicted, and
106 there's a reason all running programs require an executable file (they're
107 mmaped, and can be flushed back to disk when memory is short). And long
108 before that, disk cache gets reclaimed and has to be read back in. When the
109 operating system really can't free up any more pages it triggers the out of
110 memory killer to free up pages by killing processes (the alternative is the
111 entire OS freezing solid). Modern operating systems seldom run out of
112 memory gracefully.</p>
114 <p>Also, it's better to be simple than clever. Many people think that mmap()
115 is faster than read() because it avoids a copy, but twiddling with the memory
116 management is itself slow, and can cause unnecessary CPU cache flushes. And
117 if a read faults in dozens of pages sequentially, but your mmap iterates
118 backwards through a file (causing lots of seeks, each of which your program
119 blocks waiting for), the read can be many times faster. On the other hand, the
120 mmap can sometimes use less memory, since the memory provided by mmap
121 comes from the page cache (allocated anyway), and it can be faster if you're
122 doing a lot of different updates to the same area. The moral? Measure, then
123 try to speed things up, and measure again to confirm it actually _did_ speed
124 things up rather than made them worse. (And understanding what's really going
125 on underneath is a big help to making it happen faster.)</p>
127 <p>In general, being simple is better than being clever. Optimization
128 strategies change with time. For example, decades ago precalculating a table
129 of results (for things like isdigit() or cosine(int degrees)) was clearly
130 faster because processors were so slow. Then processors got faster and grew
131 math coprocessors, and calculating the value each time became faster than
132 the table lookup (because the calculation fit in L1 cache but the lookup
133 had to go out to DRAM). Then cache sizes got bigger (the Pentium M has
134 2 megabytes of L2 cache) and the table fit in cache, so the table became
135 fast again... Predicting how changes in hardware will affect your algorithm
136 is difficult, and using ten year old optimization advice and produce
137 laughably bad results. But being simple and efficient is always going to
138 give at least a reasonable result.</p>
140 <p>The famous quote from Ken Thompson, "When in doubt, use brute force",
141 applies to toybox. Do the simple thing first, do as little of it as possible,
142 and make sure it's right. You can always speed it up later.</p>
145 <p>Again, simple gives you most of this. An algorithm that does less work
146 is generally smaller. Understand the problem, treat size as a cost, and
147 get a good bang for the byte.</p>
149 <p>Understand the difference between binary size, heap size, and stack size.
150 Your binary is the executable file on disk, your heap is where malloc() memory
151 lives, and your stack is where local variables (and function call return
152 addresses) live. Optimizing for binary size is generally good: executing
153 fewer instructions makes your program run faster (and fits more of it in
154 cache). On embedded systems, binary size is especially precious because
155 flash is expensive (and its successor, MRAM, even more so). Small stack size
156 is important for nommu systems because they have to preallocate their stack
157 and can't make it bigger via page fault. And everybody likes a small heap.</p>
159 <p>Measure the right things. Especially with modern optimizers, expecting
160 something to be smaller is no guarantee it will be after the compiler's done
161 with it. Binary size isn't the most accurate indicator of the impact of a
162 given change, because lots of things get combined and rounded during
163 compilation and linking. Matt Mackall's bloat-o-meter is a python script
164 which compares two versions of a program, and shows size changes in each
165 symbol (using the "nm" command behind the scenes). To use this, run
166 "make baseline" to build a baseline version to compare against, and
167 then "make bloatometer" to compare that baseline version against the current
170 <p>Avoid special cases. Whenever you see similar chunks of code in more than
171 one place, it might be possible to combine them and have the users call shared
172 code. (This is the most commonly cited trick, which doesn't make it easy. If
173 seeing two lines of code do the same thing makes you slightly uncomfortable,
174 you've got the right mindset.)</p>
176 <p>Some specific advice: Using a char in place of an int when doing math
177 produces significantly larger code on some platforms (notably arm),
178 because each time the compiler has to emit code to convert it to int, do the
179 math, and convert it back. Bitfields have this problem on most platforms.
180 Because of this, using char to index a for() loop is probably not a net win,
181 although using char (or a bitfield) to store a value in a structure that's
182 repeated hundreds of times can be a good tradeoff of binary size for heap
185 <b><h3>Simple</h3></b>
187 <p>Complexity is a cost, just like code size or runtime speed. Treat it as
188 a cost, and spend your complexity budget wisely. (Sometimes this means you
189 can't afford a feature because it complicates the code too much to be
192 <p>Simplicity has lots of benefits. Simple code is easy to maintain, easy to
193 port to new processors, easy to audit for security holes, and easy to
196 <p>Simplicity itself can have subtle non-obvious aspects requiring a tradeoff
197 between one kind of simplicity and another: simple for the computer to
198 execute and simple for a human reader to understand aren't always the
199 same thing. A compact and clever algorithm that does very little work may
200 not be as easy to explain or understand as a larger more explicit version
201 requiring more code, memory, and CPU time. When balancing these, err on the
202 side of doing less work, but add comments describing how you
203 could be more explicit.</p>
205 <p>In general, comments are not a substitute for good code (or well chosen
206 variable or function names). Commenting "x += y;" with "/* add y to x */"
207 can actually detract from the program's readability. If you need to describe
208 what the code is doing (rather than _why_ it's doing it), that means the
209 code itself isn't very clear.</p>
211 <p>Prioritizing simplicity tends to serve our other goals: simplifying code
212 generally reduces its size (both in terms of binary size and runtime memory
213 usage), and avoiding unnecessary work makes code run faster. Smaller code
214 also tends to run faster on modern hardware due to CPU cacheing: fitting your
215 code into L1 cache is great, and staying in L2 cache is still pretty good.</p>
217 <p><a href=http://www.joelonsoftware.com/articles/fog0000000069.html>Joel
218 Spolsky argues against throwing code out and starting over</a>, and he has
219 good points: an existing debugged codebase contains a huge amount of baked
220 in knowledge about strange real-world use cases that the designers didn't
221 know about until users hit the bugs, and most of this knowledge is never
222 explicitly stated anywhere except in the source code.</p>
224 <p>That said, the Mythical Man-Month's "build one to throw away" advice points
225 out that until you've solved the problem you don't properly understand it, and
226 about the time you finish your first version is when you've finally figured
227 out what you _should_ have done. (The corrolary is that if you build one
228 expecting to throw it away, you'll actually wind up throwing away two. You
229 don't understand the problem until you _have_ solved it.)</p>
231 <p>Joel is talking about what closed source software can afford to do: Code
232 that works and has been paid for is a corporate asset not lightly abandoned.
233 Open source software can afford to re-implement code that works, over and
234 over from scratch, for incremental gains. Before toybox, the unix command line
235 has already been reimplemented from scratch several times in a row (the
236 original AT&T Unix command line in assembly and then in C, the BSD
237 versions, the GNU tools, BusyBox...) but maybe toybox can do a better job. :)</p>
239 <p>P.S. How could I resist linking to an article about
240 <a href=http://blog.outer-court.com/archive/2005-08-24-n14.html>why
241 programmers should strive to be lazy and dumb</a>?</p>
243 <b><h2>Portability issues</h2></b>
245 <b><h3>Platforms</h3></b>
246 <p>Toybox should run on Android (all commands with musl-libc, as large a subset
247 as practical with bionic), and every other hardware platform Linux runs on.
248 Other posix/susv4 environments (perhaps MacOS X or newlib+libgloss) are vaguely
249 interesting but only if they're easy to support; I'm not going to spend much
252 <p>I don't do windows.</p>
254 <b><h3>32/64 bit</h3></b>
255 <p>Toybox should work on both 32 bit and 64 bit systems. By the end of 2008
256 64 bit hardware will be the new desktop standard, but 32 bit hardware will
257 continue to be important in embedded devices for years to come.</p>
259 <p>Toybox relies on the fact that on any Unix-like platform, pointer and long
260 are always the same size (on both 32 and 64 bit). Pointer and int are _not_
261 the same size on 64 bit systems, but pointer and long are.</p>
263 <p>This is guaranteed by the LP64 memory model, a Unix standard (which Linux
264 and MacOS X both implement). See
265 <a href=http://www.unix.org/whitepapers/64bit.html>the LP64 standard</a> and
266 <a href=http://www.unix.org/version2/whatsnew/lp64_wp.html>the LP64
267 rationale</a> for details.</p>
269 <p>Note that Windows doesn't work like this, and I don't care.
270 <a href=http://blogs.msdn.com/oldnewthing/archive/2005/01/31/363790.aspx>The
271 insane legacy reasons why this is broken on Windows are explained here.</a></p>
273 <b><h3>Signedness of char</h3></b>
274 <p>On platforms like x86, variables of type char default to unsigned. On
275 platforms like arm, char defaults to signed. This difference can lead to
276 subtle portability bugs, and to avoid them we specify which one we want by
277 feeding the compiler -funsigned-char.</p>
279 <p>The reason to pick "unsigned" is that way we're 8-bit clean by default.</p>
281 <p><h3>Error messages and internationalization:</h3></p>
282 <p>Error messages are extremely terse not just to save bytes, but because we
283 don't use any sort of _("string") translation infrastructure.</p>
285 <p>Thus "bad -A '%c'" is
286 preferable to "Unrecognized address base '%c'", because a non-english speaker
287 can see that -A was the problem, and with a ~20 word english vocabulary is
288 more likely to know (or guess) "bad" than the longer message.</p>
290 <p>The help text might someday have translated versions, and strerror()
291 messages produced by perror_exit() and friends can be expected to be
292 localized by libc. Our error functions also prepend the command name,
293 which non-english speakers can presumably recognize already.</p>
295 <p>An enventual goal is <a href=http://yarchive.net/comp/linux/utf8.html>UTF-8</a> support, although it isn't a priority for the
296 first pass of each command. (All commands should at least be 8-bit clean.)</p>
298 <p>Locale support isn't currently a goal; that's a presentation layer issue,
299 X11 or Dalvik's problem.</p>
301 <!--#include file="footer.html" -->