Live data from Hacker News

Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

benhoyt.com

61–70 of 234 posts

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#62
Some of these efficiency gains are remarkable. Most of the code I write is just the quickest way to write a mundane function. Really curious if you went into a huge tech co and devoted ~10% of SWE time solely to optimization of code base what the energy savings would be.

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#64
post #29
post #24

I like the OCaml version. It's both very easy to read and comes out really well in the benchmark. I also looked at the "simple" Zig version, which came out really well in the benchmark, and to me it didn't look simple at all. It seems you need to make a lot of low level details explicit. But IMHO AWK takes the crown here. :)

> But IMHO AWK takes the crown here. :) Agreed! AWK is still the king of this stuff. For tasks like this, I kinda think AWK is nigh-unbeatable: so simple to write, so obvious what's going on (even if you've never seen any AWK program before, you're probably going to be able to figure out what's going on there), and decently performant. AWK is the bee's knees.

On the other hand, I'm rather surprised that the Go version is almost as long and opaque as C.

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#65
Recently there was a contest to optimize the performance of a similar program[0] and a Zoom discussion of the optimizations[1].

The programs are not comparable in the the following ways:

- Case: TFA requires (at least) ASCII lowercasing but the contest problem required no lowercasing.

- Ordering: TFA does not require sorting, but the contest problem required sorting.

- Memory: TFA imposes a requirement phrased as "don't read the whole file into memory" and this sounds like it's a resource-saving constraint, but it's usually a constraint that requires the program to spend additional resources. You could just mmap the file and store pointers into the mapped region. It costs extra to do copies instead of no copies.

- Text: TFA is unclear on what assumptions may be made about the lengths of words. For the contest problem, the Hungarian wikipedia input's longest word is around 80k.

- Safe, Hashing, Stdlib: TFA imposes some restrictions on what constructs may be used that are not imposed in the contest problem.

For the contest version of this problem, it seems like you can tokenize, hash, and count strings at around 1GB/s. Adapting a solution to solve TFA's problem (but not to conform to its Safe/Hashing/Stdlib requirements) would probably not carry too large of a penalty, since it's like 3 instructions to ASCII-lowercase 32 bytes and 1 string copy per unique string should take negligible time compared to the hash table lookups. So there is some room for the optimized solutions to go a little faster, if more optimizations are permitted.

[0]: https://easyperf.net/blog/2022/05/28/Performance-analysis-an...

[1]: https://www.youtube.com/watch?v=R_yX0XjdSBY

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#66
post #44

Earlier quoted context omitted.

The problem specified declares the words we're counting are ASCII: > ASCII: it’s okay to only support ASCII for the whitespace handling and lowercase operation UTF-8 (quite deliberately) is a superset of ASCII. So a UTF-8 solution is correct for ASCII, but a bytes-as-ASCII solution works fine in Rust if you only need ASCII. This is why Rust provides ASCII variants of a lot of functions on strings, and the same functi…

>> ASCII: it’s okay to only support ASCII for the whitespace handling and lowercase operation That's a somewhat specific list -- at least I didn't read that as a general "the program can assume that the input is only ASCII". But then, the author seems to have accepted solutions that crash on non-UTF8 sequences and ones that byte-compare them, so probably either behavior was meant to be fine. I just don't get that fro…

> That's a somewhat specific list -- at least I didn't read that as a general "the program can assume that the input is only ASCII".

I don't think it assumes the input is only ASCII. If the problem is "given UTF-8 text, split on ASCII whitespace and convert ASCII uppercase letters to lowercase," you can do that correctly (and produce correct UTF-8 output) without really being UTF-8 aware. For why, see here: https://en.wikipedia.org/wiki/UTF-8#Encoding

> But then, the author seems to have accepted solutions that crash on non-UTF8 sequences and ones that byte-compare them, so probably either behavior was meant to be fine. I just don't get that from this rule.

That's a separate concern right? The rules are only about the behavior when the program is given UTF-8 input.

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#67
post #59
post #32

Earlier quoted context omitted.

Can we move past the whole "it is not really python to use libraries written in C", especially when talking pure stdlib python? Python is basically a DSL for C extensions. That is the whole point. It would be like criticizing any compiled language for essentially being a DSL for machine code, and not "Real Instructions". Python's ability to interop with pre-built, optimized libraries with a lightweight interface is a…

I am not criticizing Python. It does well enough what it was made to do. It just make no sense to call that particular example a "language performance comparison". It is anything but.

But you are still wrong. As mentioned, Dicts are incredibly efficient data structures in Python (because they underpin everything) and the Counter class is pure python. That's 100% pure python. Saying dicts "don't count" because they are implemented in C would disqualify the entire language of CPython, as virtually everything under the hood is a PyObject struct pointer. It just so happens that "counting abstract objects" is the class of problem* CPython does basically all the time in normal VM execution anyways, so this task is easy and fast.

* looking up a reference of an attribute with a string key underpins the meat and potatoes of python's execution model

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#68
post #46
post #40

Earlier quoted context omitted.

Indeed. The original title is: > Performance comparison: counting words in Python, Go, C++, C, AWK, Forth, and Rust. Which means the submitter actively chose Go to be removed from the title. Judging from their post history they are aligned with Rust community.

The original title is 2 characters over the HN title length limit. I suspect they dropped "Go" first, because it's the language which has a 2-letter name, and they they editorialised it further to hint that there are more languages.

Leaving Awk over the vastly more popular Go, hints a more probable and frequent behaviour: bias.

Removing any of them would suffice.

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#69
How about extending this to comparing programming language efficiency from a developer's standpoint, looking at bytes of code, versus the execution time?

I took the source code file size from the repository, and the runtime from the blog post.

Then I made an arbitrary overall "PAIN SCORE" (lower is better) by multiplying code size * runtime. I suggest this is a worthwhile metric simply because lower is better on both axes, but of course, in the "real world" there will be different economic costs to CPU time and developer time depending on the use case. Here's the sorted results, from least "pain" to most:

    LANGUAGE   FILENAME        CODE SIZE      RUNTIME     PAIN SCORE (LOWER IS BETTER)
    Shell      optimized.sh      75 bytes      1.83 s      137.25
    Crystal    simple.cr        240 bytes      1.29 s      309.6
    Nim        simple.nim       424 bytes      0.77 s      326.48
    Python     simple.py        208 bytes      2.21 s      459.68
    Ruby       simple.rb        175 bytes      3.17 s      554.75
    Go         optimized.go    1514 bytes      0.40 s      605.6
    Python     optimized.py     464 bytes      1.33 s      617.12
    Zig        optimized.zig   2688 bytes      0.24 s      645.12
    Go         simple.go        688 bytes      1.12 s      770.56
    Zig        simple.zig      1394 bytes      0.55 s      766.7
    Nim        optimized.nim   1683 bytes      0.49 s      824.67
    Shell      simple.sh         60 bytes     14.81 s      888.6
    Ruby       optimized.rb     401 bytes      2.47 s      990.47
    JavaScript simple.js        532 bytes      1.88 s     1000.16
    C          optimized.c     4360 bytes      0.23 s     1002.80
    Rust       optimized.rs    3065 bytes      0.43 s     1317.95
    Swift      simple.swift     317 bytes      4.23 s     1340.91
    JavaScript optimized.js    1501 bytes      1.10 s     1651.1
    C          simple.c        2735 bytes      0.96 s     2625.6
    Rust       simple.rs       2239 bytes      1.38 s     3089.82
Sorting only by code size, the most concise implementations are: Shell, Ruby, Python, Crystal. Nobody was aiming to play code golf (i.e. minimize source code size), so these are fairly straightforward, idiomatic, readable implementations.

I am definitely a Crystal fan, in fact this afternoon I'm continuing to implement suggestions from my recent Show HN https://news.ycombinator.com/item?id=32081943 comments. :)

Re: Performance comparison: counting words in Python, C/C++, Awk, Rust, and more

#70
post #68
post #46

Earlier quoted context omitted.

The original title is 2 characters over the HN title length limit. I suspect they dropped "Go" first, because it's the language which has a 2-letter name, and they they editorialised it further to hint that there are more languages.

Leaving Awk over the vastly more popular Go, hints a more probable and frequent behaviour: bias. Removing any of them would suffice.

The author maintains an AWK implementation:

https://github.com/benhoyt/goawk

Post reply on HN