List.count() Vs Counter() Performance
Solution 1:
Counter is faster in theory, but has higher fixed overhead, especially compared to str.count, which can scan the underlying C array with direct memory comparisons, where list.count has to do rich comparisons for each element; converting moves to a list of single characters nearly triples the time for foo1 in local tests, from 448 ns to 1.3 μs (while foo2 actually gets a tiny bit faster, dropping from 5.6 μs to 5.48 μs).
Other problems:
- Importing an already imported module uses the cached import, but there is a surprising amount of overhead involved in even a cached import (the loading machinery has a lot of stuff to check to make sure it's okay to do so); in local tests, moving
from collections import Counterto the top level reduced the runtime offoo2by 1.6 μs (5.6 μs with single global import, 7.2 μs with local per-call import). This will vary a lot by environment; on another machine (with less stuff installed in both user and system site-packages), the overhead was only 0.75 μs. Regardless, it's a significant, avoidable disadvantage forfoo2. Counteron modern Python uses a C accelerator to speed up counting, but the accelerator only provides a benefit when the iterable is long enough. If you use thelistform ofmoves, but multiply it by 100 to make a longer sequence, the difference drops, relatively speaking (to 106 µs forfoo1vs. 140 µs forfoo2)- You're just not counting very many things; when there are only four things you care about, paying
O(n)four times can easily beat payingO(n)once if the former case has lower constant multipliers (which aren't included in big-O notation) than the latter.CounterremainsO(n)for any number of unique things being counted; calling.countisO(n)per call, but if you need to know the count of every unique thing in the input, for inputs that are mostly unique, individual.countcalls for each will be asymptoticallyO(n²). - The
.countapproach is short-circuiting in your specific case, so it isn't even doingO(n)work four times, just twice; theUandDcounts don't match, so it never countsLandRat all.Counterdoesn't get meaningfully slower if it can't short-circuit (all the cost is paid in the single counting pass), but yourfoo1, in the same benchmark I used from point #2 (longer input, inlistform), goes from 106 µs to 185 µs if I just add a singleDto the end of the (pre-multiplication)moves(making theUandDcounts the same, and requiring two morecountcalls);foo2only goes up to 143 µs (from 140 µs), presumably becausemovesactually got longer (adding theDbefore multiplying by 100 meant it went from 2900 elements to count to 3000).
Basically, you had some minor implementation weaknesses, but mostly, you happened to choose a use case that gave all the advantage to .count, none to Counter. If your inputs are always str, and you're only counting them a small, fixed number of times, then sure, repeated calls to count are generally going to win. But for arbitrary input types (especially iterators, where count is impossible, both because it doesn't exist, and because you can only iterate it once), especially larger ones, with more unique things to count, where consistent performance counts (so relying on short-circuiting to reduce the number of count calls isn't acceptable), Counter will win.
Post a Comment for "List.count() Vs Counter() Performance"