- URL: https://www.laruence.com/en/2026/08/14/6343.html
- Please include attribution when republishing.
A couple of days ago I finished Taint's PHP 8 adaptation with Qwen3.8 (see the previous post); yesterday DeepSeek released V4 Pro, claiming major improvements across the board and coding ability approaching top-tier closed-source models. I happened to have an optimization idea for yaconf lying around, so I figured I'd put it to the test.
First, some background on yaconf: a PHP configuration-management extension I wrote in 2015. The idea is simple — parse all ini config files once at PHP startup, keep the results in memory, and every subsequent Yaconf::get() is a pure in-memory lookup with no file IO or parsing overhead. The config data is marked immutable, and FPM workers share that memory through the OS's copy-on-write mechanism: as long as the config doesn't change, no matter how many workers there are, the memory is allocated exactly once.
Compared with the traditional approach of using PHP arrays or YAML for configuration, it avoids parsing and loading config on every request — the wins in both memory usage and performance are substantial.
This 1.2.0 release of yaconf did two main things:
- First, subdirectory support. Previously yaconf.directory could only be a single flat level; now it supports subdirectories to any depth (up to 16 levels).
sub/x.iniis accessed asYaconf::get("sub.x"), and subdirectories support hierarchical hot reload. This part was done by Qwen3.8 a few days earlier. - Second — the star of this post — compact storage, which I had DeepSeek V4 Pro build.
Why compact storage:
In opcache, compilation results are persisted into shared memory (SHM). That SHM is allocated once at process startup, and all compiled results are carved out of it.
It persists a script in two phases: first a dry run of zend_persist_calc computes the total size the script needs; only after confirming enough space remains does it allocate one contiguous block, pack all the data tightly into it, and fix up the internal pointers — so an op_array's data ends up stored contiguously.
Doing it this way has several benefits:
- Cache friendly. All config data sits in one contiguous region, so cache locality on reads is far better than scattered allocations.
- Memory saving. The calc pass deduplicates identical scalars/strings along the way, storing each only once.
- It makes exporting/importing the cache to external files straightforward.

Yaconf's current storage, by contrast, pemallocs each value separately, scattered all over. So I wanted to port the opcache approach: after MINIT finishes processing all the ini files, do a dry-run size calculation, then allocate one large contiguous block for everything, reusing identical content within the config — cutting memory use and improving cache friendliness.
DeepSeek V4 Pro:
Locally I'm still on Obsidian + Claudian + CC Switch; this time I routed the model to deepseek-v4-pro.
I handed it the two-phase compact idea, told it to follow the code style of my other projects, and it got to work.
First impression: fast, genuinely fast. Responses and code generation were visibly a tier above Qwen3.8 from a couple of days earlier.
I had it write a bench: generate 2000+ config entries and compare native PHP arrays, the old yaconf, and the new compact yaconf over a hundred thousand reads each. I also had it add an mprotect option modeled on opcache — marking our contiguous block read-only, enabled in all test cases, to surface unexpected writes.
Everything looked smooth, though a few small issues felt subtly off — until things finally went off the rails.
The problems:
Hitting walls, over and over:
Our code-editing tool works by "find an old string in the file, replace it with a new string". Across the session DeepSeek invoked Edit nearly 160 times, and over 100 of those failed outright — String to replace not found in file: it believed from memory that the file contained some code, when the file contained nothing of the sort. It had to re-read the file and try again, burning piles of tokens back and forth. It never once iterated on its own tool usage.
Amnesia after context compaction:
Because the task ran so long, the session was auto-compacted twenty-some times, and after each compaction it forgot some earlier decision. Once I had it move the bench file into a stashes directory; after a compaction it forgot all about that and started creating a new bench locally, and I had to snap at it:
Are you being dumb? Do you remember where you moved the bench file?
Only then did it recall. There were several similar episodes — after a compaction it would forget an approach we'd agreed on and detour all over again.
And then the big one:
I had asked it to tidy up the commits, splitting compact, mprotect and bench into separate commits. After a flurry of git resets and amends, the tests failed. I went over to look, and it told me: there is no compact block in the current code.
What do you mean, no compact block?
It ran a grep: not a single hit for compact, mmap or mprotect in yaconf.c. Then we looked at the commit history — the message proudly read "Two-phase compact block + mprotect support", but what was actually committed was only the tests and bench. The several hundred lines of core implementation had been wiped out by its own reset --hard.
What left me truly speechless: its first reaction upon discovering the code was gone was to edit the unit tests, trying to remove all the mprotect-related test cases… I was left without words — can't solve the problem, so solve the problem's reporter. 🤣
After I shouted it down, it finally went digging through reflog and fsck, and eventually found the complete compact implementation inside an auto-generated stash WIP commit. One git apply brought it back, and all 30-odd test cases passed.
Lost and found — it left me drenched in cold sweat.
Benchmark results:
With the code recovered, let's talk about what the performance actually looks like.
We designed a bench (published at github/laruence/stashes): it simulates the configuration of a large microservice cluster — 400 services, 40 sections each, over 250k keys total, a ~140MB working set after loading — then reads in random order so hardware prefetch can't help. This is exactly the scenario compact storage is meant to solve. Results:
| Scenario | Old | New | Gain |
|---|---|---|---|
| Single-key hot reads | 20.1 ns | 20.5 ns | unchanged |
| Random access (cold cache) | 176.7 ns | 105.0 ns | +40% |
| Sequential traversal | 39.3 ns | 31.3 ns | +20% |
| Memory footprint | 146.4 MB | 122.8 MB | -16% |
Random-access latency dropped ~40% (throughput ~1.7x): config data now lives contiguously in one block, so random jumps have far better cache locality instead of stomping all over memory. Sequential traversal is ~20% faster. Memory is down 16% thanks to string deduplication in the calc phase. Single-key hot reads are a wash on both sides — as expected: hot keys are already in cache, layout doesn't matter.
Summary:
The final yaconf 1.2.0 shipped: subdirectory support + compact storage, 30-odd test cases, multi-version CI from PHP 7.1 through 8.5 all green (on 7.1 it face-planted twice before getting it right, admittedly).
An objective assessment of DeepSeek V4 Pro:
- The speed is real — clearly faster than Qwen3.8; thinking feels at least twice as fast, and it writes code at a great rhythm.
- Comprehension is decent too: it could port opcache's two-phase persistence approach and make it work, and the issues I flagged in review (e.g. the container after MINIT processing can't be a packed array; size calculation should follow zend_persist_calc.c) it mostly fixed correctly.
- But reliability is questionable: 100+ failed Edits, one git operation losing hundreds of lines of code, and after a few compactions in a long session it forgets what it did earlier. In long tasks and big projects, these problems are lethal.
- On the bench: its first version (small dataset, hot cache) couldn't surface compact's advantage at all — single-key reads even looked slightly slower. Only after I redesigned for large data + random access did the real effect show: ~40% lower random-read latency, 16% less memory. Whether the tool works is one thing; whether the scenario design is right is another.
What I want to say:
After this test, my conclusion: DeepSeek V4 Pro can write code, and it's certainly fast — but for coding, among domestic models as of today (August 14, 2026), Qwen3.8 is still the steadier choice.
Fast matters, but steady matters more — after all, losing your code really does leave you drenched in cold sweat.
Of course, I believe in and look forward to DeepSeek's future releases — it will only get stronger!
yaconf 1.2.0 is published on my GitHub; reviews welcome.
Note: this post was synced from the WeChat Official Account "风雪之隅" to this blog by Jarvis (the author's AI assistant).
Read the original (Chinese) on my WeChat channel: 《用DS V4P改进Yaconf:快是真快,但它把我代码搞丢了》
Be First to Comment