perf: faster file-lists in the GTK client's details dialog (#2993)

* perf: faster file tree building in GTK client

* perf: faster GTK client FileList

* use a std::map for faster node lookup when building a file tree

* use tr_get_mime_type_for_filename() for mime-type lookup

* remove unnecessary layer of indirection when building the
  keys for lookuops in the mime-type icon cache

* be more careful to only call operator= on proxies whose values
  have actually changed, since that assignment is expensive
This commit is contained in:
Charles Kerr
2022-04-26 18:24:07 -05:00
committed by GitHub
parent 0025363ede
commit 20be816fee
4 changed files with 126 additions and 150 deletions

View File

@@ -6,7 +6,11 @@
#include <climits> /* INT_MAX */
#include <cstddef>
#include <cstring> // strchr
#include <functional>
#include <unordered_map>
#include <string>
#include <string_view>
#include <utility>
#include <glibmm.h>
#include <glibmm/i18n.h>
@@ -23,6 +27,8 @@
#include "Session.h"
#include "Utils.h"
using namespace std::literals;
namespace
{
@@ -145,51 +151,32 @@ bool refreshFilesForeach(
bool const is_file = iter->children().empty();
auto const old_enabled = iter->get_value(file_cols.enabled);
auto const old_priority = iter->get_value(file_cols.priority);
auto const index = iter->get_value(file_cols.index);
auto const old_have = iter->get_value(file_cols.have);
auto const size = iter->get_value(file_cols.size);
auto const old_prog = iter->get_value(file_cols.prog);
auto const old_priority = iter->get_value(file_cols.priority);
auto const old_progress = iter->get_value(file_cols.prog);
auto const old_size = iter->get_value(file_cols.size);
auto new_enabled = old_enabled;
auto new_have = old_have;
auto new_priority = old_priority;
auto new_progress = old_progress;
auto new_size = old_size;
if (is_file)
{
auto const* tor = refresh_data.tor;
auto const file = tr_torrentFile(tor, index);
int const enabled = file.wanted;
int const priority = file.priority;
auto const progress = file.progress;
uint64_t const have = file.have;
int const prog = std::clamp(int(100 * progress), 0, 100);
auto const index = iter->get_value(file_cols.index);
auto const file = tr_torrentFile(refresh_data.tor, index);
if (priority != old_priority || enabled != old_enabled || have != old_have || prog != old_prog)
{
/* Changing a value in the sort column can trigger a resort
* which breaks this foreach () call. (See #3529)
* As a workaround: if that's about to happen, temporarily disable
* sorting until we finish walking the tree. */
if (!refresh_data.resort_needed &&
(((refresh_data.sort_column_id == file_cols.priority.index()) && (priority != old_priority)) ||
((refresh_data.sort_column_id == file_cols.enabled.index()) && (enabled != old_enabled))))
{
refresh_data.resort_needed = true;
store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
}
(*iter)[file_cols.priority] = priority;
(*iter)[file_cols.enabled] = enabled;
(*iter)[file_cols.have] = have;
(*iter)[file_cols.prog] = prog;
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog);
}
new_enabled = file.wanted;
new_priority = file.priority;
new_have = file.have;
new_progress = std::clamp(int(100 * file.progress), 0, 100);
}
else
{
uint64_t sub_size = 0;
uint64_t have = 0;
int prog;
int enabled = NOT_SET;
int priority = NOT_SET;
new_size = 0;
new_enabled = NOT_SET;
new_priority = NOT_SET;
/* since gtk_tree_model_foreach() is depth-first, we can
* get the `sub' info by walking the immediate children */
@@ -203,43 +190,75 @@ bool refreshFilesForeach(
if ((child_enabled != false) && (child_enabled != NOT_SET))
{
sub_size += child_size;
have += child_have;
new_size += child_size;
new_have += child_have;
}
if (enabled == NOT_SET)
if (new_enabled == NOT_SET)
{
enabled = child_enabled;
new_enabled = child_enabled;
}
else if (enabled != child_enabled)
else if (new_enabled != child_enabled)
{
enabled = MIXED;
new_enabled = MIXED;
}
if (priority == NOT_SET)
if (new_priority == NOT_SET)
{
priority = child_priority;
new_priority = child_priority;
}
else if (priority != child_priority)
else if (new_priority != child_priority)
{
priority = MIXED;
new_priority = MIXED;
}
}
prog = sub_size != 0 ? (int)(100.0 * have / sub_size) : 1;
new_progress = new_size != 0 ? (int)(100.0 * new_have / new_size) : 1;
}
if (size != sub_size || have != old_have || priority != old_priority || enabled != old_enabled || prog != old_prog)
if (new_priority != old_priority || new_enabled != old_enabled)
{
/* Changing a value in the sort column can trigger a resort
* which breaks this foreach () call. (See #3529)
* As a workaround: if that's about to happen, temporarily disable
* sorting until we finish walking the tree. */
if (!refresh_data.resort_needed &&
(((refresh_data.sort_column_id == file_cols.priority.index()) && (new_priority != old_priority)) ||
((refresh_data.sort_column_id == file_cols.enabled.index()) && (new_enabled != old_enabled))))
{
(*iter)[file_cols.size] = sub_size;
(*iter)[file_cols.size_str] = tr_strlsize(sub_size);
(*iter)[file_cols.have] = have;
(*iter)[file_cols.priority] = priority;
(*iter)[file_cols.enabled] = enabled;
(*iter)[file_cols.prog] = prog;
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), prog);
refresh_data.resort_needed = true;
store->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
}
}
if (new_enabled != old_enabled)
{
(*iter)[file_cols.enabled] = new_enabled;
}
if (new_priority != old_priority)
{
(*iter)[file_cols.priority] = new_priority;
}
if (new_size != old_size)
{
(*iter)[file_cols.size] = new_size;
(*iter)[file_cols.size_str] = tr_strlsize(new_size);
}
if (new_have != old_have)
{
(*iter)[file_cols.have] = new_have;
}
if (new_progress != old_progress)
{
(*iter)[file_cols.prog] = new_progress;
(*iter)[file_cols.prog_str] = fmt::format(FMT_STRING("{:d}%"), new_progress);
}
return false; /* keep walking */
}
@@ -270,9 +289,7 @@ void gtr_tree_model_foreach_postorder(Glib::RefPtr<Gtk::TreeModel> const& model,
void FileList::Impl::refresh()
{
tr_torrent* tor = core_->find_torrent(torrent_id_);
if (tor == nullptr)
if (tr_torrent* tor = core_->find_torrent(torrent_id_); tor == nullptr)
{
widget_.clear();
}
@@ -424,14 +441,14 @@ void buildTree(FileRowNode& node, build_data& build)
auto const& child_data = node.data();
bool const isLeaf = node.child_count() == 0;
auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(child_data.name) : DirectoryMimeType;
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(child_data.name.raw()) : DirectoryMimeType;
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *build.w);
auto const file = isLeaf ? tr_torrentFile(build.tor, child_data.index) : tr_file_view{};
int const priority = isLeaf ? file.priority : 0;
bool const enabled = isLeaf ? file.wanted : true;
auto name_esc = Glib::Markup::escape_text(child_data.name);
auto const child_iter = build.store->append(build.iter->children());
auto const child_iter = build.store->prepend(build.iter->children());
(*child_iter)[file_cols.index] = child_data.index;
(*child_iter)[file_cols.label] = child_data.name;
(*child_iter)[file_cols.label_esc] = name_esc;
@@ -449,21 +466,6 @@ void buildTree(FileRowNode& node, build_data& build)
}
}
FileRowNode* find_child(FileRowNode* parent, Glib::ustring const& name)
{
for (auto* child = parent->first_child(); child != nullptr; child = child->next_sibling())
{
auto const& child_data = child->data();
if (child_data.name == name)
{
return child;
}
}
return nullptr;
}
} // namespace
void FileList::set_torrent(int torrent_id)
@@ -471,6 +473,15 @@ void FileList::set_torrent(int torrent_id)
impl_->set_torrent(torrent_id);
}
struct PairHash
{
template<typename T1, typename T2>
auto operator()(std::pair<T1, T2> const& pair) const
{
return std::hash<T1>{}(pair.first) ^ std::hash<T2>{}(pair.second);
}
};
void FileList::Impl::set_torrent(int torrentId)
{
/* unset the old fields */
@@ -486,32 +497,34 @@ void FileList::Impl::set_torrent(int torrentId)
if (auto* const tor = core_->find_torrent(torrent_id_); tor != nullptr)
{
// build a GNode tree of the files
FileRowNode root;
auto root = FileRowNode{};
auto& root_data = root.data();
root_data.name = tr_torrentName(tor);
root_data.index = -1;
root_data.length = 0;
auto nodes = std::unordered_map<std::pair<FileRowNode* /*parent*/, std::string_view>, FileRowNode*, PairHash>{};
for (tr_file_index_t i = 0, n_files = tr_torrentFileCount(tor); i < n_files; ++i)
{
auto* parent = &root;
auto const file = tr_torrentFile(tor, i);
for (char const *last = file.name, *next = strchr(last, '/'); last != nullptr;
last = next != nullptr ? next + 1 : nullptr, next = last != nullptr ? strchr(last, '/') : nullptr)
auto path = std::string_view{ file.name };
auto token = std::string_view{};
while (tr_strvSep(&path, &token, '/'))
{
bool const isLeaf = next == nullptr;
auto name = Glib::ustring(isLeaf ? last : std::string(last, next - last));
auto* node = find_child(parent, name);
auto*& node = nodes[std::make_pair(parent, token)];
if (node == nullptr)
{
node = new FileRowNode();
auto& row = node->data();
row.name = std::move(name);
row.index = isLeaf ? (int)i : -1;
row.length = isLeaf ? file.length : 0;
parent->append(*node);
auto const is_leaf = std::empty(path);
node = parent->prepend_data({});
auto& node_data = node->data();
node_data.name = std::string{ token };
node_data.index = is_leaf ? (int)i : -1;
node_data.length = is_leaf ? file.length : 0;
}
parent = node;
@@ -537,7 +550,8 @@ void FileList::Impl::set_torrent(int torrentId)
/* set default sort by label */
store_->set_sort_column(file_cols.label, Gtk::SORT_ASCENDING);
view_->expand_all();
view_->expand_row(Gtk::TreeModel::Path("0"), false);
// view_->expand_all();
}
/***
@@ -731,7 +745,7 @@ bool FileList::Impl::on_rename_done_idle(Glib::ustring const& path_string, Glib:
if (auto const iter = store_->get_iter(path_string); iter)
{
bool const isLeaf = iter->children().empty();
auto const mime_type = isLeaf ? gtr_get_mime_type_from_filename(newname) : DirectoryMimeType;
auto const mime_type = isLeaf ? tr_get_mime_type_for_filename(newname.raw()) : DirectoryMimeType;
auto const icon = gtr_get_mime_type_icon(mime_type, Gtk::ICON_SIZE_MENU, *view_);
(*iter)[file_cols.label] = newname;

View File

@@ -6,8 +6,10 @@
*/
#include <array>
#include <map>
#include <memory>
#include <unordered_map>
#include <string>
#include <string_view>
#include <glibmm.h>
#include <giomm.h>
@@ -17,8 +19,8 @@
using namespace std::literals;
Glib::ustring const DirectoryMimeType = "folder"s;
Glib::ustring const UnknownMimeType = "unknown"s;
std::string_view const DirectoryMimeType = "folder"sv;
std::string_view const UnknownMimeType = "unknown"sv;
namespace
{
@@ -29,7 +31,7 @@ struct IconCache
{
Glib::RefPtr<Gtk::IconTheme> icon_theme;
int icon_size;
std::unordered_map<std::string, Glib::RefPtr<Gdk::Pixbuf>> cache;
std::map<std::string, Glib::RefPtr<Gdk::Pixbuf>, std::less<>> cache;
};
std::array<std::unique_ptr<IconCache>, 7> icon_cache;
@@ -58,28 +60,6 @@ std::unique_ptr<IconCache> icon_cache_new(Gtk::Widget& for_widget, Gtk::IconSize
return icons;
}
std::string _icon_cache_get_icon_key(Glib::RefPtr<Gio::Icon> const& icon)
{
std::string key;
if (auto const* const ticon = dynamic_cast<Gio::ThemedIcon*>(gtr_get_ptr(icon)); ticon != nullptr)
{
std::ostringstream names;
for (auto const& name : ticon->get_names())
{
names << name << ',';
}
key = names.str();
}
else if (auto* const ficon = dynamic_cast<Gio::FileIcon*>(gtr_get_ptr(icon)); ficon != nullptr)
{
key = ficon->get_file()->get_path();
}
return key;
}
Glib::RefPtr<Gdk::Pixbuf> get_themed_icon_pixbuf(Gio::ThemedIcon& icon, int size, Gtk::IconTheme& icon_theme)
{
auto const icon_names = icon.get_names();
@@ -134,25 +114,21 @@ Glib::RefPtr<Gdk::Pixbuf> _get_icon_pixbuf(Glib::RefPtr<Gio::Icon> const& icon,
return {};
}
Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, Glib::ustring const& mime_type)
Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, std::string_view mime_type)
{
auto icon = Gio::content_type_get_icon(mime_type);
auto key = _icon_cache_get_icon_key(icon);
if (key.empty())
auto& cache = icons.cache;
if (auto mime_it = cache.find(mime_type); mime_it != std::end(cache))
{
key = VoidPixbufKey;
return mime_it->second;
}
if (auto pixbuf_it = icons.cache.find(key); pixbuf_it != icons.cache.end())
{
return pixbuf_it->second;
}
auto const pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme));
auto mime_type_str = std::string{ mime_type };
auto icon = Gio::content_type_get_icon(mime_type_str);
auto pixbuf = _get_icon_pixbuf(icon, icons.icon_size, *gtr_get_ptr(icons.icon_theme));
if (pixbuf != nullptr)
{
icons.cache.try_emplace(key, pixbuf);
cache.try_emplace(std::move(mime_type_str), pixbuf);
}
return pixbuf;
@@ -160,10 +136,7 @@ Glib::RefPtr<Gdk::Pixbuf> icon_cache_get_mime_type_icon(IconCache& icons, Glib::
} // namespace
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
Glib::ustring const& mime_type,
Gtk::IconSize icon_size,
Gtk::Widget& for_widget)
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
{
int n;
@@ -205,9 +178,3 @@ Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
return icon_cache_get_mime_type_icon(*icon_cache[n], mime_type);
}
Glib::ustring gtr_get_mime_type_from_filename(std::string const& file)
{
bool result_uncertain;
return Gio::content_type_guess(file, {}, result_uncertain);
}

View File

@@ -7,16 +7,11 @@
#pragma once
#include <string>
#include <string_view>
#include <gtkmm.h>
extern Glib::ustring const DirectoryMimeType;
extern Glib::ustring const UnknownMimeType;
extern std::string_view const DirectoryMimeType;
extern std::string_view const UnknownMimeType;
Glib::ustring gtr_get_mime_type_from_filename(std::string const& file);
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(
Glib::ustring const& mime_type,
Gtk::IconSize icon_size,
Gtk::Widget& for_widget);
Glib::RefPtr<Gdk::Pixbuf> gtr_get_mime_type_icon(std::string_view mime_type, Gtk::IconSize icon_size, Gtk::Widget& for_widget);

View File

@@ -350,7 +350,7 @@ namespace
Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_size, Gtk::Widget& for_widget)
{
Glib::ustring mime_type;
auto mime_type = std::string_view{};
if (auto const n_files = tr_torrentFileCount(tor); n_files == 0)
{
@@ -364,7 +364,7 @@ Glib::RefPtr<Gdk::Pixbuf> get_icon(tr_torrent const* tor, Gtk::IconSize icon_siz
{
auto const* const name = tr_torrentFile(tor, 0).name;
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : gtr_get_mime_type_from_filename(name);
mime_type = strchr(name, '/') != nullptr ? DirectoryMimeType : tr_get_mime_type_for_filename(name);
}
return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);