I have noticed that many functions that are meant to operate on string are slower compared to similar functions that operate on lists.

One example: `Counts` versus `LetterCounts` (and notice that the version with `Counts` has to do two more tasks than version with `LetterCounts` - it has to do `ToCharacterCode` and then `KeyMap` with `FromCharacterCode` to transform the output to the `LetterCounts` version)

    SeedRandom[1]
    str = RandomInteger[{1, 26}, 300] /. 
        Thread[Range[26] -> CharacterRange["A", "Z"]] // StringJoin;
    
    LetterCounts[str, 2] // RepeatedTiming
    
    KeyMap[FromCharacterCode, 
      Sort[Counts[Partition[ToCharacterCode[str], 2, 1]], 
       Greater]] // RepeatedTiming

    (*{0.00426, <|"TT" -> 3, "IF" -> 3, "EP" -> 3, ... , "HA" -> 1, "AH" -> 1, "FA" -> 1|>}*)
    (*{0.000830, <|"TT" -> 3, "IF" -> 3, "EP" -> 3, ... , "HA" -> 1, "AH" -> 1, "FA" -> 1|>}*)

The speed difference is significant!

Something is rotten in the state of <strike>Denmark</strike> Wolfram.