From what we usually hear, the fans of Vim, Emacs, Sublime and other nice editors often explain their choice by performance concerns. IntelliJ IDEA for example may feel laggy (comparing to a text editor) when you type, and the usual justification for that is some variation of “an intelligent IDE thinks while you’re typing to provide smart coding assistance”. While this is true, should it really be the case? We’ve tried to figure this out.
If you take a look at the implementation of IntelliJ IDEA editor, you’ll spot the problem: every time you change the document, the IDE acquires a read/write lock on it (and interrupt/restart every read operation). However, a more detailed investigation has also shown another source of latency: slow editor repainting.
Currently, IntelliJ IDEA editor repaints large screen areas far too often (this can be easily verified by adding a trace call to the paintComponent method), and depending on system configuration, such activity may be quite costly.
For example, the editor responsiveness is noticeably lower under X Window System, mainly because of the chatty, a client-server X protocol. Though Wayland can improve that, even in ideal conditions the effect of sub-optimal repainting is very substantial (especially for hi-res displays).
An apparent solution here is to minimize the number of repaint events and their scope. Another important cause of sluggish repainting is our excessive reliance on Swing’s RepaintManager:
- Repaint events are dispatched via the AWT event queue, obscured by other events (e.g. input, timers, etc.). This can delay the actual repaint. Usually it’s not a problem, yet if we want to achieve instant repaint, it’s better to avoid this.
- Repainted area is automatically extended to en envelop comprising all invalidated areas. In practice, if we’ve requested repaints of the caret and the status indicator, we may end up with repainting the whole editor.
- Other components repaints (e.g Project View) may unexpectedly alternate with repaints of the editor, which may also affect the speed of the editor repaint.
- Double-buffering is always applied (along with BitBLT), yet it may be unnecessary for minor “controllable” repaint events.
The proposed solution is to repaint editor changes instantly, using the underlying Graphics, bypassing RepaintManager (and double-buffering). We can rely on the Graphics.copyArea method working extremely fast to instantaneously shift the line “tail” and the caret. After such a preemptive reaction, an ordinary repaint routine may follow as usual. This way in addition to instantaneous visual response, we can also mitigate the burden of excessive repainting (yet, only to a certain extent of course).
It’s obviously too wasteful to acquire a lock on the entire project model on every keystroke: this interrupts and restarts any other activity acquired a read lock on the document (think of the amount of unnecessary CPU and thus battery/power usage).
Moreover, such an approach is prone to unpredictable “delay escalation”, when a subsystem or a plugin fails to check the interruption state frequently enough. Those delays could be tolerable, if they weren’t happening on every single keystroke.
What we need is a way to avoid acquiring a write lock during regular and continuous typing (this work is in progress). A simple but efficient workaround is to display input characters before starting the corresponding write action.
- Instant repaint on typing (before a write lock acquisition and main repaint routine);
- Repaints on other events such as delete, backspace, delete to word start, etc within a single line are performed before the main repaint routine (but after a write lock acquisition).
This should be enough to significantly improve the editor latency. Keep in mind though, that if subsequent blocking or repainting takes more time than a period until the next key is pressed, a typing lag is still possible.
The implementation is by no means complete, and minor glitches are expected. For us it’s just a proof of concept to build upon. Meanwhile we’re going to implement a generalized way to avoid write actions during continuous typing. We also consider employing the proposed technique for caret movement.
Measuring visual delays
When improving something, it’s always worth to measure it. To do that correctly, we need to encompass the entire repainting cycle which goes beyond JVM (e.g, in X system Graphics content is separated from the actual framebuffer). I’ve tried to create some kind of “type-o-meter” using AWT Robot for that purpose, yet it seems a more low-level tool is needed to achieve acceptable accuracy.
An objective comparison of the improved editor responsiveness to other IDEs and editors potentially can persuade reluctant users to finally adopt IntelliJ-based products (and finally bring “typing with pleasure” for all the rest).
Remember that we count on your feedback a lot for improving the current implementation. That’s why you’re very welcome to share your impression and thoughts with us in our EAP discussion forum and issue tracker.