Compare commits

...

4 Commits

Author SHA1 Message Date
Aryadev Chavali
a763bff532 Added release mode building
Now the build script enables you to:
- Build in debug mode (default no arguments)
- Build in debug mode then run (`run` argument)
- Build in release mode (`release` argument)
2025-12-12 04:33:13 +00:00
Aryadev Chavali
7112937b0b Better thread pool constructor
statically define a number of threads, then setup the necessary
machinery to make it work.
2025-12-12 04:30:50 +00:00
Aryadev Chavali
a5666328b7 Fixed the heap-use-after-free issue.
Just standard multithreading stuff; access to the allocator while hot
threads are running means stuff can change underneath us even /during/
a read.  I've mutex locked state for stuff in the drawing domain which
stops this issue.
2025-12-12 04:29:32 +00:00
Aryadev Chavali
5d78cb20df Adjust TODOs 2025-12-12 04:28:21 +00:00
5 changed files with 51 additions and 25 deletions

View File

@@ -20,8 +20,28 @@ the generator fraction is in green.
This was done just for fun really, but it's quite fun to see it
generate a dense number line over many iterations.
* TODOs
** TODO Multithreading
SCHEDULED: <2025-11-18 Tue>
** TODO Tree visualisation
Instead of a number line, how about visualising the actual tree at
work as a graph of nodes? Maybe colouring nodes based on where it is
on the number line.
** TODO Don't walk the tree everytime we compute_bounds
[[file:src/state.cpp::void DrawState::compute_bounds()][location]]
We already have the latest bound nodes so we're part-way through the
tree. Just keep going down what we have so far surely? Even better,
don't use nodes _at all_. Run with an index!
** DONE Fix weird issue at past 100K nodes
std::vector seems to crap itself past 100K nodes - we keep getting
heap-use-after-free issues when trying to access the allocator nodes
at that point. Seemingly random. What's going on?
Solution: _everytime_ we want to access the allocator for nontrivial
(like memory reads) we need to lock the mutex. No two ways about it.
All draw functions were causing the issue.
** DONE Prettify code base
It's a big blob of code currently in the graphics portion. Not very
pretty but it gets the job done. Try modularisation.
** DONE Multithreading
Currently single threaded. A multithreaded implementation could have
multiple nodes generated at once, which would speed up the
implementation.
@@ -29,7 +49,7 @@ implementation.
Might need to study my current implementation to see if it could be
done better.
*** TODO Formalise state structure (separate drawing functions)
*** DONE Formalise state structure (separate drawing functions)
Make a dedicated header and fit the necessary functions to our state
structure: [[file:src/main.cpp::struct State][state structure]].
@@ -40,14 +60,14 @@ A good working name would be ~cw_state~.
We could then have a separate structure for the drawing context
(~cw_draw~) which can update itself by reading the ~cw_state~.
*** TODO Setup a queue and allocator mutex on state
*** DONE Setup a queue and allocator mutex on state
We need one for the queue so we can make clean pushes and pops, and
one for the allocator to ensure we're not messing up our indices in
the nodes.
We could just set these up in the state structure itself to make
lookup easier.
*** TODO Make iterate use the state structure and mutexes
*** DONE Make iterate use the state structure and mutexes
[[file:src/numerics.cpp::std::tuple<Fraction, Fraction, Fraction>
iterate(std::queue<word_t> &queue,]]
@@ -64,11 +84,4 @@ iterate(std::queue<word_t> &queue,]]
I think this scheme minimises the any mutex is locked to just the bare
minimum, ensuring other threads can get in on the action.
*** TODO Setup a thread pool utilising state and iterate
** TODO Prettify code base
It's a big blob of code currently in the graphics portion. Not very
pretty but it gets the job done. Try modularisation.
** TODO Tree visualisation
Instead of a number line, how about visualising the actual tree at
work as a graph of nodes? Maybe colouring nodes based on where it is
on the number line.
*** DONE Setup a thread pool utilising state and iterate

View File

@@ -5,12 +5,21 @@ set -xe
OUT="cw_tree.out"
GFLAGS="-Wall -Wextra -Wswitch-enum -std=c++17"
DFLAGS="-ggdb -fsanitize=address -fsanitize=undefined"
RFLAGS="-O2"
CFLAGS="$GFLAGS $DFLAGS"
LIBS="-lraylib -lm"
c++ $CFLAGS -o $OUT src/node.cpp src/state.cpp src/worker.cpp src/main.cpp $LIBS
build() {
c++ $CFLAGS -o $OUT src/node.cpp src/state.cpp src/worker.cpp src/main.cpp $LIBS
}
if [ "$1" = "run" ]
then
./$OUT
build && ./$OUT
elif [ "$1" = "release" ]
then
CFLAGS="$GFLAGS $RFLAGS"
build;
else
build;
fi

View File

@@ -52,7 +52,7 @@ constexpr u64 clamp_to_width(const DrawState &ds, f64 val)
(val - ds.bounds.lower_val);
}
void draw_tree(const DrawState &ds, const State &state)
void draw_tree(DrawState &ds, State &state)
{
// Number line
DrawLine(0, HEIGHT / 2, WIDTH, HEIGHT / 2, WHITE);
@@ -63,6 +63,7 @@ void draw_tree(const DrawState &ds, const State &state)
DrawLine(lower_x, LINE_TOP, lower_x, LINE_BOTTOM, WHITE);
DrawLine(upper_x, LINE_TOP, upper_x, LINE_BOTTOM, WHITE);
state.mutex.lock();
std::stack<std::pair<u64, f64>> stack;
cw::node::Node n = state.allocator.get_val(0);
stack.push(std::make_pair(0, n.value.norm));
@@ -87,6 +88,7 @@ void draw_tree(const DrawState &ds, const State &state)
stack.push(std::make_pair(n.right, right.value.norm));
}
}
state.mutex.unlock();
}
using Clock = std::chrono::steady_clock;
@@ -114,13 +116,13 @@ int main(void)
std::string format_str;
u64 format_str_width = 0;
// Init threads
std::thread threads[] = {
std::thread(cw::worker::worker, std::ref(state)),
std::thread(cw::worker::worker, std::ref(state)),
std::thread(cw::worker::worker, std::ref(state)),
std::thread(cw::worker::worker, std::ref(state)),
};
// Init threads
#define THREADS 15
std::thread threads[THREADS];
for (auto i = 0; i < THREADS; ++i)
{
threads[i] = std::move(std::thread(cw::worker::worker, std::ref(state)));
}
// Setup raylib window
InitWindow(WIDTH, HEIGHT, "Calkin-Wilf tree");

View File

@@ -12,6 +12,7 @@ namespace cw::state
{
void DrawState::compute_bounds()
{
state.mutex.lock();
bounds.leftmost = state.allocator.get_val(0);
while (bounds.leftmost.left >= 0)
bounds.leftmost = state.allocator.get_val(bounds.leftmost.left);
@@ -19,6 +20,7 @@ namespace cw::state
bounds.rightmost = state.allocator.get_val(0);
while (bounds.rightmost.right >= 0)
bounds.rightmost = state.allocator.get_val(bounds.rightmost.right);
state.mutex.unlock();
bounds.lower_val = std::floorl(bounds.leftmost.value.norm);
bounds.upper_val = std::ceill(bounds.rightmost.value.norm);

View File

@@ -29,14 +29,14 @@ namespace cw::state
struct DrawState
{
const State &state;
State &state;
struct Bounds
{
cw::node::Node leftmost, rightmost;
f64 lower_val, upper_val;
} bounds;
DrawState(const State &state) : state{state} {};
DrawState(State &state) : state{state} {};
void compute_bounds(void);
};