<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Rust on FastDataScience.eu</title>
    <link>https://fastdatascience.eu/tags/rust/</link>
    <description>FastDataScience.eu (Rust)</description>
    <generator>Hugo -- gohugo.io</generator>
    <copyright>en-us</copyright>
    <lastBuildDate>Tue, 15 Oct 2024 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://fastdatascience.eu/tags/rust/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Comparing speed of some fast languages</title>
      <link>https://fastdatascience.eu/post/2024-10-18-zig_speed/</link>
      <pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://fastdatascience.eu/post/2024-10-18-zig_speed/</guid>
      <description>&lt;p&gt;To get more familiar with Rust, I&amp;rsquo;ve lately been revisting last year&amp;rsquo;s
Advent of Code problems, which I did in Go last December. My
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/main.go&#34;&gt;Go solution&lt;/a&gt;
solution for &lt;a href=&#34;https://adventofcode.com/2023/day/5&#34;&gt;Day 5&lt;/a&gt; uses brute-force
and is not very clever, but runs fast enough in Go (under 6 minutes). The
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/main.rs&#34;&gt;Rust equivalent&lt;/a&gt;
runs in 1/3 less time, and I was wondering how other languages would fare.
The results might surprise you.&lt;/p&gt;
&lt;p&gt;I ended up writing the solution in
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/main.rs&#34;&gt;Rust&lt;/a&gt;,
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/zig/src/main.zig&#34;&gt;Zig&lt;/a&gt;, and
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/day05.c&#34;&gt;C&lt;/a&gt;,
in addition to the original
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/main.go&#34;&gt;Go solution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem (at least my naive solution) is good for a language benchmark
because it involves lots of repeated transformations through a series
of logic steps, with math, logic, and branching/looping. It took 5:40
minutes in Go, and Rust brought that down to 3:45 (when compiled with
the &amp;ndash;release flag). The results for the four languages are interesting
(times in minutes):&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://fastdatascience.eu/images/blog/2024-10-18-times.png&#34; alt=&#34;Times&#34;&gt;&lt;/p&gt;
&lt;p&gt;Go takes 50% longer than Rust, better than I expected. As you&amp;rsquo;d expect, C is
faster than Rust, and at 2:40 by quite a margin (compiled using gcc with -O3
flag for optimizations, and clang did even better at 2:21). And the
big surprise was Zig, which ran in 1:51, using its ReleaseFast optimization
flag(i.e., &lt;code&gt;zig build -Doptimize=ReleaseFast&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&#34;the-cost-of-speed-lines-of-code&#34; &gt;The cost of speed: lines of code?
&lt;span&gt;
    &lt;a href=&#34;#the-cost-of-speed-lines-of-code&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;Another comparison is lines of code. You&amp;rsquo;d expect Rust to require fewer lines
of code because of its expressive syntax, with function chaining, functional
programming (map, filter, etc.), macros and the like. This is indeed the
case. C and Zig are lower level, and require more lines (and programming time)
to produce results:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://fastdatascience.eu/images/blog/2024-10-18-lines.png&#34; alt=&#34;Lines of code&#34;&gt;&lt;/p&gt;
&lt;p&gt;However, you can see from a scatter plot of these two how Zig blows the other
languages out of the water from an execution time point of view, while requiring
almost 20% fewer lines than C, and roughly the same as Go:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://fastdatascience.eu/images/blog/2024-10-18-scatter.png&#34; alt=&#34;Lines vs time&#34;&gt;&lt;/p&gt;
&lt;p&gt;Note that these line counts exclude comments and blank lines.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Looking at Zig</title>
      <link>https://fastdatascience.eu/post/2024-10-13-look_at_zig/</link>
      <pubDate>Sun, 13 Oct 2024 00:00:00 +0000</pubDate>
      <guid>https://fastdatascience.eu/post/2024-10-13-look_at_zig/</guid>
      <description>&lt;p&gt;I finally took some time this weekend to look at Zig, and I am very impressed.
It&amp;rsquo;s a fairly low-level language, but could be appropriate for some
performance-critical data science use cases. Based on an initial test, it
is twice as fast as Rust, which is about 50% faster than Go. And it appears
to be faster than C, which I find puzzling.&lt;/p&gt;
&lt;p&gt;To explore the language, I rewrote a naive and computationally intensive
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/zig/src/main.zig&#34;&gt;brute-force solution&lt;/a&gt;
to &lt;a href=&#34;https://adventofcode.com/2023/day/5&#34;&gt;day 5&lt;/a&gt; of last year&amp;rsquo;s Advent of
Code. My non-sophisticated solution took 5:40 in Go for both parts, fast
enough that I didn&amp;rsquo;t bother finding a more streamlined solution (which
would have been necessary in Python). For comparison, I also rewrote the same solution in
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/main.rs&#34;&gt;Rust&lt;/a&gt; and
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/day05.c&#34;&gt;C&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;More on the performance comparisons in a future post. Here, I want to point out
some observations on the language, and some pointers on how to use it. I&amp;rsquo;m not
an experienced Zig user, so bear with me if you are.&lt;/p&gt;
&lt;h2 id=&#34;starting-a-project&#34; &gt;Starting a project
&lt;span&gt;
    &lt;a href=&#34;#starting-a-project&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;The Zig ecosystem can be installed from your package manager, e.g.,
&lt;code&gt;brew install zig&lt;/code&gt; on the Mac or &lt;code&gt;pacman -S zig&lt;/code&gt; on Arch Linux. You will also
want to install zls, the Zig Language Server, for IDE support.&lt;/p&gt;
&lt;p&gt;To create a new project, create a new folder, enter it and run &lt;code&gt;zig init&lt;/code&gt;. This creates
a src folder with a couple of zig files in it, and two build.zig files, which
control the build process.&lt;/p&gt;
&lt;p&gt;To compile, run &lt;code&gt;zig build&lt;/code&gt;, and the binary will be created in ./zig-out/bin and
you can run this directly, i.e., &lt;code&gt;zig-out/bin/test&lt;/code&gt;. When you&amp;rsquo;re ready, you
can compile with full optimizations by typing &lt;code&gt;zig build -Doptimization=ReleaseFast&lt;/code&gt;
and it is true to this promise, as we&amp;rsquo;ll see.&lt;/p&gt;
&lt;h2 id=&#34;the-language&#34; &gt;The language
&lt;span&gt;
    &lt;a href=&#34;#the-language&#34;&gt;
        &lt;svg viewBox=&#34;0 0 28 23&#34; height=&#34;100%&#34; width=&#34;19&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;&lt;path d=&#34;M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;path d=&#34;M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71&#34; fill=&#34;none&#34; stroke-linecap=&#34;round&#34; stroke-miterlimit=&#34;10&#34; stroke-width=&#34;2&#34;/&gt;&lt;/svg&gt;
    &lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;&lt;p&gt;The Zig language looks a lot like C, but has more checks, and a type system
that looks a lot like Rust&amp;rsquo;s.  E.g., i32 and i64 for ints, etc.&lt;/p&gt;
&lt;p&gt;The big difference is that Zig is very &lt;strong&gt;picky about memory&lt;/strong&gt;, and you have to
manually allocate memory, and free it or the program will show a lot of errors
when it&amp;rsquo;s finished. So unlike Go, which does garbage collection to manage
memory automatically, and Rust, which compiles using a &amp;ldquo;borrow checker&amp;rdquo;
protocol that forces you to keep track of which variable currently &amp;ldquo;owns&amp;rdquo; each
value but then automatically deallocates it for you, Zig requires to to
allocate memory, and free it when you&amp;rsquo;re done.  I found this very tedious, but
it gets easier over time (as did the initially horrendous borrow checking in Rust).&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//  Get a memory allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer _ = gpa.deinit();

// Create a vector for numbers, add a number to it
var nums = std.ArrayList(i64).init(allocator);
try nums.append(5);

// Create a copy of a string
const my_name = &amp;quot;jabba the hut&amp;quot;;
const name = try std.mem.Allocator.dupe(allocator, u8, my_name);

// When finished, free up the list and string
nums.deinit();
allocator.free(name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can &lt;strong&gt;free things up&lt;/strong&gt; when they go out of scope by using &lt;code&gt;defer&lt;/code&gt; as above, but
you have to be careful that these are no longer being used, as the compiler will
allow you to free things that have been passed to other variables (e.g., return
values from a function call). This approach led to some very time-consuming debugging.&lt;/p&gt;
&lt;p&gt;As noted, Zig has some useful data structures which are missing from C, but are present
in every modern language, such as vectors and hash maps. Everything in the standard library
is made available by importing one file, and is accessed by prefixing with &lt;code&gt;std.&lt;/code&gt; as shown here:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// At the beginning of every program
const std = @import(&amp;quot;std&amp;quot;);

// Use stdout for writing formatted output, note the arguments have to be 
// in a .{} list, don&#39;t forget the period
const stdout = std.io.getStdOut().writer();
try stdout.print(&amp;quot;A number {d} and a string {s}\n&amp;quot;, .{33, &amp;quot;hello&amp;quot;});

// Read a file into memory, automatically allocating space for it, up to 
// the size given, fails if the file is too big. Any data must be copied 
// if you want to use it after the buffer is freed when the function ends.
const data = try std.fs.cwd().readFileAlloc(allocator, &amp;quot;input.txt&amp;quot;, 10000);
defer allocator.free(data);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Functions&lt;/strong&gt; are central as you would expect, and programs are driven by a &lt;code&gt;main&lt;/code&gt; function
which calls other functions that take and return values:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pub fn main() !void {
    try stdout.print(&amp;quot;{d} doubled is {d}\n&amp;quot;, .{12, double(12)});
}

fn double(n: i32) i32 {
    return n * 2;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will notice that some function calls start with &lt;code&gt;try&lt;/code&gt; and some function return
types start with &lt;code&gt;!&lt;/code&gt;. This is because they might fail, and Zig&amp;rsquo;s &lt;strong&gt;error handling&lt;/strong&gt; is based
on return values that might be errors, just like in Rust. In Zig, &lt;code&gt;try&lt;/code&gt; executes the
function call, and raises an error if the call fails. The ! before the return type
indicates that the calling function might fail. It&amp;rsquo;s simple and quite elegant.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested, have a look at the
&lt;a href=&#34;https://github.com/andreaskaempf/adventofcode/blob/main/2023/day05/zig/src/main.zig&#34;&gt;AoC example&lt;/a&gt;,
the &lt;a href=&#34;https://ziglang.org/documentation/0.13.0/&#34;&gt;documentation&lt;/a&gt;, and the brief but excellent
&lt;a href=&#34;https://zig-by-example.com/&#34;&gt;Zig by Example&lt;/a&gt;. There are currently no books about Zig, but
that will change.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
