Simple thread safe caching in Raku

Tags:

So I posted yesterday about the weekyl challenge and mentioned caching and how I'd not done it because I liked the mathematical simplicity. Still Markus Holzer who's been known to tweet complete solutions to the challenges, suggested it and I thought I'd take a look.

The first thought is to use the is cached trait the problem is it's experimental and when I just added it to the square function it crashed. This is possibly because I only cached the function part that does a check and not the other part of the multi or possibly due to threading. Still it got me thinking about how I would want to do a thread safe cache implementation (without pulling in an external module like OO::Monitors ).

I took a look at the Lock class and for what I was thinking about (a cache hash that gets updated in a thread safe fashion) it should work. The updated square function now looks like this :

# Not caching, not need a no calculations involved.
multi sub square(Int $ where * <= 0) returns Bool { False }

# Note these are generated outside the sub routine so shared across threads.
my %square-cache;
my $lock = Lock.new;

multi sub square(Int \i) returns Bool {
  return %square-cache{i} if defined %square-cache{i};
  my \r = i.sqrt.narrow ~~ Int;
  $lock.protect( { %square-cache{i} = r } );
  return r;
}

Note I've updated the test using the narrow method again as per Markus's thoughts. But the code is pretty simple. Return the value from %square-cache if it exists, this isn't locked as it's a read and the worst comes to the worst you may have two threads try reading it at once, as they always write the same values it's not the worst. We do wrap the cache setting in a protect call to ensure we don't have multiple threads trying to update the cache at once.

I did try the code without the Lock and sometimes it works, sometimes it seg faults. Threading is hard y'all. Still I found with this in place there was a small but noticable speed increase for the 6 digit run. If I wasn't currently using a VM I'd try the 9 digit run again. On the bright side it doesn't seem to use a huge amount of memory. The 6 digite run making roughly 6000 cache keys.

Anyway, possibly more soon but I hope this was interesting.

Update

After I posted this Elizabeth Mattijsen shared it on Reddit with a note that accessing a Hash can cause a race condition (I figured it would be safe but I defer to Liz in such things as she's really smart).

Her updated code is even simpler, I'd put it here but my blogging platform is being weird, go checkout the link. The protect returns the value inside the block and we use //= to set the cache value, and as she also mentioned the return is superfluous and in these kind of cases I generally leave it out. So that's even cleaner and works fine. Awesome.

Thanks again Liz.

Weekly Challenge in Raku Week 102

Tags:

This weeks challenges are quite fun and simple enough (after a bit of thought). I'll quickly run through my results and some thoughts on doing it in Raku.

Challenge 1

So I learn lots of fun maths stuff doing the weekly challenge. Rare numbers are weird and there's not many of them. The code I came up with goes like this :

sub rares(UInt \l) {
  ((10**(l - 1))..((10**l)-1)).race.grep( -> \i { rare(i) } );
}

So lets break that down : ((10**(l - 1))..((10**l)-1)) give us the range of all l digit long numbers. Then we put in a race to get threading easily then we grep with our rare function... And here it is :

sub rare(Int \r) returns Bool {
  my \f = r.flip;
  square(r - f) && square( r + f );    
}

So here we flip the value to be tested (note we're using Raku's dynamic typing here. r is an Int, flip is a String method so f is a string but then we treat it as an Int straight after. Nice and simple. Of course now we've got another function, square.

multi sub square(Int $ where * <= 0) returns Bool { False }

multi sub square(Int \i) returns Bool { my \s = i.sqrt; s == s.Int; }

Our square function tests if our value is a perfect square. Firstly if it's a negative number then it's never going to work (I learned that the hard way testing if 12 is a rare number). Then if not we calculate the square root and see if it matches the Integer casting of itself.

Of course here be some dragons, calcualating square roots is horrible and slow for larger numbers. (The 9 digit run of this code takes... well I've got bored waiting for it on my VM that I'm currently using). I thought about adding caching but then there's the fun of that and threading so I decided to stick with the functional purity of the code.

It's pretty and I like that, if you want it to be fast get some more cores ;) My full solution includes a test suite and a main wrapper but they are pretty simple.

Challenge 2

At first glance this seemed quite complicated. Then I had a little thought about it and the word recursion popped up in my brain. It does that quite a lot, but this time it seemed to be right. Seriously you can't use recursion to solve every problem, I mean how to you recurse chosing what you want for dinner? (Answers on a postcard please).

Anyway. Lets have a think. We want a function hash-count that takes and integer and returns a string. If the integer is 1 you return a "#" and if it's 0 you return ""...

multi sub hash-count(1) { "#" }
multi sub hash-count(0) { "" }

So those definitely look like ending conditions of a recusrsive function. For any other positive integer the string ends with the number (call it $x) and a "#". This is put after the result of calling our function with $x less then the length of our string. So if $x is 5 then we have 5# which is length 2 so we call hash-count(5-2) and bingo... recursion.

multi sub hash-count(UInt $x) {
  hash-count($x - ($x.chars + 1) ) ~ $x ~ '#';
}

And there we go, nice and simple. Again Raku's multi method calls make recursion a doddle, probably why I reach for it so often when doing challanges.

I hope this has been interesting. More later.

Ramble blog

Tags:

So I didn't blog yesterday, I had slept really badly and had a headache all day. I spent much of the day playing Valheim and dying a LOT.

It's punishing when you die far from home and all your good stuff was there, then you head out in your second best stuff and die again so not you're doing a corpse run in your pants...

I ended up bringing in my character from my local game to our shared server to save the day. Then he went exploring on my local game, found some Swamps and got killed by a leech. Ooops. Tonight I'll be fixing that.

Today I'm going to write a ramble blog, this is basically a post I will keep updaitng during the day as ideas come to me. My blog in my first coding job was like that. It got so good it was number one on search results for our company. Ooops again.

I'll look at the weekly challenge in a bit but firstly I need to buckle down on day one of my new contracting job.

Every day

Tags:

So I said I'd blog every day and then I spent all day playing Valheim on a shared server. Which was some well needed social interactions (thanks to Discord).

I did an experiment after some copper ore collapsed due to gravity so I tried digging down under a whole deposit. Turns out copper floats. Also Copper Deposits are huge!

Copper Floats

So if you're playing Valheim make sure you're not leaving copper underground.

Anyway. That's about it for me today. Tomorrow is the last day of my improptu holiday then I start my first contracting job, new things and all that. I'm feeling very positive.

Also I've got some players for a Numenera games so fingers crossed I can manage it without going screwy. Plus I can blog about it which will give me something to write.

Random Ponderings

Tags:

So as I'm sitting here my wonderful amazing stepdaughter has decided she needs to watch the 2019 Eurovision Song Contest. This is, as they say, a thing.

In case you're intested I got the job, 3 months contract starting Monday. Which is pretty awesome. Despite, or possibly because, of my difficulities in social situations I tend to interview well. I find that being honest and enthusiastic goes a long way. Having now done a lot of interviews (which are really tiring) I know that a lot of it comes down to how you come across.

I've said it often enough in my life that growing up in a pub and waiting tables from an early age really helped me. You need to learn how to deal with people and I did it a lot.

(Side note the Australian Entry.... bizarre)

So I've had a week off and mostly played Valheim because it's hecka addictive. But I've also spent some time using Godot and I'm going to try to keep working on projects over the next few months.

And I'm going to try and keep writing here, even if it's nonesense rambles like this. But right now I need to play a game with some nice loud music.