Skip to content

Commit

Permalink
small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
sivukhin committed Feb 4, 2024
1 parent f3eac7e commit 367a2da
Show file tree
Hide file tree
Showing 20 changed files with 797 additions and 81 deletions.
18 changes: 9 additions & 9 deletions aoc2023-first-days.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ <h2><a href="/">naming is hard</a></h2>
</div>
<div class="article">
<section id="Zero-allocation-hello-world-in-Rust">
<h1 date="2023/12/02" hide="true">Zero allocation hello world in Rust</h1>
<p>This year of <a href="https://adventofcode.com">Advent of Code</a> I decided to try <a href="https://www.rust-lang.org/"><strong>Rust</strong></a>. I&rsquo;m complete newbie and still learning basic concepts of language (btw, <a href="https://rust-book.cs.brown.edu">Brown Book</a> is amazing), but there were one idea that I wanted to try for the AoC challenges - I need to implement <strong>zero allocation</strong> solutions (at least for the first ones)!</p>
<a class="heading-anchor" href="#Zero-allocation-hello-world-in-Rust"><h1 date="2023/12/02" hide="true">Zero allocation hello world in Rust</h1>
</a><p>This year of <a href="https://adventofcode.com">Advent of Code</a> I decided to try <a href="https://www.rust-lang.org/"><strong>Rust</strong></a>. I&rsquo;m complete newbie and still learning basic concepts of language (btw, <a href="https://rust-book.cs.brown.edu">Brown Book</a> is amazing), but there were one idea that I wanted to try for the AoC challenges - I need to implement <strong>zero allocation</strong> solutions (at least for the first ones)!</p>
<p>What does it mean, <strong>zero allocation</strong>? Rust Book has nice <a href="https://rust-book.cs.brown.edu/ch04-01-what-is-ownership.html">chapter about ownership</a> which describes such concepts like memory, <em>stack</em> and <em>heap</em>. In short, your program usually operating with memory from two regions:</p>
<p>&hellip;something about zero allocations&hellip;</p>
<p>How can we analyze allocations of our program? I found nice tool <a href="https://github.com/matt-kimball/allocscope">allocscope</a> which record all allocations made with <code>malloc</code> in your program and allow you to analyze source of that allocations. Also, you can compile simple C code in shared library in order to override default <code>malloc</code> and add some debug information to it:</p>
<pre><code><span class="macro">#define</span> _<span class="identifier">GNU_SOURCE</span> <span class="number">1</span></code>
<pre class="language-c"><code><span class="macro">#define</span> _<span class="identifier">GNU_SOURCE</span> <span class="number">1</span></code>
<code><span class="macro">#include</span> <span class="string">"stdlib.h"</span></code>
<code><span class="macro">#include</span> <span class="string">"stdio.h"</span></code>
<code><span class="macro">#include</span> <span class="string">"dlfcn.h"</span></code>
Expand All @@ -32,9 +32,9 @@ <h1 date="2023/12/02" hide="true">Zero allocation hello world in Rust</h1>
<code>}</code>
</pre>
<p>Let&rsquo;s look at all <code>malloc</code> allocations in simple hello world program:</p>
<pre><code><span class="keyword">fn</span> <span class="function">main</span>() { <span class="identifier">println</span>!(<span class="string">"Hello, world!"</span>); }</code>
<pre class="language-rust"><code><span class="keyword">fn</span> <span class="function">main</span>() { <span class="identifier">println</span>!(<span class="string">"Hello, world!"</span>); }</code>
</pre>
<pre><code><span class="command">$> rustc main.rs</span></code>
<pre class="language-shell"><code><span class="command">$> rustc main.rs</span></code>
<code><span class="command">$> LD_PRELOAD=./libm.so ./main</span></code>
<code>malloc: 472</code>
<code>malloc: 120</code>
Expand All @@ -48,7 +48,7 @@ <h1 date="2023/12/02" hide="true">Zero allocation hello world in Rust</h1>
<p>Couple of them looks pretty suspicious: 1024 bytes allocations is almost surely used for some intermediate buffers. We are printing string to the console - so most likely that Rust implementation of writes to <code>stdout</code> uses buffering for performance.</p>
<p>If we will unwind all macros we should get some code equivalent to the <code>write_all</code> call on <code>stdout()</code> stream: <code>io::stdout().write_all(b"Hello, World!")</code>.</p>
<p>We can look up for the code of <code>io</code> module and indeed see, that <a href="https://doc.rust-lang.org/src/std/io/stdio.rs.html#614"><code>stdout()</code></a> creates synchronized instance wrapped with <a href="https://doc.rust-lang.org/src/std/io/buffered/linewriter.rs.html#87"><code>LineWriter</code></a> which has default buffer size of 1KiB.</p>
<pre><code>#[<span class="identifier">must_use</span>]</code>
<pre class="language-rust"><code>#[<span class="identifier">must_use</span>]</code>
<code>#[<span class="function">stable</span>(<span class="identifier">feature</span> = <span class="string">"rust1"</span>, <span class="identifier">since</span> = <span class="string">"1.0.0"</span>)]</code>
<code><span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function">stdout</span>() -> <span class="identifier">Stdout</span> {</code>
<code> <span class="identifier">Stdout</span> {</code>
Expand All @@ -61,14 +61,14 @@ <h1 date="2023/12/02" hide="true">Zero allocation hello world in Rust</h1>
<code> #[<span class="function">stable</span>(<span class="identifier">feature</span> = <span class="string">"rust1"</span>, <span class="identifier">since</span> = <span class="string">"1.0.0"</span>)]</code>
<code> <span class="keyword">pub</span> <span class="keyword">fn</span> <span class="function">new</span>(<span class="identifier">inner</span>: <span class="identifier">W</span>) -> <span class="identifier">LineWriter</span><<span class="identifier">W</span>> {</code>
<code> <span class="comment">// Lines typically aren't that long, don't use a giant buffer</span></code>
<code> <span class="identifier">LineWriter</span>::<span class="function">with_capacity</span>(<span class="number">1024,</span> <span class="identifier">inner</span>)</code>
<code> <span class="identifier">LineWriter</span>::<span class="function">with_capacity</span>(<span class="number">1024</span>, <span class="identifier">inner</span>)</code>
<code> }</code>
<code>}</code>
</pre>
<p>Ok, let&rsquo;s get rid of the <code>stdout</code> then and use <code>stderr</code> which also creates synchronized instance but without any additional buffering. We can write to <code>stderr</code> explicitly or use <code>eprintln!</code> macro for the same purpose. Let&rsquo;s see how much memory we allocate in this case in our new program:</p>
<pre><code><span class="keyword">fn</span> <span class="function">main</span>() { <span class="identifier">eprintln</span>!(<span class="string">"Hello, world!"</span>); }</code>
<pre class="language-rust"><code><span class="keyword">fn</span> <span class="function">main</span>() { <span class="identifier">eprintln</span>!(<span class="string">"Hello, world!"</span>); }</code>
</pre>
<pre><code><span class="command">$> rustc main.rs</span></code>
<pre class="language-shell"><code><span class="command">$> rustc main.rs</span></code>
<code><span class="command">$> LD_PRELOAD=./libm.so ./main</span></code>
<code>malloc: 472</code>
<code>malloc: 120</code>
Expand Down
4 changes: 2 additions & 2 deletions caching-is-hard.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ <h2><a href="/">naming is hard</a></h2>
</div>
<div class="article">
<section id="Caching-is-hard">
<h1 date="2023/11/01" hide="true">Caching is hard</h1>
<blockquote>
<a class="heading-anchor" href="#Caching-is-hard"><h1 date="2023/11/01" hide="true">Caching is hard</h1>
</a><blockquote>
<p>There are only two hard things in computer science: cache invalidation and naming things</p>
<p><strong>Phil Karlton</strong></p>
</blockquote>
Expand Down
190 changes: 190 additions & 0 deletions compression-kit.dj
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
{date="2024/02/03" hide="true"}
# Compression kit

- Varint encode example

``` ckit
zeros = repeat(bit, 0)
// we will generate two methods: varint_u32.encode and varint_u32.decode
fn varint_u32.encode(x: [32]bit) ~ varint_u32.decode(bytes: [?: 1..=5]u8) {
scan x ~ bytes {
result.push(byte) // ~ byte = result.pop()
// we can reconstruct computation because suffix is constant
if x[7..].prefix_of(zeros) ~ byte & 0x80 == 0 {
byte = x.pop(7) // ~ x.push(byte[0..7])
x.pop(0..) // ~ x.push(zeroes[0..]), instead of break explicitly skip rest of the x
} else {
byte = x.pop(7) | 0x80
}
}
}
```

- Shrink encode example

``` ckit
zeros = repeat(bit, 0)
fn shrink_u32.encode(x: [32]bit) ~ shrink_u32.decode(bytes: [n: 1..=5]u8) {
scan x ~ bytes {
result.push(byte) // ~ byte = result.pop()
byte = x.pop(8) // ~ x.push(byte)
if x[0..].prefix_of(zeros) ~ result.empty() {
x.pop(0..) // ~ x.push(zeroes[0..])
}
}
}
```

- Length prefixing

``` ckit
fn length_prefix.encode(b: [n: 0..1<<32]u8) ~ length_prefix.decode(bytes: [?: n+1..=n+5]u8) {
bytes.write(varint_length) // ~ 1. varint_length: [?]u8 = result.read()
varint_length = varint_u32(n) // ~ 2. n = varint_u32.decode(varint_length)
bytes.write_fixed(n, b) // ~ 3. b = result.read_fixed(n)
}
```

- Length prefixing with reverse

``` ckit
// idempotent function: reverse(reverse(b)) == b
extern fn reverse(b: [n]u8) ~ reverse(b: [n]u8);

fn length_prefix.encode(b: [n: 0..1<<32]u8) ~ length_prefix.decode(bytes: [?: n+1..=n+5]u8) {
bytes.write(varint_length) // ~ 1. varint_length: [?]u8 = result.read()
varint_length = varint_u32.encode(n) // ~ 2. n = varint_u32.decode(varint_length)
bytes.write_fixed(n, reversed) // ~ 3. reversed = result.read_fixed(n)
reversed = reverse(b) // ~ 4. b = reverse(reversed)
}

fn length_prefix.encode(b: [n: 0..1<<32]u8) ~ length_prefix.decode(bytes: [?: n+1..=n+5]u8) {
varint_length = varint_u32.encode(n) // ~ 2. n = varint_u32.decode(varint_length)
bytes.write(varint_length) // ~ 1. varint_length: [?]u8 = result.read()
reversed = reverse(b) // ~ 4. b = reverse(reversed)
bytes.write_fixed<n>(reversed) // ~ 3. reversed = result.read_fixed< n >()
}
```

- Syntax for state definition

``` ckit
forward state [?]T {
mut push(x: T) ~ pop() // dual method - we must call pop for every push in forward order
mut write(x: [?]T) ~ read()
mut<n: 0..1<<32> write_fixed(x: [n]T) ~ read_fixed()
}

forward state BlocksLru {
mut encode(offset: u32) ~ decode(encoded: u32)
mut touch(offset: u32): void // no dual method - we must call touch with same argument in dual method
}

forward state FwdBitStream {
mut init(): void
mut flush(): void
mut close(): void
mut<n: 0..=64> push(b: [n]bits) ~ pop()
}

// for backward state we must generate dual operations in reverse order
backward state BwdBitStream {
mut init_write() ~ close_read()
mut flush() ~ reload()
mut close_write() ~ init_read()
mut<n: 0..=64> push(b: [n]bits) ~ pop()
}

// must not compile because we .decode can't be implemented
fn invalid.encode(x: u32) ~ invalid.decode(bytes: [?]u32) {
var ( blocks: BlocksLru )
y = blocks.encode(x) // 2. ~ x = blocks_lru.decode(y) !!! y is unknown here but we can't violate state operations order
z = blocks.encode(y) // 3. ~ y = blocks_lru.decode(z)
bytes.push(z) // 1. ~ z = result.pop()
}
```

- [QOI](https://qoiformat.org/)

``` ckit

const RGBA = struct {
r: 0..256,
g: 0..256,
b: 0..256,
a: 0..256,
}

const Header {
magic: "qoif"
width: 0..1<<32
height: 0..1<<32
channels: {3, 4}
colorspace: {0, 1}
}

const Operation = union {
QOI_OP_RGB = struct { r: 0..256, g: 0..256, b: 0..256 },
QOI_OP_RGBA = struct { r: 0..256, g: 0..256, b: 0..256, a: 0..256 },
QOI_OP_INDEX = struct { index: 0..64 },
QOI_OP_DIFF = struct { dr: -2..2, dg: -2..2, db: -2..2 },
QOI_OP_LUMA = struct { dr: -32..32, dr_dg: -8..8, db_dg: -8..8 },
QOI_OP_RUN = struct { run: 0..64 },
}

fn qoi.encode({ header: Header, operations: [n]Operation }) ~ qoi.decode(bytes: [?]u8) {
bytes.write_fixed<4>("qoif") // 1. ~ assert!(bytes.read_fixed<4>() == "qoif")
bytes.write_fixed<4>(header.width) // 2. ~ header.width = bytes.read_fixed<4>()
bytes.write_fixed<4>(header.height) // 3. ~ header.height = bytes.read_fixed<4>()
bytes.write_fixed<4>(header.channels) // 4. ~ header.channels = bytes.read_fixed<4>()
bytes.write_fixed<4>(header.colorspace) // 5. ~ header.colorspace = bytes.read_fixed<4>()

~ size = header.width * header.height // initialize counter only for .decode method
scan operations ~ bytes {
current = operations.pop()
match current {
.QOI_OP_RGB ~ bytes[0] == 0b11111110 {
bytes.write_fixed<4>([0b11111110, current.r, current.g, current.b])
~ size -= 1
}
.QOI_OP_RGBA ~ bytes[0] == 0b11111111 {
bytes.write_fixed<5>([0b11111111, current.r, current.g, current.b, current.a])
~ size -= 1
}
.QOI_OP_INDEX ~ bytes[0][0..2] == 0b00 {
bytes.write_fixed<1>([0b00 || current.index])
~ size -= 1
}
.QOI_OP_DIFF ~ bytes[0][0..2] == 0b01 {
bytes.write_fixed<1>([0b01 || current.dr + 2 || current.dg + 2 || current.db + 2])
~ size -= 1
}
.QOI_OP_LUMA ~ bytes[0][0..2] == 0b10 {
bytes.write_fixed<2>([0b10 || current.dg + 32, current.dr_dg + 8 || current.db_dg + 8])
~ size -= 1
}
.QOI_OP_RUN ~ bytes[0][0..2] == 0b11 {
bytes.write_fixed<1>([0b11 || current.run])
~ size -= current.run
}
}
if ~ size == 0 {
break
}
}
bytes.write_fixed<8>([0, 0, 0, 0, 0, 0, 0, 1])
}

forward state qoi.Recent {

}

fn qoi.compress(image: [height: 0..1<<32, width: 0..1<<32]RGBA) ~ qoi.decompress({ header: Header, operations: [n]Operation }) {
header = Header { width: width, height: height, channels: 4, colorspace: 0 }
// how to iterate over 2d array?
scan image ~ operations {

}
}

```
Loading

0 comments on commit 367a2da

Please sign in to comment.