perf(linter, prettier, diagnostics): use FxHashMap instead of std::collections::HashMap (#5993)

Using `FxHashMap` is faster than `HashMap` in many cases, especially for hashing-heavy workloads. This change improves the performance of the linter, prettier, and diagnostics crates by using `FxHashMap` instead of `std::collections::HashMap`.
This commit is contained in:
camchenry 2024-09-23 16:29:04 +00:00
parent b240b42eb9
commit 2b17003e0b
17 changed files with 67 additions and 62 deletions

2
Cargo.lock generated
View file

@ -1572,6 +1572,7 @@ version = "0.30.0"
dependencies = [
"miette",
"owo-colors",
"rustc-hash",
"textwrap",
"unicode-width",
]
@ -1796,6 +1797,7 @@ dependencies = [
"oxc_span",
"oxc_syntax",
"pico-args",
"rustc-hash",
]
[[package]]

View file

@ -22,5 +22,6 @@ doctest = false
miette = { workspace = true }
owo-colors = { workspace = true }
rustc-hash = { workspace = true }
textwrap = { workspace = true }
unicode-width = { workspace = true }

View file

@ -1,7 +1,8 @@
use std::{borrow::Cow, collections::HashMap};
use std::borrow::Cow;
use super::{DiagnosticReporter, Info};
use crate::{Error, Severity};
use rustc_hash::FxHashMap;
#[derive(Default)]
pub struct CheckstyleReporter {
@ -24,7 +25,7 @@ impl DiagnosticReporter for CheckstyleReporter {
#[allow(clippy::print_stdout)]
fn format_checkstyle(diagnostics: &[Error]) {
let infos = diagnostics.iter().map(Info::new).collect::<Vec<_>>();
let mut grouped: HashMap<String, Vec<Info>> = HashMap::new();
let mut grouped: FxHashMap<String, Vec<Info>> = FxHashMap::default();
for info in infos {
grouped.entry(info.filename.clone()).or_default().push(info);
}

View file

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use oxc_ast::{
ast::{Argument, Expression, MethodDefinitionKind},
@ -12,6 +12,7 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::{GetSpan, Span};
use rustc_hash::FxHashMap;
use crate::{context::LintContext, rule::Rule, AstNode};
@ -62,7 +63,8 @@ impl Rule for NoThisBeforeSuper {
// first pass -> find super calls and local violations
let mut wanted_nodes = Vec::new();
let mut basic_blocks_with_super_called = HashSet::<BasicBlockId>::new();
let mut basic_blocks_with_local_violations = HashMap::<BasicBlockId, Vec<NodeId>>::new();
let mut basic_blocks_with_local_violations =
FxHashMap::<BasicBlockId, Vec<NodeId>>::default();
for node in semantic.nodes() {
match node.kind() {
AstKind::Function(_) | AstKind::ArrowFunctionExpression(_) => {
@ -153,7 +155,7 @@ impl NoThisBeforeSuper {
cfg: &ControlFlowGraph,
id: BasicBlockId,
basic_blocks_with_super_called: &HashSet<BasicBlockId>,
basic_blocks_with_local_violations: &HashMap<BasicBlockId, Vec<NodeId>>,
basic_blocks_with_local_violations: &FxHashMap<BasicBlockId, Vec<NodeId>>,
follow_join: bool,
) -> Vec<DefinitelyCallsThisBeforeSuper> {
neighbors_filtered_by_edge_weight(
@ -212,7 +214,7 @@ impl NoThisBeforeSuper {
cfg: &ControlFlowGraph,
output: Vec<DefinitelyCallsThisBeforeSuper>,
basic_blocks_with_super_called: &HashSet<BasicBlockId>,
basic_blocks_with_local_violations: &HashMap<BasicBlockId, Vec<NodeId>>,
basic_blocks_with_local_violations: &FxHashMap<BasicBlockId, Vec<NodeId>>,
) -> bool {
// Deciding whether we definitely call this before super in all
// codepaths is as simple as seeing if any individual codepath

View file

@ -1,11 +1,10 @@
use std::collections::HashMap;
use cow_utils::CowUtils;
use oxc_ast::{ast::MemberExpression, AstKind};
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::{AstNode, NodeId, ReferenceId};
use oxc_span::{GetSpan, Span};
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -80,10 +79,11 @@ impl Rule for NoConfusingSetTimeout {
let scopes = ctx.scopes();
let symbol_table = ctx.symbols();
let possible_nodes = collect_possible_jest_call_node(ctx);
let id_to_jest_node_map = possible_nodes.iter().fold(HashMap::new(), |mut acc, cur| {
acc.insert(cur.node.id(), cur);
acc
});
let id_to_jest_node_map =
possible_nodes.iter().fold(FxHashMap::default(), |mut acc, cur| {
acc.insert(cur.node.id(), cur);
acc
});
let mut jest_reference_id_list: Vec<(ReferenceId, Span)> = vec![];
let mut seen_jest_set_timeout = false;
@ -151,7 +151,7 @@ fn handle_jest_set_time_out<'a>(
reference_id_list: impl Iterator<Item = ReferenceId>,
jest_reference_id_list: &Vec<(ReferenceId, Span)>,
seen_jest_set_timeout: &mut bool,
id_to_jest_node_map: &HashMap<NodeId, &PossibleJestNode<'a, '_>>,
id_to_jest_node_map: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
) {
let nodes = ctx.nodes();
let scopes = ctx.scopes();
@ -199,7 +199,7 @@ fn handle_jest_set_time_out<'a>(
fn is_jest_fn_call<'a>(
parent_node: &AstNode<'a>,
id_to_jest_node_map: &HashMap<NodeId, &PossibleJestNode<'a, '_>>,
id_to_jest_node_map: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
ctx: &LintContext<'a>,
) -> bool {
let mut id = parent_node.id();

View file

@ -1,10 +1,9 @@
use std::collections::HashMap;
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -104,7 +103,8 @@ impl Rule for NoDuplicateHooks {
let Some(root_node) = ctx.nodes().root_node() else {
return;
};
let mut hook_contexts: HashMap<NodeId, Vec<HashMap<String, i32>>> = HashMap::new();
let mut hook_contexts: FxHashMap<NodeId, Vec<FxHashMap<String, i32>>> =
FxHashMap::default();
hook_contexts.insert(root_node.id(), Vec::new());
let mut possibles_jest_nodes = collect_possible_jest_call_node(ctx);
@ -120,7 +120,7 @@ impl NoDuplicateHooks {
fn run<'a>(
possible_jest_node: &PossibleJestNode<'a, '_>,
root_node_id: NodeId,
hook_contexts: &mut HashMap<NodeId, Vec<HashMap<String, i32>>>,
hook_contexts: &mut FxHashMap<NodeId, Vec<FxHashMap<String, i32>>>,
ctx: &LintContext<'a>,
) {
let node = possible_jest_node.node;
@ -157,7 +157,7 @@ impl NoDuplicateHooks {
let last_context = if let Some(val) = contexts.last_mut() {
Some(val)
} else {
let mut context = HashMap::new();
let mut context = FxHashMap::default();
context.insert(hook_name.clone(), 0);
contexts.push(context);
contexts.last_mut()

View file

@ -1,5 +1,3 @@
use std::collections::HashMap;
use oxc_ast::{
ast::{Argument, CallExpression},
AstKind,
@ -8,6 +6,7 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -74,8 +73,8 @@ declare_oxc_lint!(
impl Rule for NoIdenticalTitle {
fn run_once(&self, ctx: &LintContext) {
let possible_jest_nodes = collect_possible_jest_call_node(ctx);
let mut title_to_span_mapping = HashMap::new();
let mut span_to_parent_mapping = HashMap::new();
let mut title_to_span_mapping = FxHashMap::default();
let mut span_to_parent_mapping = FxHashMap::default();
possible_jest_nodes
.iter()

View file

@ -1,10 +1,9 @@
use std::collections::HashMap;
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::NodeId;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -76,10 +75,11 @@ impl Rule for NoStandaloneExpect {
fn run_once(&self, ctx: &LintContext<'_>) {
let possible_jest_nodes = collect_possible_jest_call_node(ctx);
let id_nodes_mapping = possible_jest_nodes.iter().fold(HashMap::new(), |mut acc, cur| {
acc.entry(cur.node.id()).or_insert(cur);
acc
});
let id_nodes_mapping =
possible_jest_nodes.iter().fold(FxHashMap::default(), |mut acc, cur| {
acc.entry(cur.node.id()).or_insert(cur);
acc
});
for possible_jest_node in &possible_jest_nodes {
self.run(possible_jest_node, &id_nodes_mapping, ctx);
@ -91,7 +91,7 @@ impl NoStandaloneExpect {
fn run<'a>(
&self,
possible_jest_node: &PossibleJestNode<'a, '_>,
id_nodes_mapping: &HashMap<NodeId, &PossibleJestNode<'a, '_>>,
id_nodes_mapping: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
ctx: &LintContext<'a>,
) {
let node = possible_jest_node.node;
@ -129,7 +129,7 @@ impl NoStandaloneExpect {
fn is_correct_place_to_call_expect<'a>(
node: &AstNode<'a>,
additional_test_block_functions: &[String],
id_nodes_mapping: &HashMap<NodeId, &PossibleJestNode<'a, '_>>,
id_nodes_mapping: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
ctx: &LintContext<'a>,
) -> Option<()> {
let mut parent = ctx.nodes().parent_node(node.id())?;
@ -197,7 +197,7 @@ fn is_correct_place_to_call_expect<'a>(
fn is_var_declarator_or_test_block<'a>(
node: &AstNode<'a>,
additional_test_block_functions: &[String],
id_nodes_mapping: &HashMap<NodeId, &PossibleJestNode<'a, '_>>,
id_nodes_mapping: &FxHashMap<NodeId, &PossibleJestNode<'a, '_>>,
ctx: &LintContext<'a>,
) -> bool {
match node.kind() {

View file

@ -1,10 +1,9 @@
use std::collections::HashMap;
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ScopeId;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -141,7 +140,7 @@ declare_oxc_lint!(
impl Rule for PreferHooksOnTop {
fn run_once(&self, ctx: &LintContext) {
let mut hooks_contexts: HashMap<ScopeId, bool> = HashMap::default();
let mut hooks_contexts: FxHashMap<ScopeId, bool> = FxHashMap::default();
let mut possibles_jest_nodes = collect_possible_jest_call_node(ctx);
possibles_jest_nodes.sort_by_key(|n| n.node.id());
@ -154,7 +153,7 @@ impl Rule for PreferHooksOnTop {
impl PreferHooksOnTop {
fn run<'a>(
possible_jest_node: &PossibleJestNode<'a, '_>,
hooks_context: &mut HashMap<ScopeId, bool>,
hooks_context: &mut FxHashMap<ScopeId, bool>,
ctx: &LintContext<'a>,
) {
let node = possible_jest_node.node;

View file

@ -1,10 +1,9 @@
use std::collections::HashMap;
use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_semantic::ScopeId;
use oxc_span::Span;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -123,7 +122,7 @@ impl Rule for RequireTopLevelDescribe {
}
fn run_once(&self, ctx: &LintContext) {
let mut describe_contexts: HashMap<ScopeId, usize> = HashMap::new();
let mut describe_contexts: FxHashMap<ScopeId, usize> = FxHashMap::default();
let mut possibles_jest_nodes = collect_possible_jest_call_node(ctx);
possibles_jest_nodes.sort_by_key(|n| n.node.id());
@ -137,7 +136,7 @@ impl RequireTopLevelDescribe {
fn run<'a>(
&self,
possible_jest_node: &PossibleJestNode<'a, '_>,
describe_contexts: &mut HashMap<ScopeId, usize>,
describe_contexts: &mut FxHashMap<ScopeId, usize>,
ctx: &LintContext<'a>,
) {
let node = possible_jest_node.node;

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, hash::Hash};
use std::hash::Hash;
use cow_utils::CowUtils;
use oxc_ast::{
@ -9,6 +9,7 @@ use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use regex::Regex;
use rustc_hash::FxHashMap;
use crate::{
context::LintContext,
@ -31,8 +32,8 @@ pub struct ValidTitleConfig {
ignore_type_of_describe_name: bool,
disallowed_words: Vec<String>,
ignore_space: bool,
must_not_match_patterns: HashMap<MatchKind, CompiledMatcherAndMessage>,
must_match_patterns: HashMap<MatchKind, CompiledMatcherAndMessage>,
must_not_match_patterns: FxHashMap<MatchKind, CompiledMatcherAndMessage>,
must_match_patterns: FxHashMap<MatchKind, CompiledMatcherAndMessage>,
}
impl std::ops::Deref for ValidTitle {
@ -206,14 +207,14 @@ impl MatchKind {
fn compile_matcher_patterns(
matcher_patterns: &serde_json::Value,
) -> Option<HashMap<MatchKind, CompiledMatcherAndMessage>> {
) -> Option<FxHashMap<MatchKind, CompiledMatcherAndMessage>> {
matcher_patterns
.as_array()
.map_or_else(
|| {
// for `{ "describe": "/pattern/" }`
let obj = matcher_patterns.as_object()?;
let mut map: HashMap<MatchKind, CompiledMatcherAndMessage> = HashMap::new();
let mut map: FxHashMap<MatchKind, CompiledMatcherAndMessage> = FxHashMap::default();
for (key, value) in obj {
let Some(v) = compile_matcher_pattern(MatcherPattern::String(value)) else {
continue;
@ -227,7 +228,7 @@ fn compile_matcher_patterns(
},
|value| {
// for `["/pattern/", "message"]`
let mut map: HashMap<MatchKind, CompiledMatcherAndMessage> = HashMap::new();
let mut map: FxHashMap<MatchKind, CompiledMatcherAndMessage> = FxHashMap::default();
let v = &compile_matcher_pattern(MatcherPattern::Vec(value))?;
map.insert(MatchKind::Describe, v.clone());
map.insert(MatchKind::Test, v.clone());
@ -239,7 +240,7 @@ fn compile_matcher_patterns(
|| {
// for `"/pattern/"`
let string = matcher_patterns.as_str()?;
let mut map: HashMap<MatchKind, CompiledMatcherAndMessage> = HashMap::new();
let mut map: FxHashMap<MatchKind, CompiledMatcherAndMessage> = FxHashMap::default();
let v = &compile_matcher_pattern(MatcherPattern::String(
&serde_json::Value::String(string.to_string()),
))?;

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::hash_map::HashMap};
use std::borrow::Cow;
use cow_utils::CowUtils;
use itertools::Itertools;
@ -12,7 +12,7 @@ use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use phf::{phf_map, phf_set, Map, Set};
use regex::Regex;
use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize;
use crate::{
@ -426,11 +426,11 @@ const DOM_PROPERTIES_IGNORE_CASE: [&str; 5] = [
"webkitDirectory",
];
static DOM_PROPERTIES_LOWER_MAP: Lazy<HashMap<String, &'static str>> = Lazy::new(|| {
static DOM_PROPERTIES_LOWER_MAP: Lazy<FxHashMap<String, &'static str>> = Lazy::new(|| {
DOM_PROPERTIES_NAMES
.iter()
.map(|it| (it.cow_to_lowercase().into_owned(), *it))
.collect::<HashMap<_, _>>()
.collect::<FxHashMap<_, _>>()
});
///

View file

@ -1,5 +1,3 @@
use std::collections::HashMap;
use oxc_ast::{
ast::{Statement, TSModuleReference},
AstKind,
@ -7,6 +5,7 @@ use oxc_ast::{
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::{GetSpan, Span};
use rustc_hash::FxHashMap;
use crate::{
context::{ContextHost, LintContext},
@ -113,7 +112,7 @@ impl Rule for TripleSlashReference {
// We don't need to iterate over all comments since Triple-slash directives are only valid at the top of their containing file.
// We are trying to get the first statement start potioin, falling back to the program end if statement does not exist
let comments_range_end = program.body.first().map_or(program.span.end, |v| v.span().start);
let mut refs_for_import = HashMap::new();
let mut refs_for_import = FxHashMap::default();
for comment in ctx.semantic().trivias().comments_range(0..comments_range_end) {
let raw = &ctx.semantic().source_text()

View file

@ -1,5 +1,4 @@
use std::{
collections::HashMap,
ffi::OsStr,
fs,
path::{Path, PathBuf},
@ -15,7 +14,7 @@ use oxc_resolver::Resolver;
use oxc_semantic::{ModuleRecord, SemanticBuilder};
use oxc_span::{SourceType, VALID_EXTENSIONS};
use rayon::{iter::ParallelBridge, prelude::ParallelIterator};
use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
partial_loader::{JavaScriptSource, PartialLoader, LINT_PARTIAL_LOADER_EXT},
@ -145,7 +144,7 @@ impl LintService {
///
/// See the "problem section" in <https://medium.com/@polyglot_factotum/rust-concurrency-patterns-condvars-and-locks-e278f18db74f>
/// and the solution is copied here to fix the issue.
type CacheState = Mutex<HashMap<Box<Path>, Arc<(Mutex<CacheStateEntry>, Condvar)>>>;
type CacheState = Mutex<FxHashMap<Box<Path>, Arc<(Mutex<CacheStateEntry>, Condvar)>>>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum CacheStateEntry {

View file

@ -27,6 +27,7 @@ oxc_syntax = { workspace = true }
bitflags = { workspace = true }
cow-utils = { workspace = true }
rustc-hash = { workspace = true }
[dev-dependencies]
oxc_parser = { workspace = true }

View file

@ -5,9 +5,10 @@
mod command;
use std::collections::{HashMap, VecDeque};
use std::collections::VecDeque;
use oxc_allocator::Allocator;
use rustc_hash::FxHashMap;
use self::command::{Command, Indent, Mode};
use crate::{
@ -27,7 +28,7 @@ pub struct Printer<'a> {
cmds: Vec<Command<'a>>,
line_suffix: Vec<Command<'a>>,
group_mode_map: HashMap<GroupId, Mode>,
group_mode_map: FxHashMap<GroupId, Mode>,
// states
new_line: &'static str,
@ -59,7 +60,7 @@ impl<'a> Printer<'a> {
pos: 0,
cmds,
line_suffix: vec![],
group_mode_map: HashMap::new(),
group_mode_map: FxHashMap::default(),
new_line: options.end_of_line.as_str(),
allocator,
}

View file

@ -1,5 +1,5 @@
#![allow(clippy::print_stdout)]
use std::{collections::HashMap, env, path::Path, sync::Arc};
use std::{env, path::Path, sync::Arc};
use itertools::Itertools;
use oxc_allocator::Allocator;
@ -13,6 +13,7 @@ use oxc_cfg::{
use oxc_parser::Parser;
use oxc_semantic::{dot::DebugDot, SemanticBuilder};
use oxc_span::SourceType;
use rustc_hash::FxHashMap;
// Instruction:
// 1. create a `test.js`,
@ -76,7 +77,7 @@ fn main() -> std::io::Result<()> {
.cfg()
.expect("we set semantic to build the control flow (`with_cfg`) for us so it should always be `Some`");
let mut ast_nodes_by_block = HashMap::<_, Vec<_>>::new();
let mut ast_nodes_by_block = FxHashMap::<_, Vec<_>>::default();
for node in semantic.semantic.nodes() {
let block = node.cfg_id();
let block_ix = cfg.graph.node_weight(block).unwrap();