<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Performance on TurboVision</title>
    <link>https://turbovision.in6-addr.net/tags/performance/</link>
    <description>Recent content in Performance on TurboVision</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Tue, 21 Apr 2026 14:06:12 +0000</lastBuildDate>
    <atom:link href="https://turbovision.in6-addr.net/tags/performance/index.xml" rel="self" type="application/rss&#43;xml" />
    
    
    
    <item>
      <title>Benchmarking with a Stopwatch</title>
      <link>https://turbovision.in6-addr.net/retro/benchmarking-with-a-stopwatch/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Sun, 22 Feb 2026 22:13:51 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/benchmarking-with-a-stopwatch/</guid>
      <description>&lt;p&gt;When people imagine benchmarking, they picture automated harnesses, high-resolution timers, and dashboards with percentile charts. Useful tools, absolutely. But many core lessons of performance engineering can be learned with much humbler methods, including one old trick from retro workflows: benchmarking with a stopwatch and disciplined procedure.&lt;/p&gt;
&lt;p&gt;On vintage systems, instrumentation was often limited, intrusive, or unavailable. So users built practical measurement habits with what they had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fixed test scenarios&lt;/li&gt;
&lt;li&gt;fixed machine state&lt;/li&gt;
&lt;li&gt;repeated runs&lt;/li&gt;
&lt;li&gt;manual timing&lt;/li&gt;
&lt;li&gt;written logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It sounds primitive until you realize it enforces the exact thing modern teams often skip: experimental discipline.&lt;/p&gt;
&lt;p&gt;The first rule was baseline control. Before measuring anything, define the environment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;cold boot or warm boot?&lt;/li&gt;
&lt;li&gt;which TSRs loaded?&lt;/li&gt;
&lt;li&gt;cache settings?&lt;/li&gt;
&lt;li&gt;storage medium and fragmentation status?&lt;/li&gt;
&lt;li&gt;background noise sources?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without this, numbers are stories, not data.&lt;/p&gt;
&lt;p&gt;Retro benchmark notes were often simple tables in paper notebooks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;date/time&lt;/li&gt;
&lt;li&gt;test ID&lt;/li&gt;
&lt;li&gt;config profile&lt;/li&gt;
&lt;li&gt;run duration&lt;/li&gt;
&lt;li&gt;anomalies observed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Crude format, high value. The notebook gave context that raw timing never carries alone.&lt;/p&gt;
&lt;p&gt;A useful retro-style method still works today:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define one narrow task.&lt;/li&gt;
&lt;li&gt;Freeze variables you can control.&lt;/li&gt;
&lt;li&gt;Predict expected change before tuning.&lt;/li&gt;
&lt;li&gt;Run at least five times.&lt;/li&gt;
&lt;li&gt;Record median, min, max, and odd behavior.&lt;/li&gt;
&lt;li&gt;Change one variable only.&lt;/li&gt;
&lt;li&gt;Repeat.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This method is slow compared to one-click benchmarks. It is also far less vulnerable to self-deception.&lt;/p&gt;
&lt;p&gt;On old DOS systems, examples were concrete:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;compile a known source tree&lt;/li&gt;
&lt;li&gt;load/save a fixed data file&lt;/li&gt;
&lt;li&gt;render a known scene&lt;/li&gt;
&lt;li&gt;execute a scripted file operation loop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key was repeatability, not synthetic hero numbers.&lt;/p&gt;
&lt;p&gt;Stopwatch timing also trained observational awareness. While timing a run, people noticed things automated tools might not flag immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;intermittent disk spin-up delays&lt;/li&gt;
&lt;li&gt;occasional UI stalls&lt;/li&gt;
&lt;li&gt;audible seeks indicating poor locality&lt;/li&gt;
&lt;li&gt;thermal behavior after repeated runs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These qualitative observations often explained quantitative outliers.&lt;/p&gt;
&lt;p&gt;Outliers are where learning happens. Many teams throw them away too quickly. In retro workflows, outliers were investigated because they were expensive and visible. Was the disk retrying? Did memory managers conflict? Did a TSR wake unexpectedly? Outlier analysis taught root-cause thinking.&lt;/p&gt;
&lt;p&gt;Modern equivalent: if your p99 spikes, do not call it &amp;ldquo;noise&amp;rdquo; by default.&lt;/p&gt;
&lt;p&gt;Another underrated benefit of manual benchmarking is forced hypothesis writing. If timing is laborious, you naturally ask, &amp;ldquo;What exactly am I trying to prove?&amp;rdquo; That question removes random optimization churn.&lt;/p&gt;
&lt;p&gt;A strong benchmark note has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hypothesis&lt;/li&gt;
&lt;li&gt;method&lt;/li&gt;
&lt;li&gt;expected outcome&lt;/li&gt;
&lt;li&gt;observed outcome&lt;/li&gt;
&lt;li&gt;interpretation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If interpretation comes without explicit expectation, confirmation bias sneaks in.&lt;/p&gt;
&lt;p&gt;Retro systems also made tradeoffs obvious. You might optimize disk cache and gain load speed but lose conventional memory needed by a tool. You might tune for compile throughput and reduce game compatibility in the same boot profile. Measuring one axis while ignoring others produced bad local wins.&lt;/p&gt;
&lt;p&gt;That tradeoff awareness is still essential:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;lower latency at cost of CPU headroom&lt;/li&gt;
&lt;li&gt;higher throughput at cost of tail behavior&lt;/li&gt;
&lt;li&gt;better cache hit rate at cost of stale data risk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All optimization is policy.&lt;/p&gt;
&lt;p&gt;The stopwatch method encouraged another good habit: &amp;ldquo;benchmark the user task, not the subsystem vanity metric.&amp;rdquo; Faster block IO means little if perceived workflow time is unchanged. In retro terms: if startup is faster but menu interaction is still laggy, users still feel it is slow.&lt;/p&gt;
&lt;p&gt;Many optimization projects fail because they optimize what is easy to measure, not what users experience.&lt;/p&gt;
&lt;p&gt;The historical constraints are gone, but the pattern remains useful for quick field analysis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no profiler on locked-down machine&lt;/li&gt;
&lt;li&gt;no tracing in production-like lab&lt;/li&gt;
&lt;li&gt;no permission for invasive instrumentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those cases, controlled manual timing plus careful notes can still produce actionable decisions.&lt;/p&gt;
&lt;p&gt;There is a social benefit too. Manual benchmark logs are readable by non-specialists. Product, support, and ops can review the same sheet and understand what changed. Shared understanding improves prioritization.&lt;/p&gt;
&lt;p&gt;This does not replace modern telemetry. It complements it. Think of stopwatch benchmarking as a low-tech integrity check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does automated telemetry align with observed behavior?&lt;/li&gt;
&lt;li&gt;Do optimization claims survive controlled reruns?&lt;/li&gt;
&lt;li&gt;Do gains persist after reboot and load variance?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If yes, confidence increases.&lt;/p&gt;
&lt;p&gt;If no, investigate before celebrating.&lt;/p&gt;
&lt;p&gt;A practical retro-inspired template for teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;keep one canonical benchmark scenario per critical user flow&lt;/li&gt;
&lt;li&gt;run it before and after risky performance changes&lt;/li&gt;
&lt;li&gt;require expected-vs-actual notes&lt;/li&gt;
&lt;li&gt;archive results alongside release notes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This creates performance memory. Without memory, teams repeat old mistakes with new tooling.&lt;/p&gt;
&lt;p&gt;Performance culture improves when measurement is treated as craft, not ceremony. Retro workflows learned that under hardware limits. We can keep the lesson without the limits.&lt;/p&gt;
&lt;p&gt;The stopwatch is symbolic, not sacred. Use any timer you like. What matters is disciplined comparison, clear expectations, and honest interpretation. Those traits produce reliable performance improvements on 486-era systems and cloud-native stacks alike.&lt;/p&gt;
&lt;p&gt;In the end, benchmarking quality is less about timer precision than about thinking precision. A clean method beats a noisy toolchain every time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Latency Budgeting on Old Machines</title>
      <link>https://turbovision.in6-addr.net/retro/latency-budgeting-on-old-machines/</link>
      <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
      <lastBuildDate>Mon, 09 Mar 2026 09:46:27 +0100</lastBuildDate>
      <guid>https://turbovision.in6-addr.net/retro/latency-budgeting-on-old-machines/</guid>
      <description>&lt;p&gt;One gift of old machines is that they make latency visible. You do not need an observability platform to notice when an operation takes too long; your hands tell you immediately. Keyboard echo lags. Menu redraw stutters. Disk access interrupts flow. On constrained hardware, latency is not hidden behind animation. It is a first-class design variable.&lt;/p&gt;
&lt;p&gt;Most retro users developed latency budgets without naming them that way. They did not begin with dashboards. They began with tolerance thresholds: if opening a directory takes longer than a second, it feels broken; if screen updates exceed a certain rhythm, confidence drops; if save operations block too long, people fear data loss. This was experiential ergonomics, built from repeated friction.&lt;/p&gt;
&lt;p&gt;A practical budget often split work into classes. Input responsiveness had the strictest target. Visual feedback came second. Heavy background operations came third, but only if they could communicate progress honestly. Even simple tools benefited from this hierarchy. A file manager that reacts instantly to keys but defers expensive sorting feels usable. One that blocks on every key feels hostile.&lt;/p&gt;
&lt;p&gt;Because CPUs and memory were limited, achieving these budgets required architectural choices, not just micro-optimizations. You cached directory metadata. You precomputed static UI regions. You used incremental redraw instead of repainting everything. You chose algorithms with predictable worst-case behavior over theoretically elegant options with pathological spikes. The goal was not maximum benchmark score; it was consistent interaction quality.&lt;/p&gt;
&lt;p&gt;Disk I/O dominated many workloads, so scheduling mattered. Batching writes reduced seek churn. Sequential reads were preferred whenever possible. Temporary file design became a latency decision: poor temp strategy could double user-visible wait time. Even naming conventions influenced performance because directory traversal cost was real and structure affected lookup behavior on older filesystems.&lt;/p&gt;
&lt;p&gt;Developers also learned a subtle lesson: users tolerate total time better than jitter. A stable two-second operation can feel acceptable if progress is clear and consistent. An operation that usually takes half a second but occasionally spikes to five feels unreliable and stressful. Old systems made jitter painful, so engineers learned to trade mean performance for tighter variance when user trust depended on predictability.&lt;/p&gt;
&lt;p&gt;Measurement techniques were primitive but effective. Stopwatch timings, loop counters, and controlled repeat runs produced enough signal to guide decisions. You did not need nanosecond precision to find meaningful wins; you needed discipline. Define a scenario, run it repeatedly, change one variable, and compare. This method is still superior to intuition-driven tuning in modern environments.&lt;/p&gt;
&lt;p&gt;Another recurring tactic was level-of-detail adaptation. Tools degraded gracefully under load: fewer visual effects, smaller previews, delayed nonessential processing, simplified sorting criteria. These were not considered failures. They were responsible design responses to finite resources. Today we call this adaptive quality or progressive enhancement, but the principle is identical.&lt;/p&gt;
&lt;p&gt;Importantly, latency budgeting changed communication between developers and users. Release notes often highlighted perceived speed improvements for specific workflows: startup, save, search, print, compile. This focus signaled respect for user time. It also forced teams to anchor claims in concrete tasks instead of vague “performance improved” statements.&lt;/p&gt;
&lt;p&gt;Retro constraints also exposed the cost of abstraction layers. Every wrapper, conversion, and helper had measurable impact. Good abstractions survived because they paid for themselves in correctness and maintenance. Bad abstractions were stripped quickly when latency budgets broke. This pressure produced leaner designs and a healthier skepticism toward accidental complexity.&lt;/p&gt;
&lt;p&gt;If we port these lessons to current systems, the takeaway is simple: define latency budgets at the interaction level, not just service metrics. Ask what a user can perceive and what breaks trust. Build architecture to protect those thresholds. Measure variance, not only averages. Prefer predictable degradation over catastrophic stalls. These are old practices, but they map perfectly to modern UX reliability.&lt;/p&gt;
&lt;p&gt;The nostalgia framing misses the point. Old machines did not make developers virtuous by magic. They made trade-offs impossible to ignore. Latency was local, immediate, and accountable. When tools are transparent enough that cause and effect stay visible, teams build sharper instincts. That is the real value worth carrying forward.&lt;/p&gt;
&lt;p&gt;One practical exercise is to choose a single workflow you use daily and write a hard budget for each step: open, search, edit, save, verify. Then instrument and defend those thresholds over time. On old machines this discipline was survival. On modern machines it is still an advantage, because user trust is ultimately built from perceived responsiveness, not theoretical peak throughput.&lt;/p&gt;
&lt;h2 id=&#34;budget-log-example&#34;&gt;Budget log example&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Workflow: open project -&amp;gt; search symbol -&amp;gt; edit -&amp;gt; save
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Budget:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  open &amp;lt;= 800ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  search &amp;lt;= 400ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  save &amp;lt;= 300ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Observed run #14:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  open 760ms | search 910ms | save 280ms
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Action:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  inspect search index freshness and directory fan-out&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Latency budgeting only works when budgets are written and checked, not assumed.&lt;/p&gt;
&lt;p&gt;Related reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/dos/tp/turbo-pascal-history-through-tooling/&#34;&gt;Turbo Pascal History Through Tooling Decisions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/retro/benchmarking-with-a-stopwatch/&#34;&gt;Benchmarking with a Stopwatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://turbovision.in6-addr.net/musings/clarity-is-an-operational-advantage/&#34;&gt;Clarity Is an Operational Advantage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
