AI 资讯
What Redis Is and When to Use It
Redis gets reached for reflexively, "just add Redis," as if it were a single fix for slowness. It's genuinely one of the most useful tools in a backend engineer's kit, but using it well starts with understanding what it actually is: an in-memory data structure store, not just a cache. Once you see it as a fast, versatile store of real data structures, the range of problems it solves cleanly (caching, rate limiting, queues, sessions, leaderboards, locks) stops looking like a grab bag and starts looking like one idea applied many ways. This is the opening article of the Redis Masterclass, and it builds on the PostgreSQL series : Redis usually sits alongside a primary database like Postgres, not instead of it. In-memory is the whole point Redis keeps its data in RAM. That single fact explains most of its character. Reading from memory is orders of magnitude faster than reading from disk, so Redis operations typically complete in well under a millisecond, and a single instance handles a very high request rate. That speed is why it's the default choice for anything on the hot path, where a database round trip would be too slow. The tradeoff is that RAM is smaller and more expensive than disk, and volatile. Redis addresses durability with persistence options we'll cover later, but the mental model to start with is: Redis is fast because it's in memory, and you use it for data that benefits from being fast to access, not as the permanent home for everything. It's a data structure store, not a key-value blob The common misconception is that Redis is a simple key-value store, strings in and strings out. It's much more. Redis stores real data structures as values, each with its own commands: Strings for simple values, counters, and cached blobs. Hashes for objects with fields, like a user record. Lists for ordered sequences and simple queues. Sets for unique collections and membership checks. Sorted sets for ranked data like leaderboards and priority queues. Plus streams, bit
AI 资讯
How to Detect Event Loop Freezes in Node.js
You've probably seen this: Kubernetes probes are green, /health returns 200, CPU isn't on fire - and users are timing out. Process is up. Just not doing anything useful. Classic event loop freeze. Something sync and expensive (or a dumb busy loop) grabs the thread, and suddenly timers, HTTP, DB callbacks - all of it waits. Including your health endpoint, because that also needs the loop. monitorEventLoopDelay() is fine for dashboards. It won't yell at you while the process is stuck, and it definitely won't write a log line from outside the frozen loop. That's the part that annoyed me enough to build this. I made @js-ak/watchdog - tiny N-API addon. The monitor runs in C++ on its own thread and writes JSON lines to stderr/file even while the JS event loop is stuck. Your freeze / recovered handlers only run after the loop unblocks. Optional RSS/CPU, optional stack sample. npm install @js-ak/watchdog const watchdog = require("@js-ak/watchdog"); watchdog.on("freeze", (e) => console.log(e.event, e.duration_ms)); watchdog.on("recovered", (e) => console.log("back after", e.duration_ms, "ms")); watchdog.start({ freezeThresholdMs: 1000, logTarget: "stderr", // or "file" | "both" source: "payments-api", }); Real freezes usually aren't while (true) . More like giant JSON.parse, a cursed regex, sync crypto/compress, or some dependency doing sync I/O on a hot path. Stuff you only notice after people complain. Prebuilds for the usual platforms (Node 22+). MIT. Repo: https://github.com/JS-AK/watchdog Curious how other people catch loop stalls in prod - and what you'd actually want in the freeze payload. Roast the API if it's wrong.
AI 资讯
eBPF for Networking (XDP)
Ethereal Bytecode for the Network: Unlocking XDP's Magic! Hey there, fellow tech enthusiasts! Ever felt like the traditional networking stack in your Linux kernel was a bit… sluggish? Like it was taking the scenic route when you needed it to be a supersonic jet? Well, let me introduce you to a superhero that swoops in and turbocharges your network packet processing: eBPF, specifically in the context of XDP (eXpress Data Path). Forget the days of wrestling with complex kernel modules or praying for better hardware offload. eBPF and XDP offer a revolutionary, in-kernel, safe, and incredibly efficient way to program packet processing at the very edge of your network interface. Think of it as giving your network card a tiny, super-smart brain, capable of making lightning-fast decisions before the packet even bothers the main kernel stack. Pretty cool, right? So, buckle up as we dive deep into the wonderful world of XDP and eBPF, demystifying its power and showing you why it's becoming the darling of modern networking. 1. The "What's the Big Deal?" Section: Introduction to XDP & eBPF Imagine a bustling highway (your network). Traditional networking is like having every car stop at a toll booth, get inspected, and then directed by a central traffic controller. This works, but it can get congested. XDP, on the other hand, is like having intelligent on-ramps where some cars can be instantly identified, rerouted, or even rejected before they even hit the main highway. eBPF (extended Berkeley Packet Filter) is the technology that makes this possible. It's a powerful, sandboxed virtual machine that runs within the Linux kernel. Unlike traditional kernel modules, which can potentially crash your entire system if written incorrectly, eBPF programs are rigorously verified by the kernel for safety and correctness before they are allowed to execute. This means you get the power of kernel-level access without the existential dread of a kernel panic. XDP (eXpress Data Path) leverages
AI 资讯
Next.js 16 Cache Components: use cache, PPR, and When to Reach for Each
Next.js 16 shipped Cache Components - the feature that finally lets a single route mix static HTML, cached data, and per-request dynamic content without splitting it into separate pages. It is Partial Prerendering (PPR) made stable, plus a new use cache directive that replaces the old unstable_cache and the awkward route-segment config flags. This guide covers what changed, the three content types you now think in, and the runtime-data rule that trips up almost everyone on day one. What actually changed If you were using the experimental PPR flag, it is gone. Cache Components is a single config switch, and it turns on the whole model - static shell, cached segments, and streamed dynamic content in one route. // next.config.ts import type { NextConfig } from ' next ' const nextConfig : NextConfig = { cacheComponents : true , // replaces experimental.ppr } export default nextConfig Once it is on, every piece of your route falls into one of three buckets. The whole mental model is learning which bucket each component belongs in. The three content types Static - synchronous code, imports, and pure markup. Prerendered at build time and served instantly from the CDN. Your header, nav, and layout shell. Cached - async data that does not need to be fresh on every request. Marked with use cache . Think product lists, blog posts, dashboard stats. Dynamic - runtime data that must be fresh (cookies, headers, per-user state). Wrapped in Suspense so it streams in after the shell paints. import { Suspense } from ' react ' import { cookies } from ' next/headers ' import { cacheLife } from ' next/cache ' export default function DashboardPage () { return ( <> { /* Static - instant from the CDN */ } < header >< h1 > Dashboard </ h1 ></ header > { /* Cached - fast, revalidates hourly */ } < Stats /> { /* Dynamic - streams in with fresh data */ } < Suspense fallback = { < NotificationsSkeleton /> } > < Notifications /> </ Suspense > </> ) } async function Stats () { ' use cache ' cacheL
AI 资讯
Defeating the Fargate Cold Start Chaos with SOCI
If you've ever hit deploy on AWS Fargate and watched the task sit in PENDING forever, this one's for you. I ran a small experiment with Seekable OCI (SOCI) the AWS thing that promises to fix Fargate cold starts. This article is what I learned. The good, the boring, and the "wait, that didn't work?" bits. You'll walk away knowing what SOCI is, whether you should bother setting it up, and what real numbers look like! Prerequisites ✅ You've deployed something on AWS Fargate before. You know what a Docker image is. You have an AWS account you're OK spending ~$5 on. That's it. 🤔 The Problem Fargate is great. Push a container, get a running task. No nodes to manage! Until you use a big image. Every Fargate task pulls the whole image before starting. No shared cache. No head start. If your image is 3 GB, your task waits for 3 GB to download. Every time. Every task. For an ML model or a heavy Java service, cold start becomes minutes, not seconds. Two stats made me actually care about this: 76% of container startup time is just downloading the image. Only 6.4% of that image data is needed to actually start the app. So you're pulling 100% to use 6.4%. And it costs you 76% of your startup budget doing it. That's ridiculous!! This isn't a Fargate-only problem, by the way. It's a container problem. But Fargate hurts more because you can't cache anything at the host level. I presented this talk at AWS Community Days Bangalore 2026 💡 What is SOCI? SOCI stands for Seekable OCI . Simple idea: Instead of downloading the whole image before starting the container, download only the parts the app needs right now. Everything else gets pulled lazily, in the background, while the app is already running. The way it works: You push your image to ECR like normal. A separate tool builds an index a byte-level map of what's in each layer. Fargate reads the index at startup and only fetches the bytes it actually needs to boot the process. As the app tries to read more files, Fargate fetches those
AI 资讯
Accidentally quadratic: buffer copies made MCTS in DeepMind's mctx 3 slower
I'm training an AlphaZero-style agent (Gumbel MuZero via DeepMind's mctx ) to lay out working factory modules for Factorio: the network places machines, belts and inserters on a grid, and the reward comes from an exact throughput verifier. Everything runs in JAX on a single RTX 5070: 128 environments in one batch, an action space of A = 1729 (3 entity types × 144 cells × 4 rotations + "done"), and a small 474k-parameter conv net in bf16. While benchmarking training configurations I hit this: MCTS simulations per move training throughput XLA compile time 16 143 episodes/s 5 s 32 47 episodes/s 13 s 64 9 episodes/s 60 s Doubling the simulation budget should roughly double the cost — each simulation is one network call plus some tree bookkeeping. Instead, 16→32 costs ×3 and 32→64 costs ×5 . Something in the search was superlinear, and this post is the story of finding it in the compiled HLO and fixing it by rewriting one ~80-line function ( PR #116 ), with bitwise-identical search results. Ruling out the network First, components in isolation (batch 128): one network evaluation takes ~0.8 ms , and the rest of recurrent_fn (environment step + observation + legal-action mask) adds almost nothing on top — the whole function is also ~0.8 ms. So at 64 simulations the network accounts for roughly 50 ms per move. But a full policy step at 64 simulations costs 362 ms . Hundreds of milliseconds were going somewhere else. To localize them I benchmarked three variants of the same policy step: full — production setup; no-net — network replaced by constant logits, real environment; tree-only — no network and no environment: recurrent_fn returns the embedding unchanged. Nothing left but mctx's own tree machinery. sims full no-net tree-only 8 10.7 ms 3.7 ms 3.8 ms 16 20.9 ms 8.3 ms 8.3 ms 32 64.7 ms 34.0 ms 33.7 ms 64 362.1 ms 124.7 ms 125.0 ms The pure tree machinery is superlinear all by itself. Per simulation it costs 0.47 → 0.52 → 1.05 → 1.95 ms as the budget goes 8 → 16 → 32 → 64
AI 资讯
I built a test lab to measure SSG vs SSR vs ISR on real WordPress, here's what I found
Most "SSG vs SSR vs ISR" content out there is written from documentation. Someone reads the framework, restates it, and you're left inferring the actual difference in performance and behavior. So I built a lab where you can just run the commands and see it yourself, no table to trust blindly. astro-wp-seo-lab builds the same WordPress content four different ways with Astro 7.1.1, then serves all four side by side so you can compare them directly. git clone https://github.com/nimajafari/astro-wp-seo-lab npm install npm run compare That builds each arm into its own directory and serves them all at once. arm url what it is ssg-full http://localhost:4301 everything prerendered at build time ssr http://localhost:4302 rendered per request, no caching ssr-cdn http://localhost:4303 per request plus CDN cache headers route-cache http://localhost:4304 per request plus Astro 7 route caching islands http://localhost:4305 static shell with deferred fragments Every page has a black bar at the top showing which arm rendered it and when. That timestamp is the instrument for most of what follows. First build takes a few minutes since each arm fetches from WordPress, later builds are faster because the Content Layer loader caches between them. It ships pointed at a live WordPress install (oxyplug.com), but it works against any public WordPress site with the REST API exposed. npm run probe -- https://your-site.com --save mysite SOURCE = mysite npm run compare probe checks what your own install actually exposes, REST API reachability, Yoast presence, permalink structure, then saves it under a name. Use the URLs npm run compare prints for your own site instead of the ones below, since those are generated from your own content. Build time vs request time This is the distinction most of the SSG vs SSR debate hinges on, and it takes about 30 seconds to see for yourself. Open these two side by side and reload each a few times. http://localhost:4301/optimization/crl-ocsp-certificate-revocati
AI 资讯
How the V8 Engine Optimizes JavaScript at Runtime
.The V8 engine speeds up JavaScript by dynamically compiling frequently run bytecode into optimized native machine code. However, if you pass inconsistent argument types to these optimized functions, V8 panics and deoptimizes back to bytecode. Keeping your functions monomorphic (single-typed) prevents this costly deoptimization loop, ensuring maximum runtime execution speed. If you’ve spent as much time digging into V8 execution flags as I have, you quickly realize that JavaScript is constantly rewriting itself under the hood. We like to think of JavaScript as a dynamically typed scripting language. But at runtime, engines like V8 are working tirelessly to turn your code into a highly optimized, statically typed powerhouse. When we violate that type stability, we pay a massive performance tax. How does the V8 engine optimize JavaScript at runtime? V8 uses a multi-tiered compilation pipeline that starts with an interpreter for fast startup times, then upgrades hot functions to optimized machine code using a JIT compiler. By tracking runtime type patterns, the engine can safely make assumptions to skip expensive dynamic lookups. When I look at V8’s execution pipeline, I see two primary systems working in tandem: Ignition (the interpreter) and TurboFan (the JIT compiler). Initially, Ignition compiles your raw JavaScript into bytecode so your app can boot instantly. As this bytecode executes, V8 allocates a data structure called a Feedback Vector for each function. Inside this vector are Feedback Slots (managed by Inline Caches, or ICs). These slots act as recorders, capturing the exact types (or "shapes") of the variables passing through your code. Once a function runs frequently enough to cross an execution threshold, V8 marks it as "hot" and hands it to TurboFan. TurboFan reads those feedback slots, assumes the types will remain identical in the future, and compiles a highly streamlined, native machine code version of that function. What happens when you pass differe
AI 资讯
Building Zero-Reload Web Forms: Master Modern Async/Await JavaScript Fetch API
Modern web applications demand responsive, non-blocking user flows. This tutorial demonstrates a production-grade implementation for handling form submissions without full-page reloads using the native Fetch API and clean async/await syntax. The Complete Source Code Create a file named app.js and drop in the following event-driven architecture: JavaScript document.getElementById('registrationForm').addEventListener('submit', async (event) => { event.preventDefault(); // 1. Stop full page reload const form = event.target; const formData = new FormData(form); const submitBtn = form.querySelector('button[type="submit"]'); const responseMessage = document.getElementById('responseMessage'); // 2. UI Feedback: Disable button during network request submitBtn.disabled = true; submitBtn.textContent = 'Processing...'; responseMessage.textContent = ''; try { // 3. Asynchronous Fetch Request const response = await fetch(form.action, { method: 'POST', body: formData, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); // 4. Status Code Validation if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } const result = await response.json(); // 5. Dynamic UI State Handling if (result.success) { responseMessage.style.color = '#155724'; responseMessage.textContent = result.message; form.reset(); // Clear form on success } else { responseMessage.style.color = '#721c24'; responseMessage.textContent = result.error || 'Submission failed.'; } } catch (error) { // 6. Global Error Catching responseMessage.style.color = '#721c24'; responseMessage.textContent = 'A network error occurred. Please try again.'; console.error('Submission tracking error:', error); } finally { // 7. Reset UI State Guaranteed submitBtn.disabled = false; submitBtn.textContent = 'Submit Data Securely'; } }); The Problem with Synchronous Form Lifecycles Traditional submissions trigger a synchronous navigation cycle: the browser constructs a payload, issues a full HTTP request, and replaces the
开发者
The Hidden Cost of yield in C#: What the Compiler Doesn't Tell You
Most C# developers know how to use yield return. Few understand what actually happens after compilation. If you've ever written something like this: public IEnumerable < int > GetNumbers () { yield return 1 ; yield return 2 ; yield return 3 ; } it looks almost magical. No collection. No list allocation. No iterator implementation. Yet somehow the method returns an IEnumerable. So what is really happening? The answer is one of the most elegant compiler transformations in the entire .NET ecosystem. Let's open the hood. The Illusion Most developers imagine the previous code executes like this: Call method ↓ Return 1 ↓ Pause ↓ Return 2 ↓ Pause ↓ Return 3 That isn't what happens. C# methods cannot actually pause execution. Instead, the compiler completely rewrites your method into something entirely different. The Compiler Creates a State Machine Your tiny method becomes a hidden class similar to this: private sealed class GetNumbersIterator : IEnumerable < int >, IEnumerator < int > { private int _state ; private int _current ; public bool MoveNext () { switch ( _state ) { case 0 : _current = 1 ; _state = 1 ; return true ; case 1 : _current = 2 ; _state = 2 ; return true ; case 2 : _current = 3 ; _state = - 1 ; return true ; default : return false ; } } public int Current => _current ; } Your original method no longer exists. Instead, it simply returns: return new GetNumbersIterator (); Every yield return becomes another state inside MoveNext(). Why Local Variables Don't Disappear Consider this code: IEnumerable < int > Squares () { int x = 1 ; while ( x <= 3 ) { yield return x * x ; x ++; } } After the first yield, the method "pauses." But where is x stored? Not on the stack. The original stack frame has already disappeared. Instead, the compiler promotes local variables into fields: private int _x ; The iterator object now owns every variable that must survive between iterations. This is why iterator methods can remember where they left off. Heap Allocation Happens Ma
AI 资讯
Memory-Safe Media Preflight in Mobile Browsers
Client-side media preflight can improve an upload experience, but it can also crash the page before the network request begins. A twelve-megapixel JPEG may be only four megabytes on disk and tens of megabytes after decoding. Creating several full-size canvases at once is enough to exhaust memory on older phones. Preflight is policy, not editing Decide what the browser must prove before transfer. Useful checks include file count, compressed byte size, declared type, readable dimensions, and a conservative duration limit for video. Avoid mandatory re-encoding unless the product truly needs it. Every transformation adds CPU time, memory pressure, battery use, and another failure mode. Process one file at a time A file picker may return twenty items. Do not decode all of them to build previews. Maintain a queue with one active decode on constrained devices and at most two on stronger ones. for ( const file of files ) { const result = await inspect ( file ); renderResult ( result ); await yieldToMainThread (); } The UI can list filenames immediately while dimensions and thumbnails arrive progressively. Prefer metadata over full pixels Use createImageBitmap with resize hints when supported. It can decode away from the main rendering path and avoid a full-resolution canvas for a small preview. const bitmap = await createImageBitmap ( file , { resizeWidth : 640 , resizeQuality : ' medium ' }); Always close bitmaps after drawing. Revoke object URLs when the component unmounts. Small leaks become large when guests select and remove files repeatedly. Handle orientation and color carefully Modern browsers generally honor EXIF orientation when decoding, but behavior differs across APIs and older engines. Test portrait photographs from real iOS and Android devices. Do not strip metadata silently if the original is supposed to remain untouched; upload the source file and treat the preview as disposable UI. Color differences between the preview and exported original are usually les
AI 资讯
Token Drift Explained: Why Your Agent Gets Slower and More Expensive
Your agent feels fast during a demo. Then a real session reaches twenty turns, several tools have returned large payloads, and every response starts taking longer and costing more. This pattern is often called token drift : the effective input context grows as an agent carries more conversation history, tool output, retrieved documents, and state into each model call. The model itself is not gradually becoming less efficient. The application is asking it to process more material on every turn. Token drift is manageable, but only if context is treated as a budgeted system resource rather than an unlimited transcript. What Token Drift Actually Means Most conversational agents build each request from several sources: a system prompt, tool definitions, recent messages, retrieved context, durable memory, and sometimes a summary of older work. Whether the application sends that state on every request or a provider manages part of it, the model still has an effective context to process. Consider an illustrative session: Turn Effective input What changed 1 1,200 tokens System prompt, tools, and one user message 8 6,900 tokens Conversation history and two tool results 20 18,400 tokens More history, retrieved documents, and accumulated state The exact price and latency depend on the model, provider, cache behavior, and workload. The important signal is the trend: later calls repeatedly process a larger context. Why Agents Accumulate Tokens So Quickly Conversation history is only one source of growth. Production agents often accumulate tokens in several places at once: Repeated transcripts: Every prior user and assistant message remains in context. Tool schemas: Large tool descriptions and JSON schemas may be attached to every model call. Tool results: Search results, stack traces, database rows, and API responses can be much larger than the user's request. Retrieved documents: RAG pipelines sometimes add too many chunks or keep stale retrieval results across turns. Retries an
AI 资讯
Presentation: The Rust High Performance Talk You Did Not Expect
Ruth Linehan explains how migrating high-performance caching services from Kotlin to Rust shattered internal preconceptions around delivery velocity and engineering overhead. She discusses the ergonomics of the Rust borrow checker, shares how compile-time safety shortens the developer feedback loop, and profiles how tools like Criterion and flamegraphs optimize concurrent code paths. By Ruth Linehan
开发者
Can a Mac Mini Handle 100 Million Rows?
I made ClickHouse and Postgres Fight to Find Out 🥊 One Mac Mini. Two databases. 100 million rows of...
AI 资讯
LLM Latency Budget: Make AI Workflows Feel Fast Without Guessing
A slow AI feature rarely fails all at once. It starts with a longer prompt, then a bigger retrieval result, then one more tool call, then a retry path nobody measured. The demo still works, but users feel the delay before your dashboard explains it. That is why small AI product teams need an LLM latency budget before they start optimizing. Not a vague goal like “make it faster.” A budget says how much time each stage is allowed to spend, what happens when it exceeds that limit, and which user experience is still acceptable when the model, retrieval layer, or tool chain slows down. The payoff is practical: you stop guessing where the delay lives, stop overpaying for wasted work, and make AI workflows feel reliable even when traffic, context, and providers are messy. Why latency budgets matter now Recent AI platform news points in one direction: AI workflows are becoming longer, more tool-heavy, and more expensive to run without discipline. A current news scan showed several signals builders should notice: Production LLM cost and latency guidance is shifting from “add more compute” to “remove wasted work.” Agent environments are being designed for long-running background tasks, persistent state, and cheaper idle time. New model releases emphasize tool use, computer use, multimodal context, subagents, and larger context windows. AI gateways and enterprise platforms are adding cost controls, routing, caching, audit trails, and usage limits. Developers are asking more practical questions about why AI coding and agent workflows interrupt flow with repeated prompt-wait-evaluate loops. For AI SaaS builders, this means latency is no longer just a model selection problem. It is a workflow design problem. A simple chat completion might have one bottleneck. A real AI workflow may include: request queueing auth and tenant checks prompt assembly memory lookup vector search reranking model routing tool calls browser or API actions structured output validation fallback attempts str
AI 资讯
Scale Is a Design, Not a Dial
The dashboard says forty instances, up from twelve this morning. The autoscaler did its job: it saw latency climb and threw hardware at it. And latency got worse. Not flat. Worse. You're paying for three times the compute to serve a slower product. Somewhere under all forty of those boxes is a single thing they're all waiting in line for, and every instance you add makes the line longer. Horizontal scaling multiplies work that doesn't have to coordinate. The instant the work does have to coordinate, more instances make it slower. Amdahl wrote this down in 1967: the serial fraction of a job sets a hard ceiling on how much faster you can go, no matter how much hardware you throw at the parallel part. Neil Gunther's Universal Scalability Law goes further: past a certain point, the cost of nodes coordinating with each other bends the curve back down. Add capacity, get less throughput. That ceiling was not set by the autoscaler, and it will not be moved by the autoscaler. It was set a long time before this morning, in a room, by whoever decided where the state lives and who has to touch it at the same instant. Now hand the service to a fleet of agents. It writes you something that looks built to scale: stateless handlers, a tidy repo, green tests, a canary that bakes fine at 1% traffic. Every gate you trust says ship it. And the bottleneck is sitting right there in the design, invisible to all of it, because the mistake isn't in the lines, it's in the shape. You cannot catch a shape problem by reading a diff. Name the hot state before you pick a framework. Where does the contended state live, and which requests touch it at the same instant? Answer that out loud, before anyone opens an editor. The tool is downstream of that answer, every time. Originally published at https://imacto.com/writing/scale-is-a-design-not-a-dial . Written with Claude Opus 4.8.
开发者
What MasterMemory Solves—and What It Doesn't: A Practical Guide to Static Game Data in Unity
Introduction When you build games with Unity, you eventually run into the problem of managing static game data—often called master data in Japanese game development. At first, ScriptableObject may be more than enough. If your project has a few dozen items, a few dozen enemies, and only a small number of stage definitions, ScriptableObject is convenient because you can inspect and edit everything directly in the Unity Editor. As the project grows, however, the situation changes. You may end up with tables for items, characters, skills, quests, rewards, shops, gacha pools, stages, enemy placements, progression curves, and localization text. The data is no longer edited only by programmers. Planners and game designers may need to work with it in Excel or Google Sheets. At that point, the problem is no longer just choosing a file format. You need to think about questions such as: How do you load a large amount of data quickly? How do you write ID lookups and composite-key queries safely? Should CSV or JSON be parsed directly at runtime? Is it reasonable to create a large number of Dictionaries? How do you validate references between tables? How do you debug data after converting it to binary? How do you connect the source data edited by planners to the data loaded by Unity? For the runtime loading and lookup part of that problem, one strong option is Cysharp's MasterMemory . The official README describes MasterMemory as a “Source Generator based Embedded Typed Readonly In-Memory Document Database” for .NET and Unity. In practical terms, you define your schema as C# types, a Source Generator creates a typed read-only in-memory database API, and the application loads MessagePack binary data that can be queried through type-safe methods. The official README highlights performance compared with SQLite, low allocation during queries, a small database size, and generated database structures that are type-safe and IDE-friendly. Cygames Engineers' Blog also has useful articles
AI 资讯
How I made a Rust hot path 27x faster, and the AI fix I refused to merge
Two years ago I open-sourced KeyEcho, a small desktop app that plays a mechanical-keyboard sound the...
AI 资讯
Compare Cloud and On-Device AI Costs Without Inventing Energy Numbers
“On-device AI saves battery” and “cloud AI is more efficient” can both sound plausible. Neither is a measurement. The placement decision crosses at least four different budgets: user wait + network transfer + provider spend + device energy Do not collapse them into one vague “cost” number. Measure each with its own unit and evidence boundary. Start by identifying the actual execution path I reviewed MonkeyCode mobile code at commit c58bcd4 . The task stream opens a server-supported WebSocket. The speech-to-text hook also participates in a server-supported streaming path. That reviewed path is not evidence of on-device model inference. So a fair current study would measure a mobile client using remote task and voice services. An on-device alternative would be a separate prototype with its model, runtime, and packaging declared. Record a measurement envelope The included CSV template begins with these fields: sample_id,sample_kind,placement,device,os,framework,model,network,input_tokens,output_tokens,latency_ms,bytes_up,bytes_down,energy_joules,cost_usd Why so many? device , os , and framework make thermal and runtime results interpretable; model and token counts keep workload size visible; network separates offline, Wi-Fi, and cellular behavior; latency is milliseconds, transfer is bytes, energy is joules, and provider spend is currency; sample_kind prevents synthetic examples from masquerading as device measurements. Battery percentage is too coarse for short runs. It is affected by display, radio, background work, battery health, temperature, and OS estimation. If you cannot collect energy with an appropriate platform profiler or external power measurement, leave energy_joules empty. Use matched user flows Compare the same tasks, not unrelated model demos: Flow Cloud case On-device case Short prompt Same input and output cap Same semantic task and cap Voice turn Same audio fixture Same audio fixture Offline Expected failure or queued action Local completion if supp
AI 资讯
How I export 1.2-gigapixel images on an iPhone without running out of memory
Rendering a big image on iOS is one of those things that looks trivial until your app gets killed by the OS mid-export. CGContext , draw, makeImage() , done — except the moment the output gets large, that innocent-looking pipeline quietly asks for gigabytes of RAM and iOS terminates you. I hit this wall building Mozary , an iOS app that packs 100+ photos into a single giant picture (a photo mosaic). In v1.1.0 I finally killed the "high-resolution export crashes with out-of-memory" bug for good. The fix: stop putting the canvas in RAM at all. Put it in a memory-mapped file and let the OS page it to disk. RAM usage dropped from "4.8 GB, please die" to a few dozen MB, flat, regardless of output size. This post is the walkthrough — with the actual Swift. If you've ever seen Core Graphics blow up on a big image (mosaics, collages, stitched panoramas, high-res rendering — same trap), this is for you. TL;DR A non-compressed bitmap costs 4 bytes/pixel . A 1.2-gigapixel image = ~4.8 GB of RAM just for the canvas. CGContext(data: nil, ...) allocates that in RAM. context.makeImage() then copies it again . Double death. Back the canvas with a memory-mapped file ( mmap ). Writes transparently page out to disk and don't count against your app's memory footprint . Wrap that same mapping in a CGImage via CGDataProvider — zero copy — and stream it straight to a JPEG on disk. Never call makeImage() . Decode source tiles at their draw size , not full size. Because you now spend disk instead of RAM: add a free-space pre-flight check and clean up temp files after a crash. Let's dig in. The problem: "compressed file size" is a lie about memory Mozary lays photos out on a grid. A typical high-res export is a 200 × 267 grid with each tile drawn at 150px : width: 200 × 150 = 30,000 px height: 267 × 150 = 40,050 px That's ~1.2 gigapixels . Here's the part people underestimate: the final JPEG is only a few hundred MB to ~2 GB because JPEG is compressed . But while you're drawing, the canvas i