TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

One Program Written in Python, Go, and Rust

215 pointsby giladalmost 6 years ago

24 comments

yaluealmost 6 years ago
The &quot;image&quot; interface is one of my favorite parts of Go&#x27;s standard library, and I think it&#x27;s one of the best showcases of Go&#x27;s &quot;interface&quot; feature. Want to save something as an image? Just implement three functions, two of which are trivial boilerplate and the third of which is just &quot;return the color at this coordinate&quot;. You don&#x27;t even need to manage the buffer of image data yourself this way. For example, if you want an image full of noise, just make your &quot;get color&quot; function return a random value every time it&#x27;s called. I&#x27;ve used this myself for things like simple fractals.<p>And,on top of all that, once you&#x27;ve written code satisfying the image interface, the standard library even includes functions for saving your image as one of several possible image formats. And, due to the fact that the interface itself is specified by the standard library, virtually all third-party image encoding or decoding libraries for Go use it, too. So, every image reader or writer I&#x27;ve seen for Go, even third-party ones, can be drop-in replacements for one another.<p>Anyway, it&#x27;s not Go&#x27;s standard use case, but as someone who loves fractals and fiddles with images all the time it&#x27;s one of my favorite parts of the language.
评论 #20334472 未加载
评论 #20335879 未加载
评论 #20335523 未加载
deathanatosalmost 6 years ago
&gt; <i>Rust slaps you and demands that you clean up after yourself. This stung at first, since I’m spoiled and usually have my languages pick up after me, moreso even than moving from a dynamic to a statically typed language.</i><p>And yet, in the author&#x27;s example, all memory handling in Rust was completely automatic. And that includes, AFAICT, no Box&#x27;d pointers¹, no ref-counting, and certainly no raw&#x2F;unsafe pointers.<p>IME, this seems to be a common response among people coming from GC&#x27;d languages; I think the expectation is that they&#x27;re going to be doing C-style memory management (manually alloc&#x2F;free pairs), when the truth is that 99.9% of allocations will just happen automatically and invisible thanks to RAII.<p>In the end, I really think it&#x27;s <i>resources</i>, of which memory is just one type of, that matter. Python doesn&#x27;t do anything for resources, and you have to know you&#x27;re allocating something (e.g., files, locks, connections, connection leases, etc.) that will require a manual .close() or with statement s.t. it gets dealloc&#x2F;cleaned&#x2F;released; RAII will handle this just like memory, and automatically handle it, and because of that, I find myself doing less <i>resource</i> management in Rust than I do in Python.<p>¹There are conditions in which I would argue that some uses of Box are &quot;automatic&quot;, depending on the reason it&#x27;s pulled in. E.g., I&#x27;ve used it to reduce the size of an enum in the common case, but still allow it to store a heavy structure in the rare case. The handling of the Box itself is essentially still automatic.
评论 #20337016 未加载
评论 #20338812 未加载
chriswittsalmost 6 years ago
You can use Hyperfine [1] instead of time for a nicer CLI benchmark.<p>I&#x27;d also be curious to know is pillow-simd [2] gets the Python performance closer to Go&#x2F;Rust, and if using Rayon [3] and changing your .iter()&#x27;s in your Rust code to .par_iter()&#x27;s will yield an improvement there.<p>[1] <a href="https:&#x2F;&#x2F;github.com&#x2F;sharkdp&#x2F;hyperfine" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;sharkdp&#x2F;hyperfine</a><p>[2] <a href="https:&#x2F;&#x2F;github.com&#x2F;uploadcare&#x2F;pillow-simd" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;uploadcare&#x2F;pillow-simd</a><p>[3] <a href="https:&#x2F;&#x2F;github.com&#x2F;rayon-rs&#x2F;rayon" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;rayon-rs&#x2F;rayon</a>
kwhitefootalmost 6 years ago
I don&#x27;t know much Rust (essentially none) but the sample doesn&#x27;t seem to me to warrant the &#x27;murkier&#x27; comment. It took me a few moments to realize that the loop variable was not produced from<p><pre><code> image1 </code></pre> but instead from<p><pre><code> image1.raw_pixels().iter().zip(image2.raw_pixels().iter() </code></pre> I would have created a local for that before the loop for clarity. And I would not have declared the ratio variable, that is surely unidiomatic as the last expression provides the result.<p>But apart from those stylistic quibbles it seems a model of clarity, the opposite of murky, whereas the Python version simply glues together opaque library calls.
评论 #20333998 未加载
majewskyalmost 6 years ago
&gt; No Optional Arguments: Go only has variadic functions which are similar to Python’s keyword arguments, but less useful, since the arguments need to be of the same type.<p>What I like to do is to make the last arg take a struct such that you indirectly have named (and optional) arguments:<p><pre><code> type DownloadOpts struct { UserAgent string TimeoutSeconds int &#x2F;&#x2F;... } func Download(url string, opts DownloadOpts) (io.Reader, error) { ... } &#x2F;&#x2F;use like: contents, err := Download(&quot;https:&#x2F;&#x2F;news.ycombinator.com&quot;, DownloadOpts { UserAgent: &quot;example-snippet&#x2F;1.0&quot;, }) </code></pre> &gt; Never needing to pause for garbage collection could also be a factor [in Rust&#x27;s greater speed compared to Go].<p>Would be nice if the author had checked<p><pre><code> var ms runtime.MemStats runtime.ReadMemStats(&amp;ms) print(ms.NumGC) </code></pre> to see if there was actually any garbage collection performed.
评论 #20334739 未加载
评论 #20336716 未加载
vorachosealmost 6 years ago
Nice writeup overall. Tiny nitpick however - in your Rust generation of the diff image, you go by column instead of by row, making it quite a lot slower than it should be (probably because of cache locality, I ain&#x27;t no expert). Switching from<p><pre><code> for x in 0..w { for y in 0..h { let mut rgba = [0; 4]; [...] </code></pre> to<p><pre><code> let mut rgba = [0; 4]; for y in 0..h { for x in 0..w { [...] </code></pre> cuts down runtime by between 35 and 50% on my side.<p>EDIT : moving the call to get_pixel outside the inner loop take off another ~10%, bringing it to sub 0.145 from ~0.290.<p><pre><code> let mut rgba = [0; 4]; for y in 0..h { for x in 0..w { let pix1 = image1.get_pixel(x, y); let pix2 = image2.get_pixel(x, y); for c in 0..4 { rgba[c] = abs_diff( pix1.data[c], pix2.data[c], ); } let new_pix = image::Pixel::from_slice(&amp;rgba); diff.put_pixel(x, y, *new_pix); } }</code></pre>
评论 #20339842 未加载
valzamalmost 6 years ago
To anyone coming from Python and wanting to try out Rust: If you haven&#x27;t worked with C before I highly recommend spending a weekend hacking something in C before starting Rust. It really made me appreciate the borrow checker and super restrictive type system.
评论 #20334788 未加载
评论 #20335423 未加载
kerkeslageralmost 6 years ago
&gt; I’ve used statically typed languages in the past, but my programming for the past few years has mostly been in Python. The experience was somewhat annoying at first, it felt as though it was simply slowing me down and forcing me to be excessively explicit whereas Python would just let me do what I wanted, even if I got it wrong occasionally. Somewhat like giving instructions to someone who always stops you to ask you to clarify what you mean, versus someone who always nods along and seems to understand you, though you’re not always sure they’re absorbing everything. It will decrease the amount of type-related bugs for free, but I’ve found that I still need to spend nearly the same amount of time writing tests.<p>This is one of the biggest confusion points when comparing Python and Go.<p>Go&#x27;s static type system is weak, the weakest of any widely-used static-typed languages except C&#x2F;C++. Lack of interfaces means you have to cast in&#x2F;out of object, so you&#x27;re constantly sidestepping the supposed benefits of the static type system. Type errors will, of course, be caught at runtime, but that&#x27;s completely identical to Python. You can, of course, use go generate to avoid a lot of the object casting, but then you get all the wonderful problems of macros.<p>Python doesn&#x27;t enforce types at compile time (because there isn&#x27;t a compile time) but it does enforce types--it <i>does not</i> &quot;nod and seem to understand you&quot;, if, for example you decide to do `42 + &quot;Hello, world&quot;`. It very much tells you that this is not valid.<p>This confusion comes from the common misconception that static types = strong types, and dynamic types = weak types. The truth is that there are a good number of static languages (C being the most obvious) which have much weaker type-checking that some dynamic languages like Python.
评论 #20338868 未加载
room271almost 6 years ago
Thanks, this is a pretty great write up. It&#x27;s not a langauge-war, the discussion is knowledgeable and pragmatic, and I like that you recognise that a language suitable for work might be different from one you&#x27;d use on a side project.
Thorentisalmost 6 years ago
This is a pretty silly comparison to be honest. Why would you choose an example for which a library exists in Python that can already do most of the work for you? Sure, it might be the most &quot;Pythonic&quot; thing to do (importing a library) but for an actual language comparison, you should be comparing things 1 to 1.<p>EDIT: For example, implementing a merge sort (without using any .sort() functions) would be interesting to compare between the three languages. Though to be honest, I wouldn&#x27;t expect major differences aside from basic syntax.
评论 #20333136 未加载
评论 #20334312 未加载
评论 #20334950 未加载
epxalmost 6 years ago
That&#x27;s ironic, I also wrote a similar app for image diffing and it is also written in Python, Go, Rust... and Node. It also spits a number as diff metric, but it is an integer. Code is at <a href="https:&#x2F;&#x2F;github.com&#x2F;elvis-epx&#x2F;pictdiff" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;elvis-epx&#x2F;pictdiff</a>
leshowalmost 6 years ago
&gt; There was one place where because of the type system, the implementation of the imaging library I was using would have led to an uncomfortable amount of code repetition.<p>If you truly don&#x27;t care about handling the different variants and want them all to execute the same code you could have just as easily done:<p><pre><code> let w = image1.width(); let h = image1.height(); let mut diff = image::DynamicImage::new_rgb8(w, h); </code></pre> If you don&#x27;t care about image1.color() &#x27;s variants, why even have the match at all? I guess I don&#x27;t understand why it &#x27;rubbed you the wrong way&#x27;. You don&#x27;t have to match anything if you don&#x27;t want to, and if you do want to match variants, you have all the tools necessary to reduce duplication &amp; only handle the variants you want to.
评论 #20335818 未加载
WoodenChairalmost 6 years ago
I think the author does a good job comparing the three languages here. That said, comparing just one program can provide some insights, but not enough to know if the language is a good choice for larger projects. I have rewritten a whole book of programs, first in Swift, then in Python, and soon in Java (<a href="https:&#x2F;&#x2F;classicproblems.com" rel="nofollow">https:&#x2F;&#x2F;classicproblems.com</a>). And if I didn&#x27;t know the three languages well, I probably would have to say that even writing that book has not provided me enough insight to say &quot;this language is good for my next 100,000 line project.&quot;
icebrainingalmost 6 years ago
I wish these comparisons that involve typing would include Mypy. Python is now effectively an optionally-typed language, not just a dynamically-typed language, and in my experience it solves a good deal of the problems, while not getting in the way.
truth_seekeralmost 6 years ago
I don&#x27;t understand the obsession of people trying to compare statically typed and dynamically typed language performance.<p>Besides that, cpython is not the only Python runtime or interpreter. The author should have tried pypy runtime which has mature JIT.
评论 #20334953 未加载
评论 #20335535 未加载
gigamaalmost 6 years ago
Interesting comparison, coming from a python background this was actually helpful info!<p>Just odd to read noun-verb combinations like:<p>&gt; &quot;If you’re comfortable with Python, you can go through the Tour of Go in a day or two...&quot;<p>&gt; &quot;I would go as far as saying that Go’s strength is that it’s not clever.&quot;<p>&gt; &quot;I decided to give an honest go at learning Rust.&quot;<p>&gt; &quot;Go propagates errors by returning tuples: value, error from functions wherever something may go wrong.&quot;
dev_dullalmost 6 years ago
&gt; <i>Its minimalism and lack of freedom are constraining as a single developer just trying to materialize an idea. However, this weakness becomes its strength when the project scales to dozens or hundreds of developers</i><p>I actually appreciate this less for other developers, but even for my own code. Let’s be honest, It’s difficult to come back to a code base a year or two after it went into maintenance even if you were the writer.
zevebalmost 6 years ago
I love that this Lisp version from a comment at lobste.rs (<a href="https:&#x2F;&#x2F;lobste.rs&#x2F;s&#x2F;xz5l8t&#x2F;one_program_written_python_go_rust#c_lxm4vf" rel="nofollow">https:&#x2F;&#x2F;lobste.rs&#x2F;s&#x2F;xz5l8t&#x2F;one_program_written_python_go_rus...</a>) runs in less than half the time of Rust, and is more readable to boot:<p><pre><code> (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)) (&#x2F; (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 &quot;file1.png&quot; &quot;file2.png&quot;)</code></pre>
评论 #20339438 未加载
评论 #20341265 未加载
Touchealmost 6 years ago
Great post, I like comparisons like this because its about implementing something practical. I like to see how the languages do in the real world.
todd8almost 6 years ago
This is great. I’ve been thinking about doing this very thing in a different problem domain.
DannyB2almost 6 years ago
If there were one perfect language, for all porpoises, we would be using it already.<p>Also a genuine dollars and cents factor: Development Time.<p>Do not just measure for cpu cycles and bytes. Development time is money, and that is a measurable factor too that must be considered as a language tradeoff.
yydcoolalmost 6 years ago
Good to read! let me know that pillow has lot of space for optimizing.
wiineethalmost 6 years ago
Should have also considered C++
s_k_almost 6 years ago
you could&#x27;ve given at least one JVM language, maan o_0
评论 #20358621 未加载