Putting ccache on a backed RAM disk to speed compiles
Why do this
Compiling and building ports can be meaningfully accelerated by caching (ccache) certain intermediate results and by moving work directories from slower media to faster (tmpfs /tmp). If you do regular builds, such as one might on a poudriere server, there can be a meaningful write workload to the working directory which uses up SSD life, possibly meaningfully (though probably not really that much if your SSD is modern and big).
If you have a fast, high endurance SSD, putting ccache on it won’t do much. If ccache is going on rotating media, this config will speed up builds appreciably. The save/restore code below will preserve the ccache across reboots and leaves file management inside the ccache directory to ccache itself, while managing the persistence of any other random files that get written outside the directory.
Note this code, different than other examples I’ve found, works with FreeBSD as a service and doesn’t flush files that are accessed (read) between reboots, only files that aren’t touched in any way and therefore can (probably) be evicted without penalty and prevents cruft and clutter on the RAM disk accumulating because it has been made non-volatile.
Putting the workdirectory into a RAM-based tmpfs should speed up builds even compared to a fast SSD, as SSD write times aren’t a strong feature of SSDs. There’s no persistence code as there’s no expectation that the work directories will persist.
Setup ccache
Setting up ccache is pretty easy. First, install it from ports. If you’re using binary packages, you obviously don’t need ccache.
cd /usr/ports/devel/ccache make install clean
make.conf
Append a few lines to your make.conf files like so:
nano /etc/make.conf nano /usr/local/etc/poudriere.d/FBSD_14-0-R-make.conf
Add the following:
CCACHE_DIR=/ram/ccache WITH_CCACHE_BUILD=yes # WRKDIRPREFIX="/tmp/ports"
note that WRKDIRPREFIX (to use tmpfs /tmp, see below) seems to conflict with the same directive in poudriere.conf so comment out for poudriere hosts or don’t use the option in poudriere.
poudriere.conf
nano /usr/local/etc/poudriere.conf
CCACHE_DIR=/ram/ccache
/etc/fstab
Next, make the /ram directory and set a limit of how much RAM it can use. 12884901888 is 12GB. Somewhere between 8 and 16GB is probably sufficient for most needs. After a few builds, I was using 1.3GB.
mkdir /ram nano /etc/fstab none /ram tmpfs rw,size=12884901888 0 0 mount /ram
ccache.conf
nano /root/.ccache/ccache.conf nano /usr/local/etc/ccache.conf
cache_dir = /ram/ccache max_size = 12G
status
Now all ports you build will be compiled entirely in RAM. You can check your ccache usage with:
ccache -s
CREATE A CACHE STORE/RESTORE SCRIPT
From: https://forums.gentoo.org/viewtopic-t-838198-start-0.html
This is in /etc/rc.d
and should be executed on startup and shutdown, but only actual shutdown not reboot or halt. The correct command to reboot (and preserve /ram) is (you do not need to do this now!):
shutdown -r now
Don’t reboot now, just know that using “reboot” or some other command other than calling shutdown will not call the stop script and won’t sync the cache to NV storage.
Create /etc/rc.d/syncram
something like:
#!/bin/sh - # PROVIDE: syncram # REQUIRE: FILESYSTEMS # KEYWORD: nojail shutdown . /etc/rc.subr name="syncram" rcvar="syncram_enable" desc="rsync ram disk from/to var on startup/shutdown" stop_cmd="${name}_stop" start_cmd="${name}_start" syncram_start() { # rsync data from persistent storage to ram disk on boot # preserving all file attributes logger syncram-start /usr/local/bin/rsync -a -A -X -U -H -x \ /var/tmp/syncram/ /ram \ > /dev/null 2>/var/log/syncram-store.log touch /var/tmp/syncram/.lastsync } syncram_stop() { # rsync data from ramdisk to persistent storage on shutdown # preserving all file atributes logger syncram-stop #!/bin/sh # if the dest dir doesn't exist, create it if [ ! -d /var/tmp/syncram ]; then mkdir /var/tmp/syncram fi # flush any accumulated cruft that weren't accessed since the last sync # note tmpfs records accurate atime if [ -f /ram/.lastsync ]; then find /ram -type f ! -neweram /var/tmp/syncram/.lastsync -delete fi # rsync new or accessed removing unused from target /usr/local/bin/rsync -a -A -X -U -H -x -del \ /ram/ /var/tmp/syncram \ > /dev/null 2>/var/log/syncram-restore.log } load_rc_config $name run_rc_command "$1"
chmod +x syncram
Then edit /etc/rc.conf
to include
syncram_enable="YES"
and execute
service syncram onestart
Bonus: tmpfs for working builds
tmpfs can also be used to create a similar ramdisk at the /tmp mount point where it is fairly automatically used by poudriere to speed up builds. There’s a quirk that seems to be a problem (not fully debugged, but the config described here works and survives reboots): putting the WRKDIRPREFIX in make.conf
AND in poudriere.conf
seems to yield “workdirectory” errors so pick one for the directive and probably pick poudriere.conf
if you’re running poudriere.
poudriere.conf
nano /usr/local/etc/poudriere.conf
# Use tmpfs(5) # This can be a space-separated list of options: # wrkdir - Use tmpfs(5) for port building WRKDIRPREFIX # data - Use tmpfs(5) for poudriere cache/temp build data # localbase - Use tmpfs(5) for LOCALBASE (installing ports for packaging/testing) # all - Run the entire build in memory, including builder jails. # yes - Enables tmpfs(5) for wrkdir and data # no - Disable use of tmpfs(5) # EXAMPLE: USE_TMPFS="wrkdir data" USE_TMPFS=yes # How much memory to limit tmpfs size to for *each builder* in GiB # (default: none) #TMPFS_LIMIT=4 # List of package globs that are not allowed to use tmpfs for their WRKDIR # Note that you *must* set TMPFS_BLACKLIST_TMPDIR # EXAMPLE: TMPFS_BLACKLIST="rust" TMPFS_BLACKLIST="rust" # The host path where tmpfs-blacklisted packages can be built in. # A temporary directory will be generated here and be null-mounted as the # WRKDIR for any packages listed in TMPFS_BLACKLIST. # EXAMPLE: TMPFS_BLACKLIST_TMPDIR=${BASEFS}/data/cache/tmp TMPFS_BLACKLIST_TMPDIR=${BASEFS}/data/cache/tmp
Rust may overflow even a chonky RAM config.
/etc/fstab
nano /etc/fstab
tmpfs /tmp tmpfs rw,mode=1777 0 0
This will “intelligently” allocate remaining RAM to the tmpfs mounted at /tmp and builds should mostly happen there.
mount -a
NB
There’s a risk that screwing around with /etc/fstab
will break boot – if the system reboots to single user mode, get shell, navigate to /etc/fstab
and check for errors or comment out the lines and reboot again.