contour-terminal / contour

Modern C++ Terminal Emulator
http://contour-terminal.org/
Apache License 2.0
2.31k stars 100 forks source link

throughput performance issue on lines longer than available columns #1488

Open christianparpart opened 3 months ago

christianparpart commented 3 months ago

When using termbench to compare some perf numbers, just with Contour alone, we see a dramatic drop of throughput performance when the number of characters being written, before a LF is sent, is more than available columsn per line on the screen.

image (graph generated by @Yaraslaut - thx for that :))

In this screenshot, the terminals were configured at 100 column page width, and Contour shows a really unhealth drop when the line goes longer than the number of columns available.

Note, this is quite an unusual usecase and would very rarely hit the actual user unless someone wants explicitly exploit such corner cases. So I'd not consider it a high priority, but it would still be nice to address to make the terminal perform more harmonic throughout the work load.

Yaraslaut commented 2 months ago

One of the idea is to rework grid system, and use c++23 chunk_view for resize action, prototype for this :

#include <algorithm>
#include <iostream>
#include <ranges>
#include <sstream>
#include <typeinfo>
#include <variant>
#include <vector>

void print(auto &&chunk) {
  std::cout << '[';
  for (auto inner_iter = chunk.begin(); inner_iter != chunk.end(); ++inner_iter)
    std::cout << *inner_iter;
  std::cout << "] \n";
}

template <class T>
class VectorView : public std::ranges::view_interface<VectorView<T>> {
public:
  using iterator = typename std::vector<T>::const_iterator;
  VectorView() = default;

  VectorView(iterator begin, iterator end) : m_begin(begin), m_end(end) {}

  VectorView(const std::vector<T> &vec)
      : m_begin(vec.cbegin()), m_end(vec.cend()) {}

  auto begin() const { return m_begin; }

  auto end() const { return m_end; }

private:
  iterator m_begin{}, m_end{};
};

template<typename T>
concept line_concept = requires(T line)
{
    { line.size() } -> std::convertible_to<std::size_t>;
};

template <typename vector_view = VectorView<char>,
          typename buffer_view = std::string_view,
          typename line_view = std::variant<vector_view, buffer_view>,
          typename lines_view = std::vector<line_view>>
struct Grid : public std::ranges::view_interface<lines_view> {
  int current_width = 0;
  lines_view _lines;
  lines_view _lines_view_for_display;

  auto begin() const { return _lines_view_for_display.begin(); }
  auto end() const { return _lines_view_for_display.end(); }

  void add_line(line_concept auto&& line) {
    if (line.size() > current_width)
      current_width = line.size();

    _lines.emplace_back(line);
  }

   void print() {
    for (auto &&one_line : _lines_view_for_display) {
      std::visit([](auto &&line) { ::print(line); }, one_line);
    }
  }

  void resize(int w) {
    lines_view newlines;
    auto do_chunking = [&]<class T>(T a, line_view line) {
      for (auto &&chunk : std::get<T>(line) | std::views::chunk(w)) {
        newlines.push_back(T{chunk.begin(), chunk.end()});
      }
    };
    for (auto line : _lines) {
      std::visit([&](auto &&arg) { do_chunking(arg, line); }, line);
    }
    std::swap(newlines, _lines_view_for_display);
  }
};

// helper type for the visitor #4
template <class... Ts> struct overloaded : Ts... {
  using Ts::operator()...;
};
// explicit deduction guide (not needed as of C++20)
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

int main() {

  std::vector<char> letters_vector{'a', 'b', 'c', 'd', 'e', 'f',
                                   'g', 'h', 'i', 'j', 'k'};

  Grid g;
  g.add_line(std::string_view{"ABCDEFGHIJK"});
  g.add_line(letters_vector);

  g.print();
  std::cout << "====================================\n";
  g.resize(8);
  g.print();
  std::cout << "====================================\n";
  g.resize(5);
  g.print();
  std::cout << "====================================\n";
  g.resize(8);
  g.print();

  std::for_each(g.begin(), g.end(), [](auto &&line) {
    std::visit([](auto &&arg) { std::cout << typeid(arg).name() << std::endl; },
               line);
  });

  for (auto &&el : g) {
    std::visit(
        overloaded{[](auto &&line) { std::cout << "Some other\n"; },
                   [](std::string_view line) { std::cout << "string_view\n"; }},
        el);
  }
}