1. 62
  1. 30

    For fun, here’s a Common Lisp function to do the same thing:

    (declaim (ftype (function (string string) double-float) img-diff))
    (defun img-diff (first-file-name second-file-name)
      (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
      (let ((im1 (png:decode-file first-file-name))
            (im2 (png:decode-file second-file-name)))
        (declare ((SIMPLE-ARRAY (UNSIGNED-BYTE 8) (* * *)) im1 im2))
        (/ (loop
              for i fixnum below (array-total-size im1)
              summing (abs (- (row-major-aref im1 i)
                              (row-major-aref im2 i))) fixnum)
           (* (ash 1 (png:image-bit-depth im1)) (array-total-size im1))
           1.0))) ; Convert rational to float
    (img-diff "file1.png" "file2.png"))

    Here’s a quick comparison running against two 2560x2560 PNG files I had lying around…

    CL: 0.490 seconds

    Rust (with –release): 1.07 seconds

    Go: 1.85 seconds

    Python: 2.30 seconds

    And this is on an 3.6 ghz 2011 iMac with 16gb ram, running Debian testing

    > python --version
    Python 2.7.16
    > go version
    go version go1.11.6 linux/amd64
    > rustc --version
    rustc 1.34.2
    > sbcl --version

    Edit: Added rust timing, software versions, and computer info.

    1. 3

      Which PNG library are you using? Neither cl-png nor read-png seems to expose DECODE-FILE or IMAGE-BIT-DEPTH, unless I miss something.

      1. 1

        I think Quicklisp pulls this cl-png from GitHub, which has decode-file and image-bit-depth.

        Technically on this machine I’m using a fork of a fork, though.

      2. 3

        Nice, beating the rust one.

      3. 9

        I think this was a pretty good comparison; often these articles end up with “yeah, but you didn’t write it idiomatically in anything so the whole thing is flawed”. All of these looked pretty good to me.

        I agree with Rust being fun, but sometimes it feels like the fun is in doing mental gymnastics rather than actually getting things working. It’s unclear to me whether that results in healthier code or not. I’d appreciate any replies from Rustaceans on that one :)

        One thing that the article touches on is async/await. Just today I was fiddling with running a container on k8s and I needed it to listen on two sockets: one HTTP for health checks, one something else for GRPC. I knew how to do this in Go just fine: use go httpServer(); go grpcServer(), and all is done. Not having this in Rust made this seemingly trivial task require a lot of hoops. Rust dearly needs async/await IMHO. I hope they can land it soon.

        1. 11

          I agree with Rust being fun, but sometimes it feels like the fun is in doing mental gymnastics

          The gymnastics pay off in refactoring. That’s hard to show in blogpost-sized codebases, but the rigidness of Rust helps to keep invariants together in larger codebases (including traditionally difficult things, such as thread safety).

          I find this leads to qualitatively different kind of bugs. My Rust bugs are, for lack of a better term, domain-specific. When something goes wrong, it’s because I haven’t finished implementing it, or it works, but not according to the spec. While in Go, C and JS I also get tons of boring language-level bugs, such as “nil/null/undefined is not a function”.

          1. 2

            That sounds a great deal like the way people describe Haskell, fwiw.

          2. 4

            I tend to think of async as mostly being a heavy-handed latency-throughput tradeoff favoring latency that is really only worth the severe ergonomic hit if you’re building a load balancer. But pretty much everyone else in the rust ecosystem tends to disagree with me :P

            I agree with Rust I spend more time indulging in complexity. It’s sometimes a problem in the ecosystem, where it’s not uncommon for projects to require pulling in hundreds of dependencies for a relatively simple thing. I really appreciate how Go has a strong emphasis on avoiding dependency, although I really dislike why it got there due to tooling indifference from the controlling entity. I really hate how I type thousands of lines of Go to get anything written with medium or higher complexity. In Rust I can sometimes type extremely little to get a lot done, without a ton of soundness gaps. But it took me a while to get there. I think as more people get a few years of Rust experience there will be more emphasis on tightening things down. Python and Go have had a few nice cultural movements emphasizing parsimony, and I don’t think Rust has gotten there yet.

            I’m a human who appreciates complexity (inherent complexity, I say arrogantly to myself as I busily type out a lock-free storage engine for dubious performance benefits lol) and I do better with languages that let me indulge this aspect of myself. I use Python for prototyping things, Go for bit shovels and simple services, and Rust for performance intensive systems. Rust is the one that makes me feel the best to write. If you love your tools, you’ll be more productive.

            1. 3

              I agree with Rust being fun, but sometimes it feels like the fun is in doing mental gymnastics rather than actually getting things working. It’s unclear to me whether that results in healthier code or not. I’d appreciate any replies from Rustaceans on that one :)

              Pretty much agree, and I had a similar experience with Haskell. I think it does result in healthier code, but not by much. Sometimes (not very often) you will want to optimize on code health though, and in those cases these tools are a good match.

              I’m drifting towards Nim myself. It seems to be a better compromise between pragmatic safety features, performance, stability and code clarity.

            2. 8

              This is an interesting comparison. A lot of the heavy lifting of the Python implementation actually happens in C:


              I guess the Python comparison is fair, since most Python programmers use C-based modules. However, it would be nice to also see the performance if it was completely written in Python, since that is the performance that Python programmers have to deal with if there is not a C/C++/Rust/Cython-based module available and if they cannot implement that themselves.

              It’s also surprising that the Python version is as slow as it is, given that it is largely C-based and the C code should be the same ballpark as Rust. I think it’s worth profiling to see where the bottleneck is.

              edit: It seems that the ImageStat has parts in pure Python. This is probably one of the bottlenecks.

              1. 3

                It would be also interesting to see how much faster it would be under Pypy.

              2. 8

                Here’s a q implementation of this program:

                getratio:{avg abs x-y}

                This runs cold about 10x faster than the rust example on my Macbook for the 2,000x2,000 images and 10,000x10,000 images.

                1. 4

                  (nitpick: you have one problem, and you implemented it in different languages. the way you phrased it makes it sound like one program was written using the combination of the three languages you mentioned using some kind of foreign function interface)

                  1. 5

                    Maybe I’ve been reading a lot of these comparison articles recently, but I didn’t get that impression at all.

                    1. 2

                      Oh, I thought it was going to be a polyglot!

                    2. 3

                      I find articles like these fascinating, thanks.

                      If I was going to give one improvement, I’d say the alt text for the header image should say “mascots” instead of “logos”. Tiny detail, but I got the wrong impression from the alt.

                        1. 3

                          A great site with hundreds of these is RosettaCode: https://www.rosettacode.org/

                          1. 2

                            Can someone confirm that except Exception: is bad practice for the use cases given in the article?

                            1. 2

                              It’s bad if you really need what that construct does.

                              On the other hand, if you’re just being lazy and you could find out the exact exception(s) that you want to handle, you should do that instead.