Skip to content

Instantly share code, notes, and snippets.

@andrewrk
Created May 13, 2020 18:21
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewrk/f64a232fc122cf62eec29c341fc9c7c2 to your computer and use it in GitHub Desktop.
Save andrewrk/f64a232fc122cf62eec29c341fc9c7c2 to your computer and use it in GitHub Desktop.
RedisConf2020 Talk: "Writing Redis Modules in Zig" https://www.youtube.com/watch?v=Csz26Wy9Ses
test "benchmark" {
var timer = try std.time.Timer.start();
const start = timer.lap();
var i: usize = 0;
var hash: usize = 0;
while (i < 1000000) : (i += 1) {
const src = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
var output: [100]u8 = undefined;
var bf: BrainFuckInterpreter = undefined;
bf.init(src, "", &output, 500);
bf.start();
for (bf.output[0..bf.output_index]) |byte| {
hash +%= byte;
}
}
const end = timer.lap();
const elapsed_s = @intToFloat(f64, end - start) / std.time.ns_per_s;
std.debug.warn("time={} hash={}\n", .{ elapsed_s, hash });
}
#include <time.h>
int main(int argc, char **argv) {
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
size_t hash = 0;
for (size_t i = 0; i < 1000000; i += 1) {
const char *src = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
char output[100];
struct BrainFuckInterpreter bf;
BrainFuckInterpreter_init(&bf, src, strlen(src), "", 0, output, 100, 500);
BrainFuckInterpreter_start(&bf);
for (size_t j = 0; j < bf.output_index; j += 1) {
hash += bf.output_ptr[j];
}
}
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
double start_ns = start.tv_sec * 1000000000.0 + start.tv_nsec;
double end_ns = end.tv_sec * 1000000000.0 + end.tv_nsec;
double elapsed_s = (end_ns - start_ns) / 1000000000.0;
fprintf(stderr, "time=%f hash=%zu\n", elapsed_s, hash);
}
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> gcc --version
gcc (GCC) 9.2.0
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> gcc -o bench_c bf.c -fPIC -std=gnu99 -O2
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> ./bench_c
time=1.302713 hash=1095000000
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> clang --version
clang version 7.1.0 (tags/RELEASE_710/final)
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> ./bench_c
time=1.147775 hash=1095000000
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> zig version
0.6.0
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> zig test bf.zig -lc -I. --release-fast
Test [2/2] test "benchmark"...time=1.072691358e+00 hash=1095000000
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> zig cc -o bench_c bf.c -fPIC -std=gnu99 -O2
andy@ark ~/m/R/Talk-WritingRedisModulesInZig> ./bench_c
time=1.052307 hash=1095000000
>>> (1.302713 - 1.147775) / 1.302713
0.11893486900030936
>>> (1.302713 - 1.072691358) / 1.302713
0.17657123403236175
>>> (1.302713 - 1.052307) / 1.302713
0.19221885403768896
#include "redismodule.h"
#include <string.h>
#define TAPE_SIZE (8 * 1024)
#define MAX_PRG_BYTES 400
struct BrainFuckInterpreter {
char tape[TAPE_SIZE];
size_t tape_head;
size_t pc;
size_t cycle_count;
const char *program_ptr;
size_t program_len;
size_t max_cycles;
size_t matching_bracket[MAX_PRG_BYTES];
char *output_ptr;
size_t output_len;
size_t output_index;
const char *input_ptr;
size_t input_len;
size_t input_index;
};
static struct BrainFuckInterpreter BrainFuckInterpreter_init(
const char *src_ptr, size_t src_len,
const char *input_ptr, size_t input_len,
char *output_ptr, size_t output_len,
size_t max_cycles)
{
struct BrainFuckInterpreter bf;
memset(&bf.tape, 0, TAPE_SIZE);
bf.tape_head = 0;
bf.pc = 0;
bf.cycle_count = 0;
bf.program_ptr = src_ptr;
bf.program_len = src_len;
bf.max_cycles = max_cycles;
bf.input_ptr = input_ptr;
bf.input_len = input_len;
bf.input_index = 0;
bf.output_ptr = output_ptr;
bf.output_len = output_len;
bf.output_index = 0;
// parse for matching brackets
for (size_t i = 0; i < src_len; i += 1) {
// default: no control flow modification
bf.matching_bracket[i] = i;
}
size_t stack[MAX_PRG_BYTES];
size_t stack_index = 0;
for (size_t i = 0; i < src_len; i += 1) {
switch (src_ptr[i]) {
case '[':
stack[stack_index] = i;
stack_index += 1;
continue;
case ']':
if (stack_index != 0) {
stack_index -= 1;
size_t begin_index = stack[stack_index];
bf.matching_bracket[i] = begin_index;
bf.matching_bracket[begin_index] = i;
}
continue;
default:
continue;
}
}
return bf;
}
static void BrainFuckInterpreter_start(struct BrainFuckInterpreter *bf) {
while (bf->pc < bf->program_len) {
if (bf->cycle_count >= bf->max_cycles) return;
switch (bf->program_ptr[bf->pc]) {
case '<':
if (bf->tape_head != 0)
bf->tape_head -= 1;
break;
case '>':
if (bf->tape_head < TAPE_SIZE)
bf->tape_head += 1;
break;
case '+':
bf->tape[bf->tape_head] += 1;
break;
case '-':
bf->tape[bf->tape_head] -= 1;
break;
case '.':
if (bf->output_index < bf->output_len) {
bf->output_ptr[bf->output_index] = bf->tape[bf->tape_head];
bf->output_index += 1;
}
break;
case ',':
if (bf->input_index >= bf->input_len) {
bf->tape[bf->tape_head] = 0;
} else {
bf->tape[bf->tape_head] = bf->input_ptr[bf->input_index];
bf->input_index += 1;
}
break;
case '[':
if (bf->tape[bf->tape_head] == 0) {
bf->pc = bf->matching_bracket[bf->pc];
}
break;
case ']':
if (bf->matching_bracket[bf->pc] != bf->pc) {
bf->pc = bf->matching_bracket[bf->pc];
bf->cycle_count += 1;
continue;
}
break;
default:
break;
}
bf->pc += 1;
bf->cycle_count += 1;
}
}
int BF_Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc < 2)
return RedisModule_WrongArity(ctx);
size_t src_len;
const char *src_ptr = RedisModule_StringPtrLen(argv[1], &src_len);
size_t max_cycles = 1000;
if (argc >= 3) {
long long requested_cycles;
if (RedisModule_StringToLongLong(argv[2], &requested_cycles) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (requested_cycles < 0)
return RedisModule_ReplyWithError(ctx, "invalid max cycles value");
max_cycles = requested_cycles;
}
const char *input_ptr = "";
size_t input_len = 0;
if (argc >= 4)
input_ptr = RedisModule_StringPtrLen(argv[3], &input_len);
unsigned int hash = 0;
for (size_t i = 0; i < src_len; i += 1) {
unsigned int random_bits = 0xfb5c4c07;
hash += (unsigned)src_ptr[i] * random_bits;
}
RedisModule_Log(ctx, "NOTICE", "program hash: 0x%x", hash);
char output_buf[1024];
struct BrainFuckInterpreter bf = BrainFuckInterpreter_init(
src_ptr, src_len, input_ptr, input_len,
output_buf, sizeof(output_buf), max_cycles);
BrainFuckInterpreter_start(&bf);
if (bf.cycle_count == bf.max_cycles)
return RedisModule_ReplyWithError(ctx, "program exceeded maximum cycle count");
return RedisModule_ReplyWithStringBuffer(ctx, bf.output_ptr, bf.output_index);
}
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
if (RedisModule_Init(ctx, "bfmodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand(ctx, "bf", BF_Command, "readonly", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
#include <time.h>
int main(int argc, char **argv) {
struct timespec start;
clock_gettime(CLOCK_MONOTONIC, &start);
size_t hash = 0;
for (size_t i = 0; i < 1000000; i += 1) {
const char *src = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
char output[100];
struct BrainFuckInterpreter bf = BrainFuckInterpreter_init(src, strlen(src), "", 0, output, 100, 500);
BrainFuckInterpreter_start(&bf);
for (size_t j = 0; j < bf.output_index; j += 1) {
hash += bf.output_ptr[j];
}
}
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
double start_ns = start.tv_sec * 1000000000.0 + start.tv_nsec;
double end_ns = end.tv_sec * 1000000000.0 + end.tv_nsec;
double elapsed_s = (end_ns - start_ns) / 1000000000.0;
fprintf(stderr, "time=%f hash=%zu\n", elapsed_s, hash);
}
const std = @import("std");
usingnamespace @cImport(@cInclude("redismodule.h"));
export fn BF_Command(ctx: ?*RedisModuleCtx, argv: ?[*]?*RedisModuleString, argc: c_int) c_int {
if (argc < 2)
return RedisModule_WrongArity.?(ctx);
var src_len: usize = undefined;
const src_ptr = RedisModule_StringPtrLen.?(argv.?[1], &src_len);
const src = src_ptr[0..src_len];
const max_cycles = if (argc >= 3) blk: {
var requested_cycles: i64 = undefined;
if (RedisModule_StringToLongLong.?(argv.?[2], &requested_cycles) == REDISMODULE_ERR)
return REDISMODULE_ERR;
const cycles_usize = std.math.cast(usize, requested_cycles) catch |err| switch (err) {
error.Overflow => return RedisModule_ReplyWithError.?(ctx, "invalid max cycles value"),
};
break :blk cycles_usize;
} else 1000;
const input = if (argc >= 4) blk: {
var len: usize = undefined;
const ptr = RedisModule_StringPtrLen.?(argv.?[3], &len);
break :blk ptr[0..len];
} else "";
var hash: u32 = 0;
for (src) |byte| {
const random_bits = 0xfb5c4c07;
hash +%= @as(u32, byte) *% random_bits;
}
RedisModule_Log.?(ctx, "NOTICE", "program hash: 0x%x", hash);
var output_buf: [1024]u8 = undefined;
var bf = BrainFuckInterpreter.init(src_ptr[0..src_len], input, &output_buf, max_cycles);
bf.start();
if (bf.cycle_count == bf.max_cycles)
return RedisModule_ReplyWithError.?(ctx, "program exceeded maximum cycle count");
const output = bf.output[0..bf.output_index];
return RedisModule_ReplyWithStringBuffer.?(ctx, output.ptr, output.len);
}
export fn RedisModule_OnLoad(ctx: *RedisModuleCtx) c_int {
if (RedisModule_Init(ctx, "bfmodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
if (RedisModule_CreateCommand.?(ctx, "bf", BF_Command, "readonly", 0, 0, 0) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return REDISMODULE_OK;
}
const tape_size = 8 * 1024;
const max_prg_bytes = 400;
const BrainFuckInterpreter = struct {
tape: [tape_size]u8,
tape_head: usize,
pc: usize,
cycle_count: usize,
program: []const u8,
max_cycles: usize,
matching_bracket: [max_prg_bytes]usize,
output: []u8,
output_index: usize,
input: []const u8,
input_index: usize,
pub fn init(program_bytes: []const u8, input: []const u8, output: []u8, max_cycles: usize) BrainFuckInterpreter {
var self: BrainFuckInterpreter = .{
.tape = [1]u8{0} ** tape_size,
.tape_head = 0,
.matching_bracket = undefined,
.pc = 0,
.cycle_count = 0,
.program = program_bytes,
.max_cycles = max_cycles,
.input = input,
.input_index = 0,
.output = output,
.output_index = 0,
};
// parse for matching brackets
for (program_bytes) |_, i| {
// default: no control flow modification
self.matching_bracket[i] = i;
}
var stack: [max_prg_bytes]usize = undefined;
var stack_index: usize = 0;
for (program_bytes) |byte, i| {
switch (byte) {
'[' => {
stack[stack_index] = i;
stack_index += 1;
},
']' => if (stack_index != 0) {
stack_index -= 1;
const begin_index = stack[stack_index];
self.matching_bracket[i] = begin_index;
self.matching_bracket[begin_index] = i;
},
else => continue,
}
}
return self;
}
pub fn start(self: *BrainFuckInterpreter) void {
while (self.pc < self.program.len) {
if (self.cycle_count >= self.max_cycles) return;
switch (self.program[self.pc]) {
'<' => if (self.tape_head != 0) {
self.tape_head -= 1;
},
'>' => if (self.tape_head < self.tape.len) {
self.tape_head += 1;
},
'+' => self.tape[self.tape_head] +%= 1,
'-' => self.tape[self.tape_head] -%= 1,
'.' => if (self.output_index < self.output.len) {
self.output[self.output_index] = self.tape[self.tape_head];
self.output_index += 1;
},
',' => if (self.input_index >= self.input.len) {
self.tape[self.tape_head] = 0;
} else {
self.tape[self.tape_head] = self.input[self.input_index];
self.input_index += 1;
},
'[' => if (self.tape[self.tape_head] == 0) {
self.pc = self.matching_bracket[self.pc];
},
']' => if (self.matching_bracket[self.pc] != self.pc) {
self.pc = self.matching_bracket[self.pc];
self.cycle_count += 1;
continue;
},
else => {},
}
self.pc += 1;
self.cycle_count += 1;
}
}
};
test "interpreter" {
{
const src = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
var output: [100]u8 = undefined;
var bf = BrainFuckInterpreter.init(src, "", &output, 500);
bf.start();
std.testing.expect(std.mem.eql(u8, bf.output[0..bf.output_index], "Hello World!\n"));
}
}
test "benchmark" {
var timer = try std.time.Timer.start();
const start = timer.lap();
var i: usize = 0;
var hash: usize = 0;
while (i < 1000000) : (i += 1) {
const src = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>.";
var output: [100]u8 = undefined;
var bf = BrainFuckInterpreter.init(src, "", &output, 500);
bf.start();
for (bf.output[0..bf.output_index]) |byte| {
hash +%= byte;
}
}
const end = timer.lap();
const elapsed_s = @intToFloat(f64, end - start) / std.time.ns_per_s;
std.debug.warn("time={} hash={}\n", .{ elapsed_s, hash });
}
Zig is an ideal language for writing Redis modules, where software must have uncompromising performance and seamless interaction with C ABIs. In the talk we compare the development process of creating, testing, debugging, and measuring performance of a new Redis module in C vs Zig.
20 to 45 minute video presentation
0. show redismodule.h
1. hello world in C and Zig.
2. Show both of them working in Redis.
3. flip the vim tab back and forth to show it's the same structure
1. show the brainfuck C code and brainfuck Zig code, brief code tour
2. show that zig version is fewer lines of code
3. run the zig test suite
4. Show them both working on the redis server
5. introduce a segfault in the C version
6. introduce a the same problem in the zig version, show the stack trace
7. show how zig cc finds Undefined Behavior in the C code
1. copy paste the benchmarking code into bf.c
2. run the commands for benchmarking
- gcc
- clang
- zig cc
3. copy paste the benchmarking code into bf.zig
4. run the zig test command for benchmarking
5. show the results slide
6. give caveats
#include "redismodule.h"
int HelloWorld_Command(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return RedisModule_ReplyWithSimpleString(ctx, "Hello World!");
}
int RedisModule_OnLoad(RedisModuleCtx *ctx) {
if (RedisModule_Init(ctx, "hellomodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return RedisModule_CreateCommand(ctx, "hello", HelloWorld_Command, "readonly", 0, 0, 0);
}
usingnamespace @cImport(@cInclude("redismodule.h"));
export fn HelloWorld_Command(ctx: ?*RedisModuleCtx, argv: ?[*]?*RedisModuleString, argc: c_int) c_int {
return RedisModule_ReplyWithSimpleString.?(ctx, "Hello World!");
}
export fn RedisModule_OnLoad(ctx: *RedisModuleCtx) c_int {
if (RedisModule_Init(ctx, "hellomodule", 1, REDISMODULE_APIVER_1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
return RedisModule_CreateCommand.?(ctx, "hello", HelloWorld_Command, "readonly", 0, 0, 0);
}
@samof76
Copy link

samof76 commented Oct 4, 2022

I have moved the redismodules.h into the folder in which I am compiling, not sure what am I doing wrong.

> zig build-lib hello.zig -dynamic -Bsymbolic -I. -lc
hello.zig:3:37: error: use of undeclared identifier 'RedisModuleCtx'
export fn HelloWorld_Command(ctx: ?*RedisModuleCtx, argv: ?[*]?*RedisModuleString, argc: c_int) c_int {
                                    ^
hello.zig:7:36: error: use of undeclared identifier 'RedisModuleCtx'
export fn RedisModule_OnLoad(ctx: *RedisModuleCtx) c_int {
                                   ^

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment