jingfei195887 commented on issue #20414: URL: https://github.com/apache/nuttx/issues/20414#issuecomment-6100014365
## Code size analysis for stm32f103-minimum:nsh by modules Good suggestion @acassis . I'm looking into simplifying VFS. Before that, I think we'd better to sync up code size growth. so I broke down the problem from a module perspective based on the `stm32f103-minimum:nsh` growth from NuttX 7.19 (2016) to current master, down to the object level, with the historically quoted per-feature numbers reconciled against what the final linked image actually shows. Both sides built with the **same** arm-none-eabi-gcc 10.3.1: 7.19 = `02146758ef` + apps `5291d3e9bc`; current = `cc2edca964` + apps `1f8cac8b87`. ### Method Derived from the linker `.map`, not from summing `nm` symbols: attribute each placed section to its **object file** (not the section-name suffix, else `-ffunction-sections` `.text.<symbol>` fragments are dropped); count **only addr != 0** sections (skip `--gc-sections` discards that linger at addr=0, size>0); cross-check against `size` (text+data) as a correctness gate. Validation: current side placed-vs-ELF error **0.29%**, 7.19 side **0.76%** (fixed unattributed overhead - padding, vector table, `.ARM.exidx` - is a larger *fraction* of a 39 KB image). The 7.19 map placed total is **38,872 B, byte-for-byte the historical 2016 figure in this issue**. Per-module deltas also agree line-for-line with an independent `nm`-based pass. ### Totals | Metric | 7.19 | current | delta | |---|---:|---:|---:| | ELF Flash (text+data) | 39,170 | 52,427 | **+13,257** | | map placed total | 38,872 | 52,273 | **+13,401** | The per-library table below sums to the map-placed +13,401; the ~150 B gap to the ELF +13,257 is the unattributed overhead noted above. Per-module figures are map-placed, the headline Flash figure is ELF-measured. | Library (subsystem) | 7.19 | current | delta | |---|---:|---:|---:| | libsched.a (sched / sem / mqueue / wqueue / timers) | 9,422 | 13,736 | **+4,314** | | libfs.a (VFS / filesystem) | 3,094 | 6,611 | **+3,517** | | libapps.a (NSH + apps) | 10,429 | 12,974 | **+2,545** | | libc.a (C library) | 5,473 | 7,870 | **+2,397** | | libdrivers.a (drivers) | 2,727 | 4,118 | **+1,391** | | libmm.a (memory manager) | 1,451 | 2,107 | **+656** | | libarch.a (arch startup/exception) | 4,540 | 3,861 | **-679** | ### Three cross-cutting corrections (object-level de-duplication) Object diffs have the same "a new symbol is really a renamed/relocated/merged old one" trap as symbol sums: 1. **`assert.o` +690 in libsched is a relocation, not new code** - 7.19's assert was in `arch/.../up_assert.c` (libarch); it moved to `sched/misc/assert.c`. This almost exactly explains why libarch *shrank* 679. 2. **libapps per-object diffs are a naming artifact** - 7.19 `nsh_command.o` vs current `nsh_command.c.<path>_1.o`, zero shared keys. Matched by logical name, `nsh_command` grew only +127, not the +1782 a raw diff shows. 3. **The signal subsystem net is -517 (it shrank)** - the `nx*` refactor made signals *smaller*, directly contradicting the "signal atomic fast/slow ~2.8 KB growth" line in the original list. ### libsched.a +4,314 Largest mover - and not because of a few named features. Every object is partitioned into exactly one group below, so the column sums to the full +4,314. The headline is that the scheduler got broadly heavier: core/task/pthread/clock/group/env together add ~2.75 KB, more than all the named new features combined. | Group | 7.19 -> cur | net | nature | |---|---|---:|---| | scheduler core + startup (`sched_*`, `nx_start`/`os_start`) | 2123 -> 2970 | **+847** | broad per-object growth | | semaphore (incl. new `sem_rw` rwlock) | 534 -> 1348 | +814 | atomic paths + rwlock | | assert/panic | 0 -> 754 | +754 | relocated from arch (not new; `assert.o` is +690) | | task mgmt (`task_*`) | 1597 -> 2139 | **+542** | broad per-object growth | | POSIX timer (`timer_initialize/release`) | 0 -> 524 | +524 | feature: preallocated timers | | workqueue (`kwork_*` merged into `kwork_thread`) | 327 -> 796 | +469 | thread model | | pthread (`pthread_*`) | 48 -> 462 | **+414** | join/exit/cancel paths | | clock (`clock_*`) | 136 -> 464 | **+328** | timekeeping | | group (`group_*`) | 387 -> 709 | **+322** | task-group lifecycle | | env (`env_*`) | 538 -> 840 | **+302** | environment handling | | misc (tls/errno/instrument) | 36 -> 174 | +138 | | | exit | 28 -> 60 | +32 | | | irq | 160 -> 170 | +10 | | | mqueue (`mq_*`) | 452 -> 254 | -198 | shrink | | watchdog (`wd_*`) | 711 -> 244 | -467 | shrink | | signal (`sig_*`/`nxsig`) | 2345 -> 1828 | **-517** | `nx*` refactor shrank it | So libsched's +4,314 is dominated not by the named new features (semaphore +814, timer +524, workqueue +469, which net against signal -517 / watchdog -467 / mqueue -198) but by broad, small per-object growth across the scheduler core, task management, pthread, clock, group and env (~2.75 KB combined), plus the assert relocation from arch (+754, offset by libarch -679). ### libfs.a +3,517 One theme: directories, read/write and ioctl all routed through the file layer / vectored path. Full partition, sums to +3,517. | Subsystem | net | reason | |---|---:|---| | growable fd table (`fs_files`) | +743 | dynamic fd list | | inode search (`fs_inodesearch`) | +628 | `inode_search_setup` rework (incl. FS-tree rwlock) | | generic ioctl (`fs_ioctl`) | +474 | split-out ioctl layer (`FIOC_FILEPATH`) | | vectored read/write (`fs_read`+`fs_write`) | +404 | scalar -> `struct uio` | | open (`fs_open`) | +282 | O_DIRECTORY + dir_allocate dispatch | | directory layer (`fs_dir` + old dirent) | +300 | directories-as-fds | | mkdir/rmdir (split out) | +266 | standalone objects | | umount (`fs_umount2`) | +222 | | | uio (`fs_uio`) | +184 | vectored-I/O helper | | other objects (net) | +14 | | | **TOTAL** | **+3,517** | | `fs_dir.o` (+832) absorbs old `fs_opendir`+`fs_readdir`+`fs_closedir`; the visible dir symbols actually *shrank* (530->170 B). The growth is the file-layer machinery the dir path now forces to link (`opendir` -> `open(O_DIRECTORY)` -> fd -> `file_readv` -> `dir_read`), which buys `dirfd()`/`fdopendir()`/`fchdir()`/`/proc/self/fd`. Nearly all of libfs is implementation bloat from interface unification - the subsystem with the most reduction leverage. ### libapps.a +2,545 Almost entirely NSH feature growth. Matched by logical name (object naming scheme changed between versions). Full partition, sums to +2,545. | delta | object (logical) | reason | |---:|---|---| | +1,498 | `nsh_fscmds` | more filesystem commands | | +796 | `nsh_parse` | heavier parser | | +528 | `nsh_mntcmds` | new mount/umount commands | | +400 | `nsh_syscmds` | new system commands (uname) | | +364 | `readline` (fd+common) | new fd-based readline | | +211 | `nsh_envcmds` | environment commands | | +127 | `nsh_command` | dispatch table | | -1,379 | other objects (net) | many 7.19 commands shrank/merged (ddcmd, dbgcmds, etc.) | | **+2,545** | **TOTAL** | | The growing objects are features users asked for (config-gated via `CONFIG_NSH_DISABLE_*`); the large -1,379 "other" shows a lot of the 2016 command set was simultaneously slimmed, so the net is much smaller than the gross additions. ### libc.a +2,397 `ls -l` time formatting, getopt_long, termios - not printf. Full partition, sums to +2,397. | group | net | driver | |---|---:|---| | `ls -l` time (`strftime`+`gmtime_r`) | +910 | timestamp display | | `getopt_long` (`getopt_common`, net of old `getopt`) | +380 | long-option parsing | | realpath | +346 | NSH canonical paths | | termios (`cfspeed`) | +320 | serial line config | | printf engine (`libvsprintf`+`vasprintf`) | +180 | | | uname (`utsname`) | +142 | | | other objects (net) | +119 | strtoul, itoa, streams, mutex, getcwd, ... | | **TOTAL** | **+2,397** | | The core printf engine grew only +92 (1131->1223) - *not* a major contributor, contrary to the usual assumption. The real growth is the `ls -l` time chain (+910) and `getopt_long` (+380, net of the removed old `getopt`), both individually config-selectable. ### libdrivers.a +1,391 Essentially just serial vectoring + termios. Full partition, sums to +1,391. | group | net | reason | |---:|---|---| | serial (`serial.o` +1,413, `serial_io` +250) | +1,663 | `uart_read/write` -> `uart_readv/writev(struct uio*)`; adds baud table, `tcdrain`/`tcflush`/`cfsetspeed` | | syslog family (net) | -180 | reorganized into fewer, larger objects (`syslog_device`/`putc`/`stream` removed -> `syslog_write`+`syslog_channel`) - net *shrink*, not ~0 | | other objects (net) | -92 | `dev_null` etc. removed | | **TOTAL** | **+1,391** | | The driver-side growth is entirely serial, mirroring the VFS `readv`/`writev` move; vectoring and termios can't be cleanly split because both landed in `serial.o`. The syslog refactor and other objects actually net *down* 272 B. ### libmm.a +656 Allocator core heavier, plus locking and mmap. Full partition, sums to +656. | group | net | driver | |---|---:|---| | allocator core (malloc/free/realloc/shrink/init) | +778 | delayed-free + heavier paths | | `mm_lock` | +88 | concurrency (replaces `umm_sem`) | | `mm_map` | +76 | mmap support | | `umm_*` user-heap wrappers | +61 | | | other objects (net) | -347 | `mm_addfreechunk`/`mm_size2ndx`/`umm_sem` merged away | | **TOTAL** | **+656** | | The historical "delayed-free ~1,032 B" over-counts: measured net for delayed-free + lock is ~458 B. The allocator core growth is partly offset by small objects merged away (-347). ### Historical estimate vs measured net Features spanning multiple `.a` are netted across the whole image: | Historical change (year) | est. B | measured net B | note | |---|---:|---:|---| | Semaphores atomic fast/slow (2024) | 2,839 | **+1,054** | incl. new `sem_rw` rwlock | | posix_spawn file actions (2023) | 2,474 | **+678** | task_* setup chain | | Directories via file layer (opendir) | 2,148 | **+736** | directories-as-fds | | Full `_assert` / panic path | 1,973 | **-59** | assert.o relocated from arch (+690); stackdump is in arch (`CONFIG_ARCH_STACKDUMP=n` -> -1,320, per @arnavsharma990) | | All I/O via readv/writev (2024) | 1,942 | **+588** (VFS) | serial vectoring bundled with termios, counted separately (+1,983) | | Growable fd table (2021) | 1,911 | **+743** | above the ~196 B stub upper bound @arnavsharma990 measured, It needs to be verified again. | | Heap delayed-free list (2023) | 1,032 | **+458** | incl. new `mm_lock` | | FS-tree read/write lock (2024) | 749 | **+628** | inside the `inode_search` rework | | Memory-mapping tables (2022) | 279 | **+76** | `mm_map` | | **Sum of historical items** | **15,347** | | **above the measured total growth of 13,401 B** | Takeaways: 1. **The historical items cannot be summed** - their total (15,347 B) exceeds the entire measured growth (13,401 B); they overlap and were each measured under different scenarios. 2. **Each item measures smaller than its estimate** - semaphores 2,839->1,054, spawn 2,474->678, opendir 2,148->736, FD table 1,911->743. Only the FS-tree rwlock (749->628) is close. 3. **`_assert`/panic barely grows in the final image (-59)** libsched (+4.3 K) and libfs (+3.5 K) are the two largest movers and where a size effort has the most leverage; libapps/libc are mostly feature growth; libarch shrank. Reproduction data (maps, configs, member-level JSON) available - happy to drill further into any single library. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
