-
Notifications
You must be signed in to change notification settings - Fork 702
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix quadratic runtime when updating region highlighter matches
Running %sYeti<ret>casdf on file [example.journal.txt](#4685 (comment)) can cause noticeable lag. This is because we insert text at 6000 selections, which means we need to update highlighters in those lines. The runtime for updating range highlighters is quadratic in the number of selections: for each selection, we call on_new_range(), which calls add_matches(), which calls std::rotate(), which needs needs linear time. Fix the quadratic runtime by updating all ranges in the same loop. This means that we no longer need to use std::rotate() for every single range; instead we can just use one call to std::inplace_merge() for all ranges (since ranges are sorted). The implementation uses C++20 coroutines to implement a generator that supplies the ranges to the above loop. One alternative is to allocate an intermediate Vector<LineRange>, which is actually a few percent faster but less fun. Another alternative is to replace the generator with an iterator with a custom destructor. I used this script to benchmark the improvements. (In hindsight I could have just used "-ui json" instead of tmux). #!/bin/sh set -ex N=${1:-100} kak=${2:-./kak.opt} for i in $(seq "$N") do echo -n "\ 2022-02-06 * Earth expense:electronics:audio 116.7 USD liability:card -116.7 USD 2022-02-06 * Blue Yeti USB Microphone expense:electronics:audio 116.7 USD liability:card -116.7 USD " done > big-journal.ledger echo > .empty-tmux.conf 'set -sg escape-time 5' test_tmux() { tmux -S .tmux-socket -f .empty-tmux.conf "$@" } test_tmux new-session -d "$kak" big-journal.ledger test_tmux send-keys '%sYeti' Enter c 1234567890 sleep .2 test_tmux send-keys Escape while ! test_tmux capture-pane -p | grep 123 do sleep .1 done test_tmux send-keys ':wq' Enter while test_tmux ls do sleep .1 done rm -f .tmux-socket .empty-tmux.conf The average runtimes for this script show an improvement as the input file grows: kak.old kak.new N=10000 1.142 0.897 N=20000 2.879 1.400 Detailed results: $ hyperfine -w 1 './bench.sh 10000 ./kak.opt.'{old,new} Benchmark 1: ./bench.sh 10000 ./kak.opt.old Time (mean ± σ): 1.142 s ± 0.072 s [User: 0.252 s, System: 0.059 s] Range (min … max): 1.060 s … 1.242 s 10 runs Benchmark 2: ./bench.sh 10000 ./kak.opt.new Time (mean ± σ): 897.2 ms ± 19.3 ms [User: 241.6 ms, System: 57.4 ms] Range (min … max): 853.9 ms … 923.6 ms 10 runs Summary './bench.sh 10000 ./kak.opt.new' ran 1.27 ± 0.09 times faster than './bench.sh 10000 ./kak.opt.old' $ hyperfine -w 1 './bench.sh 20000 ./kak.opt.'{old,new} Benchmark 1: ./bench.sh 20000 ./kak.opt.old Time (mean ± σ): 2.879 s ± 0.065 s [User: 0.553 s, System: 0.126 s] Range (min … max): 2.768 s … 2.963 s 10 runs Benchmark 2: ./bench.sh 20000 ./kak.opt.new Time (mean ± σ): 1.400 s ± 0.018 s [User: 0.428 s, System: 0.083 s] Range (min … max): 1.374 s … 1.429 s 10 runs Summary './bench.sh 20000 ./kak.opt.new' ran 2.06 ± 0.05 times faster than '../repro.sh 20000 ./kak.opt.old'
- Loading branch information
Showing
5 changed files
with
141 additions
and
57 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
#ifndef coroutine_hh_INCLUDED | ||
#define coroutine_hh_INCLUDED | ||
|
||
#include "safe_ptr.hh" | ||
|
||
#include <concepts> | ||
#include <coroutine> | ||
#include <exception> | ||
#include <iterator> | ||
|
||
namespace Kakoune | ||
{ | ||
|
||
template<typename T> | ||
class Generator : public SafeCountable | ||
{ | ||
public: | ||
struct promise_type; | ||
using Handle = std::coroutine_handle<promise_type>; | ||
|
||
Generator(Handle handle) : m_handle(std::move(handle)) {} | ||
~Generator() { kak_assert(m_handle.done()); m_handle.destroy(); } | ||
Generator(const Generator&) = delete; | ||
Generator& operator=(const Generator&) = delete; | ||
Generator(Generator&&) = default; | ||
Generator& operator=(Generator&&) = default; | ||
|
||
struct promise_type | ||
{ | ||
T value; | ||
std::exception_ptr exception; | ||
|
||
Generator get_return_object() { return Generator(Handle::from_promise(*this)); } | ||
std::suspend_always initial_suspend() { return {}; } | ||
std::suspend_always final_suspend() noexcept { return {}; } | ||
void unhandled_exception() { exception = std::current_exception(); } | ||
|
||
template<std::convertible_to<T> From> | ||
std::suspend_always yield_value(From &&from) { value = std::forward<From>(from); return {}; } | ||
void return_void() {} | ||
}; | ||
|
||
class iterator | ||
{ | ||
public: | ||
using value_type = T; | ||
using difference_type = std::ptrdiff_t; | ||
using pointer = T*; | ||
using reference = T&; | ||
using iterator_category = std::input_iterator_tag; | ||
|
||
iterator() = default; | ||
iterator(Generator<T>& generator) : m_generator(&generator) { ++(*this); } | ||
|
||
iterator& operator++() | ||
{ | ||
m_generator->m_handle(); | ||
if (auto exception = m_generator->m_handle.promise().exception) | ||
std::rethrow_exception(exception); | ||
return *this; | ||
} | ||
T&& operator*() const noexcept | ||
{ | ||
return std::move(m_generator->m_handle.promise().value); | ||
} | ||
bool operator==(const iterator& rhs) const | ||
{ | ||
kak_assert(not rhs.m_generator); | ||
return m_generator->m_handle.done(); | ||
} | ||
private: | ||
SafePtr<Generator<T>> m_generator; | ||
}; | ||
|
||
iterator begin() { return {*this}; } | ||
iterator end() { return {}; } | ||
|
||
private: | ||
Handle m_handle; | ||
}; | ||
|
||
} | ||
|
||
#endif // coroutine_hh_INCLUDED |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters