Well, I have ADHD. I've found the most effective approach (on top of treatment) that helps me retain focus is reexec-on-save, a la `while :; do tput clear; $thing; inotifywait -q -e moved_to .; done`. I usually have a dozen of those in old shell histories (^R FTW). (Ha, my laptop actually has exactly 12, and my other machine has 23 - although ignoredups
is off...)
$thing might be `bash ./script.sh` (because my text editor's atomic rename doesn't understand execute bits >.>), `php script.php` or `gcc -O0 script.c && ./script`. (Also, as an aside I used to use `-e close_write $file` until I realized watching even giant directories is equivalently efficient to watching a file.)
Shell scripts (the small kind that run few subprocesses) are typically fast. Likewise, small C programs of But for better or worse, PHP is currently the language I use the most. Because it's faster than Python and Ruby.
A while back I wanted to do a bit of analysis on a dataset of information that was only published as a set of PDF documents... yayyy. But after timidly gunzipping the stream blocks and googling random bits of PDF's command language ("wat even is this"), I discovered to my complete surprise that it was trivial to interpret the text coordinate system and my first "haha let's see how bad this is" actually produced readable text on pretty much the first go. (To be pedantic, step #-1 was "draw little boxes where the text should be", step #0 was "how to x,y correctly" (with a side serving of "...those boxes look quite reasonably positioned..."), and step #1 was "replace boxes with texWHAT it worked?!")
With rendering basically... viable (in IIRC 300-500 LOC O.o), the next step was the boring stir-the-soup-for-144-hours bespoke state machine that cross-correlated text coordinates with field meanings ("okay, that's a heading, and the next text instruction draws the field value underneath. OK, assert that the heading is bold, the value is not, and they're both exactly the same (floating-point) Y position").
While that part took a while, it was mostly extremely easy, because I was pretty much linearly writing the script "from start to finish", ie just chipping away at the rock face of the task at hand until I processed an entire document, then the next document ("oh no"), then the next one ("ugh") and so forth ("wait, the edge cases are... decreasing? :D"). My workflow was pretty much founded entirely on the above-noted method where I would re-exec the script from scratch upon save.
Loading/gunzipping a given PDF and getting to the point where the little pipeline would crash would typically complete well before I had a chance to release the CTRL key after hitting CTRL+S. So while the process was objectively quite like stirring soup, it did not feel like that at all and I was able to kind of float a bit as my brain cohesively integrated the mental model of the architecture I was building without any distractions, pauses or forced context switches getting jammed in the mental encoding process like so many wrenches.
Soon 15 documents were handled correctly, then 20, then 30, then 100 ("oooh, if all the items on the page add up exactly right it pushes line 2 of the summary heading down to the second page! Hmmm... how on earth to special-case that without refactoring to look at more than 1 page at a time..."), and then I hit some sort of threshold and it suddenly just started ticking through PDFs like crazy without asserting. Which was both awesome and a Problem™: the thing ran at something like ~60 PDFs/sec, and while jumping to just after the last successfully-processed PDF on restart worked great when the code crashed constantly, now I was sitting spinning for tens of seconds, getting distracted as I anticipated the next crash. ADHD(R)(TM).
I wasn't surprised to learn from htop that the script was disk-bound; for some reason my ZFS mirror setup will happily read sequentially at 200MB/s, but thousands-of-tiny-files situations are... suffice to say apt unconditionally takes 60 seconds to install the smallest thing, unless the entire package db is in the FS cache. I'm not sure why. The PDFs were sharded sanely, but they were still in separate files. So I decided to pack them all into a giant blob, and since there weren't too many PDFs and they were numbered sequentially I used a simple offset-based index at the front of the blob where `fseek(data_start + ( * 4)); $o = fread(4); fseek($o);` would give me random seeking when I needed it, or I could just fseek() to data_start and start reading directly.
Reading the blob instead promptly pegged a single CPU core (yay!), and gave me IIRC ~150-200+ PDFs/sec. This was awesome. But I was still just a tiny bit curious, so after googling around for a profiler and having a small jawdrop moment about SPX (https://github.com/NoiseByNorthwest/php-spx), I had a tentative look at what was actually using the most CPU (via `SPX_ENABLED=1 php ./script.php`, which will automatically print a one-page profile trace to stdout at graceful exit or ^C).
Oh. The PDF stack machine interpreter is what's taking all the CPU time. That tiny 100 line function was the smallest in the whole script. lol
So, I moved that function to the preprocessor/packer, then (after some headscratching) serialized the array of tokenized commands/strings into the blob by prefixing commands with \xFF and strings with \xFF\xFE\xFF so I could explode() on \xFF and tell commands from strings by checking if the previous entry was \xFE (and just skip entries of '\xFE' when I found them) :D. Then I reran the preprocessor to regenerate the pack file.
$ php convert_dlcache.php
Scanning...
24/66060
67,927 entries [4.27 sec]
Sorting... 10013-343271 [1.61 sec]
data_start=1373094
* 67,920/67,927 99.99% 83/s 00:00 13:58 343259
Thankfully I've only needed to run the preprocessor rarely, like for example I only needed to run it twice today because it promptly truncated its pack file when I accidentally reran it after the first run (yay). Yes, it really does take ~14 minutes, and yes, there are just under 70,000 PDFs.
Then I reran the pipeline script.
$ php conv2.php
67,927/67,927 890/s 01:16 00:00 343271
Complete
Oh. 900 PDFs/sec.
Uh, what happens if I... set up a simple pcntl_fork() + stream_socket_pair() multi-process worker system...?
$ php conv2.php
Reading... done
4 workers, (16,982 x 3) + (16,981 x 1)
62,162/67,927 1,800/s (469/s 166319, 444/s 237743, 442/s 302338, 444/s 340327) 00:34 00:03
^C
"Oh. Okay."
":D"
(The workers disappear from the output as they complete, so I ^C'd it just before they all exited, at 3 seconds left.)
So. 68,000 PDFs/sec on a not particularly amazing 3.3Ghz i3-3220 with 1600MHz RAM.
PHP 8's JIT is aweso--wait, is the JIT actually on?
Oh. It's off by default.
$ php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=32M conv2.php
Reading... done
4 workers, (16,982 x 3) + (16,981 x 1)
58,206/67,927 2,537/s (669/s 163803, 623/s 230254, 619/s 300420, 627/s 338470) 00:22 00:03
*
Blinks*
(In small voice) "I am processing/unit-testing 68k documents in 22 seconds. At over two and a half thousand PDFs a second."
I also noticed that
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
31511 i336 20 0 276M 19864 5736 R 100. 0.2 0:18.25 php conv2.php
31513 i336 20 0 276M 19856 5732 R 100. 0.2 0:18.35 php conv2.php
31512 i336 20 0 276M 19728 5600 R 100. 0.2 0:18.32 php conv2.php
31510 i336 20 0 276M 19764 5648 R 97.7 0.2 0:18.03 php conv2.php
31509 i336 20 0 275M 34072 20036 S 0.7 0.4 0:00.19 php conv2.php
the workers are only using 20MB RAM each. This is with object deferencing going on, after I kinda started going crosseyed and properly moved my JBOGF (Just a Bunch of Globals and Functions) into a proper class (albeit a static one, since I'm still learning, and construction/deconstruction would probably slow things down too). I also note that with the JIT off, VIRT is 115M, RES is 16,312K and SHR is 2,412K for all workers, with the master taking 29M RES and 15M SHR.
--
Why'd I write all this?!
To make the point that a) live reexec on save is an awesome programming model when it can be applied, and b) PHP is my incredibly awkward gold standard reference, lol.
(In rerunning everything for this post I discovered the JIT and learned my script could go even faster!)
I've been keeping vague tabs on recent language developments from a bit of a distance for a while now, and am very interested to dig into Rust and Zig's incremental compilation capabilities at some point.
Based on what I'm hearing I don't know if I should just go dive in yet though - to be entirely honest, there are multiple realms of applications I simply cannot write in PHP - including things as simple as console CLIs, because the neccessary TTY I/O control (eg, turning TTY echo off and reading 1 char at a time) remain unavailable (see also: multiple years-old long-forgotten bugs/feature requests).
I don't really want to discover what's possible only to find myself between a rock and a hard place as my programs start to grow and I hit invisible quadratic brick walls that persist after I enable every "fastest possible" setting the language offers. That happened a few years ago when I tried to play around with FLTK in C++; once build times were around the 3-4 second mark (*with* -O0 and precompiled headers), regardless of the fact that I'd been working on the project for enough time I was invested in it, I just gave up.
Much more recently I installed .NET Core the other day to run and dissect a small F# program. Running `dotnet fsi` cold took 5 seconds to reach a REPL prompt (ouch), and while warmed-up reruns would only take 1 second, I found actually loading a ~300-line program would take a non-reducible ~4-5 seconds.
Everyone's different, and sometimes things people say can look crazy, or stupid, or "...how even...", but they can still be true for that person. For me, waiting 4 seconds for an interpreter/runtime/etc to reach a useful milestone such as "I can actually interact at all with it" is untenable. Yup. I go straight to "I've waited for this thing to do the thing 200 times today " after the 3rd pause. I am utterly incompatible with the Old Guard "code's compiling!" way of doing things. It puts me straight into defensive not-in-my-comfort-zone mode.
Completely open-minded about Crystal, which I've heard repeatedly good things about. Yes, of course, I haven't tested it (yet), again because I don't want to go "wow this is awesome... except I don't have the patience for it :(".
The status quo noted above remains an actively unsolved problem; trying to figure out how many interesting things I can wedge into the "focusable space" defined by "how big I can make my program before the programming languge slows down" is getting boring.
Perhaps I'm just stuck here because I'm using older(ish) hardware, and once I finally figure out that problem I'll suddenly realize all of this deep analysis was unnecessary. heh