Skip to content

Instantly share code, notes, and snippets.

@delasy
Last active October 12, 2024 21:32
Show Gist options
  • Save delasy/4674785062892745abe8757ad3b5b32c to your computer and use it in GitHub Desktop.
Save delasy/4674785062892745abe8757ad3b5b32c to your computer and use it in GitHub Desktop.
C vs Node.js performance comparison

So I compared Node.js and C code you can find tests below, this was the task:

There's a staircase with N steps, and you can climb 1 or 2 steps at a time. Given N, write a function that returns the number of unique ways you can climb the staircase. The order of the steps matters.
For example, if N is 4, then there are 5 unique ways:
1, 1, 1, 1
2, 1, 1
1, 2, 1
1, 1, 2
2, 2
What if, instead of being able to climb 1 or 2 steps at a time, you could climb any number from a set of p

These are the result:
C: 298ms
Node.js: 672ms

NOTE: you need to run node --stack_size=8192 for it to work.

FROM alpine
RUN apk add build-base --no-cache
RUN apk add valgrind --no-cache
WORKDIR /app
COPY . .
RUN gcc test.c -g -o test.out
ENTRYPOINT ["valgrind", "--error-exitcode=255", "--errors-for-leak-kinds=all", "--leak-check=full", "--show-leak-kinds=all", "--tool=memcheck", "--track-origins=yes", "./test.out"]
; nasm -f macho64 test.asm
; ld -macos_version_min 10.15.0 -no_pie -L$(xcode-select -p)/SDKs/MacOSX.sdk/usr/lib -lSystem -o test.out test.o
global _main
section .text
alloc:
mov rax, 0x02000007
xor rdi, rdi
mov rsi, 8
mov rdx, 0x03
mov r10, 0x1002
mov r8, -1
xor r9, r9
syscall
test rax, rax
jz alloc_fail
ret
alloc_fail:
mov rax, 0x02000004
mov rdi, 2
mov rsi, alloc_fail_msg
mov rdx, alloc_fail_msg.len
syscall
mov rax, 0x02000001
mov rdi, 1
syscall
; todo realloc
climb:
mul rax,
mov rax,
_main:
mov rax, 40
; todo move here input
; todo move here input_len
call climb
; todo print result
mov rax, 0x02000004
mov rdi, 2
mov rsi, alloc_fail_msg
mov rdx, alloc_fail_msg.len
syscall
mov rax, 0x02000001
xor rdi, rdi
syscall
section .data
alloc_fail_msg: db "Failed to allocate bytes", 10
.len: equ $ - alloc_fail_msg
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
static void *alloc (const size_t l) {
void *r = malloc(l);
if (r != NULL) return r;
fprintf(stderr, "Failed to allocate %zu bytes\n", l);
exit(1);
}
static void *re_alloc (void *p, const size_t l) {
void *r = realloc(p, l);
if (r != NULL) return r;
fprintf(stderr, "Failed to re-allocate %zu bytes\n", l);
exit(1);
}
static int *slice_clone (const int *head, const size_t head_len, const size_t capacity) {
int *r = alloc(capacity * sizeof(int));
memcpy(r, head, head_len);
return r;
}
static size_t climb_priv (int ***r, const int max_steps, const int *n, const size_t n_len, const int cur_steps, const int *head, const size_t head_len) {
size_t r_len = 0;
*r = alloc(sizeof(int *));
if (cur_steps == max_steps) {
*r = re_alloc(*r, sizeof(int *));
(*r)[r_len++] = slice_clone(head, head_len, head_len);
return r_len;
}
for (size_t i = 0; i < n_len; i++) {
int choice = n[i];
size_t head_cloned_len;
int *head_cloned;
int **t = NULL;
size_t t_len;
if (cur_steps + choice > max_steps) continue;
head_cloned_len = head_len + 1;
head_cloned = slice_clone(head, head_len, head_cloned_len);
head_cloned[head_len] = choice;
t_len = climb_priv(&t, max_steps, n, n_len, cur_steps + choice, head_cloned, head_cloned_len);
*r = re_alloc(*r, (r_len + t_len) * sizeof(int *));
memcpy(&(*r)[r_len], t, t_len * sizeof(int *));
r_len += t_len;
free(head_cloned);
free(t);
}
return r_len;
}
static int climb (const int max_steps, const int *n, const size_t n_len) {
int *choices = alloc(n_len * sizeof(int));
size_t choices_len = 0;
int **r = NULL;
size_t r_len;
for (size_t i = 0; i < n_len; i++) {
bool duplicate = false;
if (n[i] <= 0) continue;
for (size_t j = 0; j < choices_len; j++) {
if (choices[j] == n[i]) {
duplicate = true;
break;
}
}
if (duplicate) continue;
choices[choices_len++] = n[i];
}
r_len = climb_priv(&r, max_steps, choices, choices_len, 0, NULL, 0);
for (size_t i = 0; i < r_len; i++) {
free(r[i]);
}
free(choices);
free(r);
return (int) r_len;
}
int main (void) {
struct timespec start, end;
uint64_t delta_sum = 0;
clock_gettime(CLOCK_MONOTONIC_RAW, &start);
for (int i = 0; i < 100; i++) {
int input[2] = {1, 2};
climb(30, input, sizeof(input) / sizeof(input[0]));
}
clock_gettime(CLOCK_MONOTONIC_RAW, &end);
delta_sum += (end.tv_sec - start.tv_sec) * 1000 + (end.tv_nsec - start.tv_nsec) / 1000000;
printf("delta: %f\n", (double) delta_sum / 100);
}
function climb(maxSteps, n) {
const choices = n
.filter((it) => it > 0)
.filter((it, idx, arr) => arr.indexOf(it) === idx);
return climb_priv(maxSteps, choices).length;
}
function climb_priv(maxSteps, n, curSteps = 0, head = []) {
if (curSteps === maxSteps) { return [head]; }
let r = [];
for (const choice of n) {
if (curSteps + choice <= maxSteps) {
r.push(...climb_priv(maxSteps, n, curSteps + choice, [...head, choice]));
}
}
return r;
}
const start = Date.now();
for (let i = 0; i < 100; i++) {
const input = [1, 2];
climb(30, input);
}
const end = Date.now();
console.log("delta:", (end - start) / 100)
fn climb(
max_steps: i32,
n: &Vec<i32>,
cur_steps_maybe: Option<i32>,
head_maybe: Option<Vec<i32>>
) -> Vec<Vec<i32>> {
let cur_steps = cur_steps_maybe.unwrap_or(0);
let head = head_maybe.unwrap_or(Vec::new());
if cur_steps == max_steps { return vec![head] }
let mut r: Vec<Vec<i32>> = Vec::new();
for step in n {
if cur_steps + *step > max_steps {
continue;
}
let mut head_cloned = head.clone();
head_cloned.push(*step);
r.extend(
climb(max_steps, n, Some(cur_steps + *step), Some(head_cloned))
);
}
r
}
fn main() {
println!("{:?}", climb(4, &vec![1, 2], None, None));
println!("{:?}", climb(4, &vec![1, 3, 5], None, None));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment