Most Linux distributions ship generic binaries compiled to run safely on a vast array of older hardware configurations. While this ensures broad compatibility, it sacrifices the speed that comes from using the specific, modern instruction sets of your processor. Compiling Emacs directly from source allows instructing the compiler to generate machine code targeted at your CPU architecture, resulting in a faster and more efficient runtime environment.
Before we dive in, please consider sharing this article on your website/blog, Mastodon, Reddit, X, or your preferred social media platforms. Sharing it will help fellow Emacs users discover better ways to manage code folding.
Beyond raw hardware optimization, building from source enables dropping decades of legacy compatibility layers and embracing modern desktop technologies. For example, Wayland users can configure the build to bypass old X11 display protocols in favor of a Wayland environment, ensuring smoother rendering and better system integration.
Additionally, this article details how to optimize the internal native Lisp compiler, which transforms Lisp packages into machine code to ensure that every Emacs package in your configuration operates at its maximum potential speed.
Here is the step-by-step process for compiling a highly optimized Emacs binary and packages.
Installing dependencies
Arch Linux
On Arch Linux, the cleanest and most native method to resolve build dependencies is to use the official Emacs PKGBUILD, which automatically read the official dependency list and install them:
mkdir -p ~/emacs-deps
cd ~/emacs-deps
wget https://gitlab.archlinux.org/archlinux/packaging/packages/emacs/-/raw/main/PKGBUILD
makepkg --syncdeps --nobuild
NOTE: Once makepkg finishes installing the dependencies, you can safely delete the ~/emacs-deps directory.
Debian, Mint and Ubuntu
On Debian-based systems, the most efficient method to gather dependencies is to use the package manager to automatically fetch them based on the distribution’s source package. You must first ensure that source repositories are enabled:
- Open
/etc/apt/sources.list with root privileges in your text editor.
- Find the lines starting with
deb-src (Read: “sources.list format” on Debian Wiki) and uncomment them by removing the # at the beginning of the line.
- Update your package lists and download the install Emacs dependencies:
sudo apt update
sudo apt build-dep emacs
Other Linux Distributions (Generic)
If you are using a distribution not listed above, you will need to use your distribution’s specific package manager to install the required development libraries. Look for packages that include the library headers, typically suffixed with -dev or -devel.
Here is the master list of upstream library names required to compile this specific Wayland and Native-Comp optimized build:
- Core Build Tools: gcc, make, autoconf, automake, pkg-config (or pkgconf), git, texinfo
- Native Compilation Engine: libgccjit (Ensure this matches your GCC version)
- System and Core Libraries: glib2, gnutls, zlib, ncurses, sqlite3, tree-sitter, gmp
- Graphics and Wayland Toolkit (PGTK): gtk3, cairo, harfbuzz, pango, dbus
- Image Formats: libjpeg (or libjpeg-turbo), libpng, libtiff, giflib, libwebp, librsvg2, lcms2
- Fonts and Text Shaping: fontconfig, freetype2, libotf, m17n-lib, libxml2
Optimizing the Native Lisp Compiler
Fine-Tuning Native Compilation
Before building Emacs with native compilation support, we will optimize how Emacs transforms Lisp packages into machine code. This is managed via the native-comp-compiler-options variable. While the main Emacs build uses your global CFLAGS, this variable specifically instructs libgccjit on how to handle every .el file it encounters.
Add the following to your early-init.el file:
(setq my-cpu-architecture "CHANGE_THIS_TO_YOUR_ARCHITECTURE")
(setq native-comp-compiler-options `(;; The most meaningful optimizations:
"-O2"
,(format "-mtune=%s" my-cpu-architecture)
,(format "-march=%s" my-cpu-architecture)
;; Reduce .eln size and compilation
;; overhead.
"-g0"
;; Good defensive choice for Emacs
;; stability.
"-fno-omit-frame-pointer"
"-fno-finite-math-only"))
(setq native-comp-driver-options '(;; -Wl,-z,pack-relative-relocs compresses
;; relocation tables to reduce file size and
;; slightly improve load times.
"-Wl,-z,pack-relative-relocs"
;; -Wl,-O2 applies standard linker-level
;; optimizations (like string merging) to the
;; generated shared object.
"-Wl,-O2"
;; -Wl,--as-needed prevents the linker from
;; recording dependencies on libraries that
;; are not actually used by the code.
"-Wl,--as-needed"))
IMPORTANT: Change CHANGE_THIS_TO_YOUR_ARCHITECTURE to your specific architecture. Run the following command to display the CPU architecture:
gcc -march=native -Q --help=target | grep march
The above command asks the compiler to resolve native for your current CPU and display the resulting target. For example, if the output shows -march=skylake, then replace CHANGE_THIS_TO_YOUR_ARCHITECTURE with skylake.
(If you prefer automatic CPU architecture detection, refer to the comments section below. I shared a function that detects the CPU architecture using gcc and adds it to the configuration.)
NOTE: Avoid adding -mtune=native and/or -march=native to the native-comp-compiler-options variable. While these flags work in global CFLAGS, libgccjit, the library responsible for native compilation, often fails to resolve the native keyword correctly. This happens because the JIT interface does not always trigger the same host-probing logic as the standalone GCC driver.
The rationale behind the selection of these flags is as follows
- The choice of
-O2 is an intentional balance. While -O3 may provide small performance gains in some workloads, it can also increase binary size and occasionally reduce responsiveness. -Ofast is generally discouraged because it enables aggressive floating-point optimizations.
- Using
-g0 disables the generation of debug symbols for .eln files, which reduces their size on disk and speeds up the compilation process itself.
-fno-omit-frame-pointer The Emacs developers recommend using this flag to disable omit-frame-pointer. Although enabling omit-frame-pointer frees up a general-purpose CPU register, it does not yield significant performance gains on modern architectures and can lead to bugs that are difficult to debug. According to Eli Zaretskii, an Emacs developer: “See bug#76180. This is in the context of the igc branch, where omit-frame-pointer is particularly troublesome, though it also causes problems in other situations. For further details, see etc/DEBUG in the Emacs source tree.”
- The
-fno-finite-math-only flag prevents the compiler from assuming that floating-point operations never produce NaN or infinity values. This flag is mostly defensive because GCC does not enable -ffinite-math-only at -O2 by default, but it can help avoid unsafe assumptions if additional aggressive optimization flags are introduced later.
Purging the Native Cache to Force JIT Recompilation
After modifying the native-comp-compiler-options and/or native-comp-driver-options variables, you must force Emacs to rebuild your packages. Ensure Emacs is closed and delete all existing .eln binaries using the following command:
find ~/.emacs.d/ -name '*.eln' -delete
Because Emacs uses a deferred compilation engine (Native JIT compiler), you do not need to manually trigger a build script. Simply launch Emacs and begin your normal workflow. As Emacs loads your packages and detects the missing .eln files, it will spin up background threads to recompile them using your new optimization flags.
Compiling Emacs for Performance
Step 1: Cloning and Preparing the Source
To build Emacs, you must first obtain the source code from the official mirror and switch to a stable release branch:
git clone https://github.com/emacs-mirror/emacs
cd emacs
git checkout "emacs-30.2"
NOTE: The official Savannah Git repository at https://git.savannah.gnu.org/git/emacs.git can occasionally be slow or unreliable, so this guide uses the official GitHub mirror instead.
We target a specific release tag like emacs-30.2 (If you desire to install another version, you can display tags using the git tag command) to ensure a stable foundation. Building from a tagged release, rather than the bleeding-edge development branch like the master branch, minimizes the risk of build failures or experimental regressions that could impact your daily productivity.
Standard cleanup is often insufficient when switching branches or recovering from a failed build. Execute the following commands to ensure a pristine environment where no residual artifacts can interfere with the new build:
git reset --hard HEAD
git clean -f -d -x
rm -fr .git/hooks/*
(Executing these commands acts as the ultimate factory reset for your repository, which is important when switching between major Emacs versions. Emacs has a highly complex build process where the C core compiles its own Lisp files; if leftover artifacts from a previous configuration, such as cached object files, old Makefiles, or stale byte-compiled .elc scripts, git hooks, are left behind, they can silently contaminate the new build and cause segmentation faults or compile-time errors. Together, these commands guarantee a pristine, predictable environment.)
Once inside the repository, all subsequent configuration and compilation commands are executed from this directory.
Step 2: Generating the Configuration Script
Emacs uses the Autotools build system, which requires generating the final configuration script before it can be used:
./autogen.sh
This shell script reads the developer configuration files provided in the repository and generates the final ./configure script. It sets up the necessary infrastructure to inspect your specific environment, locate system libraries, and prepare the Makefile.
Step 3: Setting Compiler and Linker Flags
To enhance runtime performance, we must export specific variables that instruct the C compiler and linker to optimize the resulting binary for the host CPU:
export CFLAGS="-O2 -pipe -march=native -mtune=native -fno-omit-frame-pointer -fno-plt -flto=auto"
export LDFLAGS="-Wl,-O2 -Wl,-z,now -Wl,-z,relro -Wl,--sort-common -Wl,--as-needed -Wl,-z,pack-relative-relocs -flto=auto"
The CFLAGS variable tells the compiler to apply safe, general-purpose optimizations (-O2), optimize instruction scheduling for the specific CPU compiling the code (-march=native and -mtune=native), store the metadata necessary for the linker to later perform cross-module optimization (-flto=auto), disable omit-frame-pointer (-fno-omit-frame-pointer), use memory pipes instead of temporary files to speed up the build (-pipe), and remove indirect PLT calls to external functions, improving runtime call efficiency at the cost of slightly higher startup and dynamic loading overhead (-fno-plt).
The LDFLAGS variable instructs the linker to apply additional optimization passes to the final executable (-Wl,-O2), group common symbols to reduce duplicate storage (-Wl,--sort-common), avoid linking unused shared libraries (-Wl,--as-needed), reduces binary size by performing a global analysis to inline functions and remove dead code across the entire program (-flto=auto), compress relative relocations to reduce relocation overhead and binary size (-Wl,-z,pack-relative-relocs), resolve all dynamic symbols at startup, trading slightly slower launch time for faster, more consistent runtime execution (-Wl,-z,now -Wl,-z,relro). Together, these flags help produce a smaller, more memory-efficient executable with improved startup and runtime performance.
NOTE: You must ensure CC are exported to the exact gcc version that matches your libgccjit version. For example: export CC="/usr/bin/gcc-11".
Step 4: Configuring the Build
We run the configure script to select exactly which features to include.
For X11 users:
./configure \
--with-x \
--with-x-toolkit=gtk3 \
--without-toolkit-scroll-bars \
--with-cairo \
--without-xft \
--with-harfbuzz \
--without-libotf \
--with-gnutls \
--without-xdbe \
--without-xim \
--without-gpm \
--disable-gc-mark-trace \
--enable-link-time-optimization \
--with-gsettings \
--with-modules \
--with-threads \
--with-libgmp \
--with-xml2 \
--with-tree-sitter \
--with-zlib \
--without-included-regex \
--with-native-compilation \
--with-file-notification=inotify \
--without-compress-install
For Wayland users:
./configure \
--without-x \
--with-pgtk \
--with-toolkit-scroll-bars \
--with-cairo \
--without-xft \
--with-harfbuzz \
--without-libotf \
--with-gnutls \
--without-xdbe \
--without-xim \
--without-gpm \
--disable-gc-mark-trace \
--enable-link-time-optimization \
--with-gsettings \
--with-modules \
--with-threads \
--with-libgmp \
--with-xml2 \
--with-tree-sitter \
--with-zlib \
--without-included-regex \
--with-native-compilation \
--with-file-notification=inotify \
--without-compress-install
The rationale behind the selection of these flags is as follows
--enable-link-time-optimization Instructs the build system to natively apply link-time optimization. This ensures the Emacs build scripts coordinate the optimization properly across all compiled object files to produce the fastest possible executable. When you pass this to ./configure, the build system actively probes your environment. If you look at the configure.ac script, you will see it attempts to find the number of available CPU cores. It then automatically injects -flto=N (where N is your core count) into the build flags. More importantly, this flag ensures that archiving tools like ar and ranlib are configured to use the correct LTO plugins.
--disable-gc-mark-trace: Disable the GC mark trace buffer for about 5% better garbage collection performance. It removes debugging code from the garbage collector marking phase. This provides a reduction in overhead during garbage collection cycles, leading to a more responsive experience during memory-intensive tasks.
--with-native-compilation Enable native compilation. (I omitted the aot flag to instruct the build system to skip compiling the built-in Lisp files during the make step. Instead, Emacs defers this work until runtime, where it will use the optimizations in native-comp-compiler-options and native-comp-driver-options while compiling all .el files the first time Emacs is launched.)
--with-cairo and --with-harfbuzz enable modern text rendering.
--without-compress-install By default, Emacs compresses its installed Lisp files to save disk space. Using this flag keeps the files uncompressed. The benefit is faster load times when Emacs needs to read these files from disk, as it skips the decompression step entirely.
--without-xft Disable xft because modern Emacs rendering no longer needs it. Modern GTK builds of Emacs use Cairo + HarfBuzz handle rendering.
--without-included-regex Makes Emacs use the system libc regex implementation. On contemporary Linux systems using glibc, the system regex engine is usually better optimized and maintained than Emacs’s bundled GNU regex implementation.
--with-libgmp Enables GMP, the GNU Multiple Precision Arithmetic Library, an optimized engine Emacs relies on to calculate arbitrary-precision integers (bignums) at bare-metal speeds. Modern Emacs workflows constantly process massive numbers in the background. If you disable this flag or fail to install the system library, Emacs is forced to fall back on mini-gmp, a generic, software-only C implementation designed strictly for portability rather than speed.
--without-xim: Disable XIM, a legacy X Input Method protocol used primarily for typing complex languages (like Chinese, Japanese, or Korean) on old X11 systems. If you use GTK3/Wayland, modern input methods handle this instead. Disabling this is recommended for Wayland/PGTK users as it removes unnecessary X11-specific code.
--with-file-notification=inotify Guarantees that the build will use the Linux kernel’s efficient event-watching API rather than falling back to slow, resource-heavy polling if the detection fails.
--with-gsettings Enable communication with the GTK configuration storage system. This allows inheriting system-wide preferences for themes, fonts, antialiasing hints…
--without-libotf: Disables the legacy OpenType Font library. This is recommended for modern builds that use HarfBuzz (--with-harfbuzz), as HarfBuzz handles text shaping more efficiently. Disabling this library reduces binary size without sacrificing font quality in modern desktop environments.
--with-gnutls: Enables secure network communication. This is required for secure package installation from MELPA/ELPA and for browsing HTTPS websites via EWW.
--without-xdbe: Disables the X11 Double Buffer Extension. This protocol is redundant for modern builds because both the PGTK (Wayland) and GTK3 (X11) layers handle window buffering internally. Disabling it simplifies the binary and ensures Emacs uses modern rendering paths.
--without-gpm Disable General Purpose Mouse (GPM), a background service that provides mouse support for the Linux console (the text-only TTY you see before logging into a graphical desktop). Unless you plan to use Emacs in a bare-metal Linux console (outside of a terminal emulator like Alacritty, Foot, or GNOME Terminal), GPM is unnecessary. Modern terminal emulators use their own internal protocols for mouse interaction that do not rely on the GPM daemon.
Additional flags to pass to ./configure
If your workflow is primarily text-based, you can significantly reduce the number of external libraries linked to your binary. However, each omission comes with a specific trade-off:
--without-sound Disables support for audio notifications. You will not hear the audible system bell or sound cues from productivity packages like Pomodoro timers or chat clients.
--without-libsystemd Disables the ability for Emacs to communicate with systemd. You cannot use advanced systemd service features, such as Type=notify, which allows systemd to know exactly when the Emacs daemon is fully loaded and ready.
--without-dbus: Disables D-Bus integration in Emacs. This produces a slightly leaner build, but removes integration with desktop services that rely on D-Bus. Depending on the desktop environment and enabled packages, this can affect desktop notifications, interaction with services such as GNOME Keyring, file manager integration, power and network state notifications, and various portal-based desktop features. It is generally safe on minimal Emacs GUI builds or terminal-only setups, but many modern Linux desktop environments expect D-Bus support.
--without-gconf Disables support for GConf. This is a deprecated GNOME configuration system that was replaced by GSettings. Disabling it ensures your build is not carrying legacy, obsolete desktop integrations.
--without-sqlite3 Disables the built-in SQLite engine. This breaks modern packages that rely on a local database for performance, such as org-roam or certain mail indexers.
--without-m17n-flt Disables the m17n library for complex text layout. While fine for English-only workflows, this will cause rendering issues (incorrect character joining or positioning) for complex scripts like Indic or Thai.
--without-selinux Disables Security-Enhanced Linux support. On systems where SELinux is active, Emacs will be unable to preserve or manage SELinux security contexts when creating or copying files, potentially leading to permission issues or security policy violations.
--without-libsmack Disables Smack security support. Similar to SELinux, if your Linux distribution uses the Smack kernel security module, disabling this prevents Emacs from managing those specific security contexts on files.
--disable-build-details: Omits build metadata, such as your machine’s hostname and the compilation timestamp, from the final executable. While this doesn’t provides performance or startup speed benefits, it ensures a “reproducible build” and prevents your personal system details from leaking if you share your compiled binary.
--without-gif, --without-jpeg, --without-png, --without-rsvg, --without-tiff, --without-xpm, --without-webp Disabling these prevents Emacs from linking against heavy image processing libraries. Emacs will be unable to render these image formats natively. This breaks functionality for image-heavy modes, such as image-mode, viewing PDFs via pdf-tools, or displaying mathematical LaTeX previews.
--without-imagemagick Prevent Emacs from using this large dependency for complex image transformations. You lose the ability to perform advanced image manipulation (like dynamic resizing or rotation of certain formats) within Emacs buffers.
--without-lcms2 Remove the Little CMS color management layer. Images may appear with slightly incorrect colors on high-quality displays, as Emacs will ignore ICC color profiles embedded in files.
--without-xinput2: Disables the modern X11 input extension. Warning: If you are building for X11, disabling this will break pixel-precision smooth scrolling for trackpads and mouse wheels, forcing Emacs to use choppy line-by-line scrolling. This flag does nothing for Wayland/PGTK builds and offers no performance benefits.
--disable-acl: Disables support for Access Control Lists. Disabling this reduces the number of system calls Emacs performs during file operations. This is a good choice for single-user systems where standard Unix permissions are sufficient.
--disable-xattr: Disables support for Extended File Attributes. Only keep this enabled if you rely on SELinux or filesystem-level file tagging.
--without-kerberos --without-pop --without-kerberos5 --without-hesiod --without-mail-unlink: These flags disable support for ancient mail retrieval methods, directory services, and local mail spool manipulation. Because modern Emacs mail setups rely on external synchronization, disabling these ensures the build script does not waste time searching for legacy network libraries and keeps the binary entirely free of dead mail code.
Step 5: Compiling the Source Code
With the configuration complete, the next step is to compile the C source files and the core Emacs Lisp files:
make -j "$(nproc)" -l "$(nproc --ignore=1)"
-j $(nproc): This sets the maximum number of concurrent jobs to the number of CPU cores. Make will attempt to run up to the the number of CPU cores compilation commands in parallel. This is an upper bound; actual concurrency may be lower depending on dependencies and system load.
-l $(nproc --ignore=1): This sets a load average limit. $(nproc --ignore=1) returns the number of available CPU cores minus 1. Example: on an 8-core system, this evaluates to 7. So effectively, -l 7 means: do not start new jobs if system load average is 7 or higher. Make uses system load average (typically 1-minute load average) as a throttling mechanism.
On a low-load system, Make may run close to 48 jobs in parallel. Under high CPU pressure, Make will throttle and pause scheduling new jobs until load decreases.
(Related article: Similar to make -j command above for Emacs compilation, read “Enabling Emacs native compilation and dynamically adjusting the number of Elisp files compiled in parallel“)
Step 6: Installing the Stripped Binary
The final step is to copy the compiled binary and its associated files to their proper locations on your file system.
sudo make install-strip
While the standard make install simply copies the compiled files, make install-strip performs an additional optimization. It strips debugging symbols from the final executable. Removing these symbols reduces reduces binary size on disk.
Verifying Emacs Build Features
After successfully compiling Emacs from source, execute the resulting Emacs binary.
Once the editor is running, it is good practice to verify that your desired features, such as native JSON parsing or tree-sitter support, were correctly detected and linked during the build process.
You can verify this by inspecting the system-configuration-features variable. To view its contents, type M-x describe-variable RET system-configuration-features RET, which will output a string detailing the exact libraries and features baked into your current executable.
Optional: Risky Emacs Optimizations
Risky Optimization: Replacing -O2 with -O3
NOTE: The Emacs developers strongly recommend against using -O3, -Os, and -fsanitize=undefined for ordinary production builds, stating that these flags are known to sometimes cause unpredictable problems with the generated code. (Source: The INSTALL file in the Emacs source tree.) That said, I have compiled Emacs using -O3 and have not experienced any issues.
Here is how to apply this optimization:
- Compiler configuration: Replace the
-O2 flag with -O3 in your CFLAGS. (Avoid substituting -Wl,-O2 with -Wl,-O3 in your LDFLAGS, as the underlying GNU ld documents optimization levels up to -O2 for linker optimizations.)
- Emacs configuration: Replace
-O2 flag with -O3 in the native-comp-compiler-options variable.
The theoretical benefits of -O3:
- Aggressive function Inlining: Function inlining speeds up execution by pasting a function’s code directly where it is called, eliminating the overhead of jumping to a different memory address.
- Loop Unrolling: Loop unrolling is a compiler optimization that copies parts of a loop multiple times so the program does not need to repeatedly check conditions like
i < 10 as often. This reduces repeated checks and jumps, allowing the CPU to spend more time performing useful work instead of constantly deciding whether to continue the loop. This creates longer uninterrupted sections of code, which modern processors can execute more efficiently by processing several independent operations in parallel at the same time.
- Faster Buffer Processing (Vectorization/SIMD): For heavy text searching, such as complex regular expression matching or byte-level buffer manipulation,
-O3 may allow the compiler to use special CPU instructions that operate on many bytes of data at once instead of processing each byte individually. For example, instead of comparing one character at a time, the CPU may compare 16, 32, or more characters in a single operation, which can improve performance in some workloads.
The tangible risks of -O3:
- Instruction Cache Bloat: Aggressive inlining copies large amounts of code directly into other functions, which can significantly increase the size of the final executable.
- Exposing Undefined Behavior:
-O3 enables more aggressive optimizations that assume the C source code fully follows the rules of the C standard. In large and old codebases like Emacs, some code may accidentally rely on behavior that is technically invalid or undefined. When combined with Link-Time Optimization (LTO), the compiler can optimize across multiple source files and make transformations that expose these hidden issues, potentially causing random crashes, UI freezes, or unstable behavior.
- Mangled Stack Traces:
-O3 aggressively rearranges and simplifies code to improve performance. Functions may be merged, variables may disappear entirely, and instructions may execute in a very different order than they appear in the original source code. If Emacs crashes, debugging tools such as gdb may produce stack traces that no longer clearly match the original program structure, making debugging substantially more difficult.
Using -O3 is an experimental optimization. If you experience out-of-memory build failures, random micro-stutters, UI freezing, or crashes, revert to a standard -O2 build with or without LTO.
Conclusion
Building Emacs from source and bypassing generic Emacs distribution binaries strips away decades of legacy compatibility layers and align the Emacs’ execution directly with your specific hardware architecture.
Remember to trigger a fresh recompilation whenever you update core system toolchains, such as GCC or your graphical compositor, to maintain binary compatibility and performance integrity.