Repository: swc-project/swc
File: crates/swc_ecma_minifier/src/util/base54.rs (Rust)

Code before:
                        table[i] = b - b'A' + 26;
                    }
                    b'0'..=b'9' => {
                        table[i] = b - b'0' + 52;
                    }
                    b'$' => {
                        table[i] = 62;
                    }
                    b'_' => {
                        table[i] = 63;
                    }
                    _ => {
                        table[i] = 64;
                    }
                }

                i += 1;
                if i == 256 {
                    break;
                }
            }
            table
        };

        for &c in s.as_bytes() {
            self.0[SCAN_INDEX_TABLE[c as usize] as usize] += delta;
        }
    }

    pub fn compute(p: &Program, preserved: &FxHashSet<Id>, unresolved_ctxt: SyntaxContext) -> Self {
        let (mut a, b) = GLOBALS.with(|globals| {
            par_core::join(
                || {
                    GLOBALS.set(globals, || {
                        let cm = Lrc::new(DummySourceMap);
                        let mut freq = Self::default();

                        {
                            let mut emitter = Emitter {
                                cfg: swc_ecma_codegen::Config::default()
                                    .with_target(EsVersion::latest())
                                    .with_minify(true),
                                cm,
                                comments: None,
                                wr: &mut freq,
                            };

                            emitter.emit_program(p).unwrap();
                        }

                        freq

Review comment (performance):
Since you're having to look up every character in a 256-entry table, wouldn't it be cheaper to make the CharFreq working structure be a 256-entry array, add every character to its own bucket directly, and then extract the 64 relevant entries at the end? It avoids a lookup during the critical path in exchange for a one-time hit of 64 lookups and writes at the end, but it would use more cache to store the table during processing.

Alternatively, all valid values are in the range 0x24-0x7a (inclusive), so if you are willing to pay the three instructions per iteration (unsigned subtract 0x24, check < 0x56, branch) to bounds check the value you only need an 86-entry working array.

Code after:
                        let cm = Lrc::new(DummySourceMap);
                        let mut freq = Self::default();

                        {
                            let mut emitter = Emitter {
                                cfg: swc_ecma_codegen::Config::default()
                                    .with_target(EsVersion::latest())
                                    .with_minify(true),
                                cm,
                                comments: None,
                                wr: &mut freq,
                            };

                            emitter.emit_program(p).unwrap();
                        }

                        freq
                    })
                },
                || {
                    GLOBALS.set(globals, || {
                        let mut visitor = CharFreqAnalyzer {
                            freq: Default::default(),
                            preserved,
                            unresolved_ctxt,
                        };

                        // Subtract
                        p.visit_with(&mut visitor);

                        visitor.freq
                    })
                },
            )
        });

        a += b;

        a
    }

    pub fn compile(self) -> Base54Chars {
        static BASE54_DEFAULT_CHARS: &[u8; 64] =
            b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_";

        let mut arr = BASE54_DEFAULT_CHARS
            .iter()
            .copied()
            .map(|c| (self.0[c as usize], c))
            .collect::<Vec<_>>();