Part 50: Let's go RAIDing
Part 50 - Let's go RAIDing=== Trash World Inbox ===
Last time, hacking the school, I got 710, 55 and 8 as best scores.
We got one improvement from Quackles this week.
Quackles posted:
After the whole craziness that was last week's, this week is going to be straightforward. I don't have as much of an optimization on cycles as CO2, but I've been able to improve on activity a bit.This code runs in two EXAs, one of which splits during runtime. This is the first one. It travels to Hunter's class schedule file, gloms onto it, then sends the keyword 'AP ENGLISH' to the other EXA (which will search the global class list to find its time). Then it splits, spinning a copy of itself to MESSENGERBOT (see below).code:
;CLASS SCHEDULE WRITER EXA - STARTS GLOBAL M GRAB 300 SEEK 1 COPY F X ;'AP ENGLISH' LINK 800 LINK 800 LINK 802 COPY X M ;SEND 'AP ENGLISH' COPY 0 M ;NO AVOID TIME DROP GRAB 235 REPL MESSENGERBOT MODE ;LOCAL MARK REPLACELOOP SEEK -9999 SEEK 2 COPY M X ;GET TARGET TIME ;REPLACE LOOP WAS UNROLLED TEST X = F TJMP REPLACE SEEK 1 TEST X = F TJMP REPLACE SEEK 1 TEST X = F TJMP REPLACE SEEK 1 TEST X = F TJMP REPLACE SEEK 3 ;SKIP LUNCH TEST X = F TJMP REPLACE SEEK 1 TEST X = F TJMP REPLACE SEEK 2 ;NO NEED TO TEST IF LAST ITEM IN FILE MARK REPLACE COPY F X ;GET OLD CLASS SEEK -1 COPY M F ;REPLACE CLASS NAME COPY X M ;SEND OLD CLASS JUMP REPLACELOOP
Once MESSENGERBOT is started, the split (messenger) EXA will handle all communications with the global class list. This EXA just gets the time slot to write to from the messenger, finds that time slot in Hunter's schedule, overwrites the new class name, and sends the old class name.
It's worth noting that the loop where we search Hunter's schedule has been unrolled, allowing us certain optimizations. We can skip over the 'lunch' row entirely, and if we reach the end row, we don't need to test if it'll be the one we're looking for - it'll have to be.The messenger acts as a cache for the name of the class we just replaced, and also sends that name to the global class list EXA and gets the correct time slot back. It sends the cached class name and the received time slot to the writer, then gets the name of the class that was replaced out to start the process over again.code:
MARK MESSENGERBOT GRAB 300 MARK MSGRLOOP COPY M T ;GET TIME SLOT TO REPLACE MODE ;LOCAL COPY T M ;TELL WRITER TIME SLOT COPY X M ;TELL WRITER NEW CLASS NAME COPY M X ;GET OLD CLASS NAME MODE ;G ;CLASS COPY X M ;SEND OLD CLASS NAME COPY T M ;SEND OLD TIME SLOT AS AVOID SEEK -1 TEST X = F ;ENGLISH FJMP MSGRLOOP VOID M COPY 0 M WIPE ;HALT KILL
Note that we also send out the time of the class we just replaced. This is used to avoid replacing the wrong class when searching the class list.
When the class we replaced is ENGLISH (we hold on to the file with ENGLISH so we can check this), we know we're done. In this case, we KILL the writer EXA, tell the class list EXA to quit, and finish up.This EXA is a very simple search loop. It starts at the front of the class list and searches for the class name sent in over M. Once it finds it, it reads a second value over M. This is the time we want to avoid. It checks that against the time of the class we've found in the file. If it's a match, we search again for the other instance of that class. Either way, once we find the correct version of the class, we return its time over M.code:
;GLOBAL CLASS LIST SEARCH EXA LINK 800 GRAB 200 ;TAKES TWO VALUES: ONE TO FIND, AND A TIME TO AVOID ;THEN FINDS A CLASS AND A TIME AND SENDS THEM OUT MARK FINDLOOP COPY M T FJMP DONE COPY T X SEEK -9999 MARK FINDINFILE SEEK 1 TEST X = F FJMP FINDINFILE ;SECOND TEST - IS THIS ;AT OUR 'AVOID' TIME? SEEK -2 TEST F = M FJMP SEND SEEK 1 MARK FINDINFILE2 SEEK 1 TEST X = F FJMP FINDINFILE2 SEEK -1 MARK SEND SEEK -1 COPY F M ;TIME JUMP FINDLOOP MARK DONE
If we receive a 0, we stop. That's it.
804/83/5. The trick to getting a low activity score is sticking to the use of M as much as possible. I could probably get even lower if I were to REPL the writer EXA off from the class search EXA.
Fun fact: I also tried making a search table out of the class file (in the form Class, Time1, Time2), and then searching through it quickly for both times when needed. But this strategy takes 3-4 times as long!
It is indeed easy to change your code to get the activity all the way down to 3. As you say, put all the code in one big EXA, then REPL XB off after the initial LINK 800. Since the original XA is holding file 300 it can go on to the grade-11 host. The other activity point comes from that KILL which can be removed if the messenger, when done, also sends a 0 in LOCAL mode, and the writer checks for that the same way the reader did (temporarily store it in T, try an FJMP, then copying it to X).
=== Personal Storage Array ===
OST: Code and Registers
Alright, so x10x10x stored their anime on a drive array with three disks where all data is copied across all the disks for redundancy. However, this redundant array of independent disks is completely failing and it's up to us to retrieve their anime. Let's look at the assignment first.
- The data on this drive array is duplicated across three drives for redundancy, with a file name index stored in the controller (file 200). Unfortunately, the drive array is failing.
- For each file stored in the drive array, create a file in your host that contains the file name and data. Some values are corrupted and will read as a keyword (FAIL) instead of a number. You will need to read these values from a different drive that is not corrupted.
- Note that some links may be unreliable as a result of the drive array's impending failure.
That's not good. That drive is completely busted and I'll be lucky if I can get anything off of it.
The third point means that links 801, 802 and 803 will sometimes just disappear and then return a couple cycles later. If that happens during M transmission, well, the EXA will just wait. But if that happens when the EXA tries to LINK there, it will run into a wall and die.
Because everything is so unreliable, I'm going to handle the individual drives one by one. It'll be slower than parallelization but I have no idea how to make any sense of concurrent global M communication if links just randomly drop.
I have to admit, this puzzle gave me more trouble than I expected. It's mostly that whenever I thought I had all parts working together, one or two tests would screw up everything because their links dropped in an unexpected order. I ended up rewriting major chunks of my code several times before I got it working.
I think showing my thought process step by step as I'd do normally would be a bit confusing. So, instead I'll walk you through my actual working solution.
I'll start with XA which is rather simple but takes some work off of the main EXAs.
It starts with simply copying all the file IDs and file names from the index file. Once that's done, it will switch into local mode and let a visiting EXA know the next drive to LINK to. After each drive link ID it will also send a magic number, the purpose of which will become clear later.
Then to XB. It handles the bulk of the logic and does so by making many REPLs.
code:
;XB
COPY 7 T
MARK NEXTFILE
SUBI T 1 T
FJMP KICKSTART
MAKE
COPY 0 F
COPY M F
COPY M F
REPL NEXTFILE
SEEK -2
MODE; LOCAL
COPY M X
But it also REPLs itself and the REPL will run the same code for the next file. This repeats for all 6 files, after which the final REPL jumps to KICKSTART.
When this code is done, there are 7 EXAs in the home host. 6 of them are holding a (still mostly empty) result file, the 7th is the one that will kickstart the next step.
Let's look at the kickstarter.
code:
MARK KICKSTART
LINK 800
MODE; LOCAL
COPY M X
MODE ;GLOBAL
MARK TRYDRIVE
REPL DRIVE
NOOP
TEST MRD
FJMP TRYDRIVE
VOID M
MODE ; LOCAL
COPY M X
LINK -1
COPY X M
;HALT
;------
MARK DRIVE
LINK X
COPY 0 M
GRAB M
KILL
It is possible that the link dies in the one cycle between the DRIVE EXAs LINK command and the M message. In that case, the EXA will have successfully reached it, but TRYDRIVE makes another instance regardless. This could even happen multiple times.
Now, that other instance can't actually link to the drive because with the one EXA in there, it's full. It'll either try repeatedly until it bonks into a missing link and dies, or it'll actually make it after the actual DRIVE EXA makes some space by GRABbing a file. That's why that EXA needs a KILL after the GRAB.
This bit of code actually gave me the most trouble. To save on activity, I tried to have XA handle all this, but then there's messages going from the drive to XA and from XA to the XBs... and it all seems to work until some link disconnects at the worst time and everything desyncs. Making the kickstarter personally responsible was the only solution that worked for me.
Anyway, we now got a stable EXA in the drive, and the kickstarter VOIDs its M. It then jumps back to local mode, copies the magic number from XA, goes back to the home host, and copies it to one of the six waiting EXAs (randomly chosen). Then the kickstarter's work is done and it stops.
Before we go back to the six EXAs at home, let's see the rest of DRIVE's logic.
code:
MARK DRIVE
LINK X
COPY 0 M
GRAB M
KILL
MARK COPYFILE
COPY F M
TEST EOF
FJMP COPYFILE
COPY 0 M
DROP
GRAB M
JUMP COPYFILE
As a note, I tried to see if swapping the COPY 0 M and DROP made the code any faster, in case that M message was slow. It didn't - the solution was one cycle slower overall. But more importantly, doing so increased the activity. It's something I noticed a few times in this task. Even the smallest timing difference will change where the EXAs are in the code when the links are open, and getting to the drives will get harder or easier.
The 6 EXAs in the home host all have a file (with the cursor sitting on the file ID) and are waiting for an M message. The kickstarter will send one, the magic value 1 from XA.
code:
COPY M X
MODE
COPY F M
SEEK 1
COPY M T
MARK NEXTCOPY
COPY T F
COPY M T
TJMP NEXTCOPY
TEST X = 6
TJMP NEXTROUND
MODE
ADDI 1 X M
HALT
This way, one by one, all of the 6 EXAs will copy the data of their file from the first drive, and then just pass the baton to the next one. The random order doesn't matter because the EXAs run one by one and each knows which file ID it needs.
None of this code needs retries because if the link is down, the M calls will just wait.
The final EXA won't die, instead it will jump to NEXTROUND.
At this point, there are 6 files with partially corrupt data in the home host, one being held by the sole surviving EXA. The DRIVE EXA is waiting for a next file to pick up, and XA wants to send the next drive's link ID on local M.
code:
MARK NEXTROUND
COPY 0 M
REPL KICKSTART
DROP
COPY 400 X
MARK GRABNEXT
GRAB X
ADDI X 1 X
REPL GRABNEXT
MODE
COPY M F
The NEXTROUND EXA will use a similar REPL strategy as before, to make 6 EXAs each holding a file. This time it just uses a counter to go through all homemade file IDs (starting at 400), and the final REPL will die as it tries to grab a file that doesn't exist.
Again, all of them will wait for a message from the kickstarter, and it doesn't matter who gets it first. Importantly, this time the value isn't stored to X but in the placeholder at the start of the result file.
code:
MODE
COPY F M
SEEK 1
MARK FIXNEXT
COPY M X
TEST X > -9999
FJMP SKIP
COPY X F
TEST EOF
FJMP FIXNEXT
JUMP DONEFIX
MARK SKIP
SEEK 1
TEST EOF
FJMP FIXNEXT
I did have to use X > -9999 instead of X < 9999 here because it turns out 9999 is a valid number in one of the tests.
DONEFIX is directly after this code, so no matter which branch hits the EOF, we end up here:
code:
MARK DONEFIX
VOID M
SEEK -9999
ADDI F 1 X
TEST X = 6
TJMP NEXTROUND
TEST X = 12
TJMP FINISH
MODE
COPY X M
TEST X > 6
DIVI 1 T T
JUMP CLEANUP
If the value equals 6, the code jumps into NEXTROUND once more. The second DRIVE EXA gets killed, a third gets created, and the kickstarter comes back with a magic value of 6. A new set of 6 EXAs will be spawned, each one holding a partially fixed file, and each one will run another FIXNEXT loop, using the correct values from the third drive.
But, with the magic value starting at 6, this DONEFIX code works slightly differently. After incrementing it the first time, this value will not be 6 so it will never jump into NEXTROUND again. If it's 12 it will jump into FINISH. Otherwise, it'll hand the baton to the next EXA, and jump into CLEANUP now since X is always larger than 6.
Almost there.
code:
MARK FINISH
COPY 0 M
MARK CLEANUP
SEEK -1
VOID F
VOID F
XA already died since it ran out of lines of code to run.
Quite convoluted, but it works. It might not be the best idea to let all 6 file writing EXAs die each round and recreate them but this was the best way I found to keep control.
This runs at 5293/111/14.
Apparently my cycle count is the kind of solution many people found.
Top percentiles are 1012, 61 and 4. I bet the faster speed is gotten through some kind of parallelization but I have no clue how to set that up. The links disconnecting keeps messing with every idea I might otherwise have.
By the way, the activity is 14 because of XA going to the controller (1), the kickstarter going to the controller and back thrice (6), every DRIVE EXA that actually makes it into a drive (3), and a total of two duplicate DRIVE EXAs that LINK into the drive and need to be KILLed (4).
If anyone has any better scores let me know.
Next time, we help deadlock book a trip.