MatrixDevFunnyVideosMusicBooksProjectsAncapsTechEconomicsPrivacyGIFSCringeAnarchyFilmPicsThemesIdeas4MatrixAskMatrixHelpTop Subs
7

Welcome to the first SubMatrix programming competition. For the first problem in the series we are going to start with a classic: Fibonacci.

You can code in any language you like. Each response will be hand-scored by myself on my hardware. The scoring for these competitions will be based on (peak memory usage) * (total process time). Whichever language you choose to use, I will likely be able to test it.

Rule adjustments specific to Fibonacci: Without giving away hints, different solutions can achieve entirely different magnitudes of inputs and outputs, so they can't really be scored on the same scale. Solutions will first be scored on the highest power of 10 input they can process in under 5 seconds on my hardware. Then, within its cohort, a submission will be scored on peak memory * process runtime for the main problem.

A consumer-grade GPU will be available in the environment for those who wish to make use of it.

Problem Specification:

Create a program that reads a series of newline-separated numbers as input. The program can choose to read inputs either from stdin or from a file named input.list. The program will output the sum of the Fibonacci values associated with those inputs.

Assume the standard Fibonacci definition: F(0)=0, F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5, F(n)=F(n-1)+F(n-2)

For example, given the input:

5
3
7

The output should be: 20

Scoring structure:

For the first round, the program will be fed successively larger powers of 10 (10^1, 10^2, 10^3) until it can't produce a result within 5 seconds. The last successful input will be termed the program's cohort number, or CN.

For the second round of scoring, the program will be fed 30 random numbers ranging between CN/10 and CN. Programs in the same cohort will be fed the exact same sequence. The solution will be scored by peak memory * process runtime

When submitting a solution, please let us know what level of assistance was used (e.g., AI-assistance, research, looking at others' answers, or independent). All are welcome, just be transparent. More than one solution is welcome.

Comment preview

Google Gemini answer in C.

#include <stdio.h>
#include <stdlib.h>

#define INITIAL_CAPACITY 1024

// Structure to cache Fibonacci values for fast lookup and to handle large 'n'
typedef struct {
long long *data;
int size;
int capacity;
} FibCache;

// Initialize the Fibonacci cache with F(0) = 0 and F(1) = 1
FibCache* create_fib_cache() {
FibCache *cache = (FibCache *)malloc(sizeof(FibCache));
cache->capacity = INITIAL_CAPACITY;
cache->size = 2;
cache->data = (long long *)malloc(cache->capacity * sizeof(long long));
cache->data[0] = 0;
cache->data[1] = 1;
return cache;
}

// Retrieve or compute the nth Fibonacci number efficiently
long long get_fibonacci(FibCache *cache, int n) {
if (n < 0) return 0; // Guard for invalid negative inputs

// Expand cache if necessary
if (n >= cache->capacity) {
    while (n >= cache->capacity) {
        cache->capacity *= 2;
    }
    cache->data = (long long *)realloc(cache->data, cache->capacity * sizeof(long long));
}

// Compute iteratively up to n if not already computed
if (n >= cache->size) {
    for (int i = cache->size; i <= n; i++) {
        cache->data[i] = cache->data[i - 1] + cache->data[i - 2];
    }
    cache->size = n + 1;
}

return cache->data[n];

}

void free_fib_cache(FibCache *cache) {
if (cache) {
free(cache->data);
free(cache);
}
}

int main(void) {
FILE *input_stream = NULL;

// Try opening input.list first; fallback to stdin if it doesn't exist
input_stream = fopen("input.list", "r");
if (input_stream == NULL) {
    input_stream = stdin;
}

FibCache *cache = create_fib_cache();
long long total_sum = 0;
int n;

// Read newline-separated numbers until EOF
while (fscanf(input_stream, "%d", &n) == 1) {
    total_sum += get_fibonacci(cache, n);
}

// Close file stream if it was loaded from input.list
if (input_stream != stdin) {
    fclose(input_stream);
}

// Output the final sum of Fibonacci values
printf("%lld\n", total_sum);

free_fib_cache(cache);
return 0;

}

[-]x0x71(0|0)

Scoring:

The program was compiled with clang -o index index.c -O4 --march=native

The program managed to take an input of 10 and took 0.002s to complete
Attempting higher powers of 10 lead to inaccurate results from overflow errors

Completing 30 random numbers ranging from 1-10 took 0m0.002s
This had a peak memory usage of 262144 bytes.

Final score: 524bs

But because it only managed to calculate up to F(10) it's in third place. Thank goodness AI isn't beating real programmers yet.

[-]stuner2(+1|0)

Thanks for setting this up! Here's my solution in Rust:

Unfortunately, the basic idea I came up with turned out to be the same as x0x7 (computation using multiplication). But, it seems that Rust with rug is quite a bit faster than Ruby. So, I was able to reach 100M within 2.87 seconds on my machine. I did come up with an optimization for the second round to reduce the amount of Fibonacci numbers computed. Instead of the half points, the closest power of 2 is used as a reference point. In my testing, this brought the time down from 14.89 to 8.95 seconds. At that point, printing 20MB to the terminal already takes a long time, so I did my tests with stdout redirected.

I didn't use AI/LLM tools.

Code:

use std::fs;
use std::collections::HashMap;

type MyBigUInt = rug::Integer;

pub struct Fib {
    // Cache to hold already computed results
    cache : HashMap<u64, MyBigUInt>,
}

fn bconst(val: u64) -> MyBigUInt {
    MyBigUInt::from(val)
}

impl Fib {

    pub fn new() -> Self {
        return Self {cache : HashMap::new()}
    }

    // Compute sum of fibonacci nubers
    pub fn compute_sum(numbers_in : Vec<u64>) -> MyBigUInt
    {
        let mut inst = Self::new();
        let mut sum :MyBigUInt = bconst(0);
        for number in numbers_in {
            sum += inst.compute(number);
        }

        return sum;
    }

    // Compute fibonacci number recursively and with caching
    pub fn compute(&mut self, number_in : u64) -> MyBigUInt
    {
        // Handle 0, 1, ...
        if number_in <= 4 {
            return MyBigUInt::from(match number_in {
                0 => 0u32,
                1 => 1u32,
                2 => 1u32,
                3 => 2u32,
                4 => 3u32,
                _ => panic!("not reachable"),
            });
        }

        if self.cache.contains_key(&number_in) { return self.cache[&number_in].clone(); }

        let upper = 1 << (number_in-1).ilog2(); // Largest power of 2 smaller than number_in
        let lower = number_in - upper;

        // F[l+u] = F[u-1] * F[l] + F[u] * F[l+1]

        // Compute recursively
        let f_lower = self.compute(lower);
        let f_lowerp1 = self.compute(lower+1);
        let f_upper = self.compute(upper);
        let f_upperm1 = self.compute(upper-1);

        let res = f_upperm1 * f_lower + f_upper * f_lowerp1;
        self.cache.insert(number_in, res.clone());
        return res;
    }
    
}

fn main() {

    // Read input file
    let file_path = "input.list";
    let contents = fs::read_to_string(file_path).expect("Unable to read input file");

    // Parse lines to integers
    let mut numbers_in : Vec<u64> = Vec::new();
    for line in contents.lines() {
        let trimmed = line.trim();
        if !trimmed.is_empty() {         
            match trimmed.parse::<u64>() {
                Ok(val) => numbers_in.push(val),
                Err(..) => println!("Input line is not an integer: {}", trimmed),
            };   
        }
    }

    // Compute sum
    let sum = Fib::compute_sum(numbers_in).to_string();

    // Print sum
    println!("{sum}");
}

Cargo.toml:

[package]
name = "fibonacci"
version = "0.1.0"
edition = "2024"

[dependencies]
rug = "1.30.0"
[-]x0x72(+1|0)

Cool. Give me a second to get this scored.


It handled an input value of 10000000 in 0.332s

The list of 30 values between 1000000-10000000 took 1.317s
Peek memory usage was 42401792 bytes

Final score: 53Mbs

Now I need to rescore mine because I improved the setup. Barring some huge surprise with that, I think you're winning unless someone else comes along.

[-]stuner1(0|0)

Neat. It seems like our two halving implementations would've taken almost exactly the same amount of time.

I was playing around a bit with Rust parallelism to improve my solution but I didn't get anything to work well (turns out the async runtimes I used both only ran on a single thread...). But there's certainly some room for improvement there in case someone wants to try :)

[-]pumpkin1(0|0)

Cool

[-]x0x71(0|0)

Here is my solution. I came up with this speedup myself. Code below.

#!/usr/bin/ruby

def fib(a)
 return a if a<2 
 fib(a-1)+fib(a-2)
end

def fibN(a,memo={})
 return fib(a) if a<4 
 n=a/2
 memo[a] ||= fibN(n+1,memo)*fibN(a-n,memo)+fibN(n,memo)*fibN(a-n-1,memo)
end

puts(File.readlines('input.list').map(){|i| i.to_i}.reduce(0){|acc,num| acc+fibN(num)}.to_s)

I noticed that when you manually expand Fibonacci that Fibonacci numbers show up in the coefficients. The deeper you do it the more steps you can skip. It could be generalized. An arbitrary n value can be chosen representing how many expansions and simplifications are done, and you can pick an n value that minimizes the inputs of the next wave.

The fact that each Fibonacci call branches to four sub-calls is mitigated by memoization and the fact that we are scaling down n rather quickly.

I have ideas for how it could be done better. But I wanted to get a solution in.


I suppose I need to score my own. It managed to handle an input value of 10000000 and responded in 0.821s. But the program will explode past that. Running it with 30 random numbers between 1000000-10000000 took 4.666s. I'll have it's memory score in just a second...

According to cgroups it had a peak memory of 81326080 bytes. Final score 362Mbs.

This is the integer it produced. https://submatrix.net/z/fib/result.txt

[-]x0x71(0|0)

Edit: Results from better scoring method (and changing to the correct device). Same as used on @stuner's Rust implementation

It handled an input value of 1000000 in 0.360s
The list of 30 values between 1000000-10000000 took 2.114s
Peak memory usage was 115412992 bytes

Final score: 233Mbs