Use faster hashing algorithm in sorting code

The default std hasher is DoS-resistant but relatively slow, and
DoS-resistance isn't a useful property during sorting.

I tested using the rustc-hash, fnv and ahash crates against my
1600-plugin load order, and the results vs the default std hasher
were:

- AHash: 31% faster
- FNV: 33% faster
- FxHasher (from rustc-hash): 37% faster

With the std hasher the Rust libloot is 35% slower than C++, with
FxHasher it's 15% faster.
This commit is contained in:
Oliver Hamlet
2025-03-25 22:02:33 +00:00
parent de97b90618
commit 3b6752843a
5 changed files with 35 additions and 19 deletions
Generated
+7
View File
@@ -586,6 +586,7 @@ dependencies = [
"rayon",
"rstest",
"rstest_reuse",
"rustc-hash",
"saphyr",
"saphyr-parser",
"tempfile",
@@ -1076,6 +1077,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
+1
View File
@@ -16,6 +16,7 @@ saphyr = "0.0.3"
saphyr-parser = "0.0.3"
unicase = "2.8.1"
rayon = "1.10.0"
rustc-hash = "2.1.1"
[target.'cfg(windows)'.dependencies]
windows = { version = "0.60.0", features = ["Win32_Storage_FileSystem"] }
+7 -4
View File
@@ -1,10 +1,11 @@
use std::collections::{HashMap, HashSet, VecDeque};
use std::collections::VecDeque;
use petgraph::{
Graph,
graph::{EdgeReference, NodeIndex},
visit::EdgeRef,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use crate::{EdgeType, Vertex, logging};
@@ -36,8 +37,10 @@ pub fn bidirectional_bfs<N, E>(
) -> bool {
let mut forward_queue = VecDeque::from([from_index]);
let mut reverse_queue = VecDeque::from([to_index]);
let mut forward_visited = HashSet::from([from_index]);
let mut reverse_visited = HashSet::from([to_index]);
let mut forward_visited = HashSet::default();
forward_visited.insert(from_index);
let mut reverse_visited = HashSet::default();
reverse_visited.insert(to_index);
while let (Some(forward_current), Some(reverse_current)) =
(forward_queue.pop_front(), reverse_queue.pop_front())
@@ -81,7 +84,7 @@ pub fn find_cycle<N>(
) -> Option<Vec<Vertex>> {
let mut cycle_detector = CycleDetector::new(graph, node_mapper);
let mut colour_map = HashMap::new();
let mut colour_map = HashMap::default();
for node_index in graph.node_indices() {
depth_first_search(graph, &mut colour_map, node_index, &mut cycle_detector);
+5 -3
View File
@@ -1,4 +1,6 @@
use std::{cmp::Reverse, collections::HashMap};
use std::cmp::Reverse;
use rustc_hash::FxHashMap as HashMap;
use petgraph::{Graph, algo::bellman_ford, graph::NodeIndex};
@@ -29,7 +31,7 @@ pub fn build_groups_graph(
let userlist_groups = sorted_by_name(userlist_groups);
let mut graph = GroupsGraph::new();
let mut group_nodes: HashMap<&str, NodeIndex> = HashMap::new();
let mut group_nodes: HashMap<&str, NodeIndex> = HashMap::default();
logging::trace!("Adding masterlist groups to groups graph...");
add_groups(
@@ -206,7 +208,7 @@ pub fn sorted_group_nodes(graph: &GroupsGraph) -> Vec<NodeIndex> {
if is_root_node(graph, n) {
let mut visitor = GroupsPathLengthVisitor::new();
depth_first_search(graph, &mut HashMap::new(), n, &mut visitor);
depth_first_search(graph, &mut HashMap::default(), n, &mut visitor);
(n, true, visitor.max_path_length())
} else {
+15 -12
View File
@@ -1,13 +1,11 @@
use std::{
collections::{HashMap, HashSet},
rc::Rc,
};
use std::rc::Rc;
use petgraph::{
Graph,
graph::{EdgeReference, NodeIndex},
visit::EdgeRef,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use crate::{
EdgeType, LogLevel, Plugin,
@@ -308,7 +306,7 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> {
// Keep a record of which vertices have already been fully explored to avoid
// adding edges from their plugins more than once.
let mut finished_nodes = HashSet::new();
let mut finished_nodes = HashSet::default();
// Now loop over the vertices in the groups graph.
// The vertex sort order prioritises resolving potential cycles in
// favour of earlier-loading groups. It does not guarantee that the
@@ -330,7 +328,12 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> {
Some(default_group_node),
);
depth_first_search(groups_graph, &mut HashMap::new(), group_node, &mut visitor);
depth_first_search(
groups_graph,
&mut HashMap::default(),
group_node,
&mut visitor,
);
}
// Now do one last DFS starting from the default group and not ignoring its
@@ -345,7 +348,7 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> {
depth_first_search(
groups_graph,
&mut HashMap::new(),
&mut HashMap::default(),
default_group_node,
&mut visitor,
);
@@ -496,7 +499,7 @@ impl<'a, T: SortingPlugin> PluginsGraph<'a, T> {
let mut new_load_order: Vec<NodeIndex> = Vec::new();
// Holds nodes that have already been put into new_load_order.
let mut processed_nodes = HashSet::new();
let mut processed_nodes = HashSet::default();
// First get the graph vertices and sort them into the current load order.
let mut nodes: Vec<_> = self.node_indices().collect();
@@ -897,8 +900,8 @@ impl<'a, 'b, T: SortingPlugin> PathFinder<'a, 'b, T> {
cache,
from_node_index,
to_node_index,
forward_parents: HashMap::new(),
reverse_children: HashMap::new(),
forward_parents: HashMap::default(),
reverse_children: HashMap::default(),
intersection_node: None,
}
}
@@ -985,7 +988,7 @@ struct PathCacher<'a> {
fn get_plugins_in_groups<T: SortingPlugin>(
graph: &InnerPluginsGraph<T>,
) -> HashMap<String, Vec<NodeIndex>> {
let mut plugins_in_groups: HashMap<String, Vec<NodeIndex>> = HashMap::new();
let mut plugins_in_groups: HashMap<String, Vec<NodeIndex>> = HashMap::default();
for node in graph.node_indices() {
let group_name = graph[node].group.clone();
@@ -1066,7 +1069,7 @@ impl<'a, 'b, 'c, 'd, 'e, T: SortingPlugin> GroupsPathVisitor<'a, 'b, 'c, 'd, 'e,
finished_group_vertices,
group_node_to_ignore_as_source,
edge_stack: Vec::new(),
unfinishable_nodes: HashSet::new(),
unfinishable_nodes: HashSet::default(),
}
}