Posts Profiling Performance in Emacs
Post
Cancel

Profiling Performance in Emacs

This short blog post showcases the builtin “performance profiling” support that Emacs has. TLDR: all you need to know are 3 commands: M-x profiler-start, M-x profiler-stop, and M-x profiler-report.

Backstory

Earlier this afternoon, I was browsing a tutorial series on OpenGL and GLFW, since I’ve just finished my midterm exams at Columbia and was looking for interesting C++ frameworks to check out. Though the industry standard is to develop OpenGL on Visual Studio (the IDE, not VSCode), me as a proud Emacs user obviously wanted to set it up in my beloved editor. So I installed glfw-x11, glew, and glut on my Arch machine, created the project folder structure, wrote a CMakeLists.txt file, and generated a compilation database (compile_commands.json file) with compiledb so my clangd server can pick up the library include paths correctly.

I followed the tutorial and wrote some code. When I came across these lines:

1
2
3
4
5
6
7
std::array<float, 6> positions{-0.25f, -0.25f, 0.0f, 0.0f, 0.5f, -0.5f};
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, 
             positions.size() * sizeof(float), 
             positions.data(), GL_STATIC_DRAW);

, I wanted to investigate the function signature of glGenBuffers. I naturally pressed F12 for LSP’s go-to-definition. And to my surprise, Emacs hung for 10 seconds, before it finally took me to the macro definition in /usr/include/GL/glew.h, which looked something like this:

1
#define glGenBuffers GLEW_GET_FUN(__glewGenBuffers)

I invoked F12 again on __glewGenBuffers to explorer deeper. This time, Emacs straight-up froze for 15 seconds before it finally found the definition… Well, I thought, this hasn’t happened before, surely it’s because this glew.h file has more than 26,000 lines of code, right? But seriously, 15 seconds?

I needed to compare. I opened VSCode to try the same LSP actions: and it took only 1-2 seconds. My stomach started to ache as I fired up Vim (with LSP configured) and tried doing the same thing… only 2-3 seconds!

My sight grew dim as I felt the world collapsing on me – NO! Emacs can’t lose to Visual Studio Code or (neo)vim! It’s the holy grail of all editors!

I started disabling fancy minor modes and unimportant features to see which package I installed was causing the trouble, but nothing helped. I tweaked settings of lsp-mode and bumped up the threshold for certain actions. I even uninstalled Emacs 27.2 and grabbed the latest Emacs 29.0 with native-compilation (which by the way took me quite a while to build), yet still! No improvement on the go-to-definition on the C file that’s 26K+ lines long.

Builtin profiler support in Emacs

Just when I was about to give up, I came across a post on Reddit that was talking about “profiling” in Emacs. I followed the steps and typed M-x profiler-start, chose “CPU” as the mode, invoked a couple of F12 go-to-definitions of my OpenGL code for Emacs to “record”, and ended the session by typing M-x profiler-stop.

I then typed M-x profiler-report to see what was really going on. And these are the results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- redisplay_internal (C function)                                5109  75%
 - jit-lock-function                                             5094  75%
  - jit-lock-fontify-now                                         5094  75%
   - jit-lock--run-functions                                     5094  75%
    - run-hook-wrapped                                           5094  75%
     - #<compiled 0x158e566e17f5>                                5094  75%
      - font-lock-fontify-region                                 5094  75%
       - #<compiled 0x158e5585c36d>                              5094  75%
        - apply                                                  5094  75%
         - tree-sitter-hl--highlight-region-with-fallback        5094  75%
          - tree-sitter-hl--highlight-region                     5094  75%
           + mapc                                                   5   0%
           + tree-sitter-hl--extend-regions                         5   0%
           + font-lock-fontify-keywords-region                      4   0%
 + eval                                                            15   0%
+ command-execute                                                 888  13%
+ timer-event-handler                                             733  10%
+ ...                                                               9   0%
  jit-lock--antiblink-post-command                                  1   0%
+ #<compiled 0x158e564836dd>                                        1   0%

You don’t need to be a profiler expert to see that the culprit is “tree-sitter-hl”. For those who don’t know, tree-sitter is a “parser generator tool and an incremental parsing library, capable of building a concrete syntax tree for a source file and efficiently updating the syntax tree on-the-fly.” I use it mainly to provide my source code with richer and more modern syntax highlighting. It’s a cool package, but not a necessity. After finding out that tree-sitter is causing the poor performance in Emacs, I disabled the package, restarted the editor, and tried the LSP go-to-definition code action one more time. This time, it took less than a second for clangd to find the implementation of the macro.

The builtin profiler saved the day, and Emacs feels snappy once again!

Conclusion

Whenever Emacs feels sluggish, don’t panic. Just start the profiler, perform the actions you thought were slow, and check what was causing the trouble under the hood. This is my first time using the builtin profiler, and I’ve been using Emacs for years! I suppose you learn something new every day, and this brilliant piece of software from the 70s never ceases to amaze me.

This post is licensed under CC BY 4.0 by the author.