<?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 Charlie Chiang's blog</title><link>https://charlie0129.github.io/blog/tags/performance/</link><description>Recent content in Performance on Charlie Chiang's blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Sun, 16 Aug 2026 16:42:45 +0800</lastBuildDate><atom:link href="https://charlie0129.github.io/blog/tags/performance/index.xml" rel="self" type="application/rss+xml"/><item><title>Building Audio Insight: An Audio Analyzer That Actually Renders Smoothly</title><link>https://charlie0129.github.io/blog/p/building-audio-insight/</link><pubDate>Sun, 16 Aug 2026 16:42:45 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/building-audio-insight/</guid><description>&lt;p>I like audio analyzers. It answers questions that my ears alone cannot answer quickly. Where is that resonance? Is the low end actually mono? How loud is this over the whole track? Is a limiter catching an occasional peak, or working all the time?&lt;/p>
&lt;p>What I do not like is an analyzer whose interface feels slower than the display it runs on.&lt;/p>
&lt;p>On my M1 Max, Excite Audio VISION 4X appeared to top out at roughly 30 FPS, with inconsistent frame timing, while consuming about one CPU core. iZotope Insight 2 looked smoother, but in my experience it was comparatively resource-heavy and expensive. These were observations from my own setup, not controlled benchmarks that apply to every machine, host, and plugin version. Still, they were enough to make me wonder: how difficult would it be to build the analyzer I wanted?&lt;/p>
&lt;p>That became &lt;a class="link" href="https://github.com/charlie0129/audio-insight" target="_blank" rel="noopener"
>Audio Insight&lt;/a>, an open-source AUv2 and VST3 analyzer for macOS. Its first goal is deliberately narrow: show useful measurements, leave the audio unchanged, keep real-time callback work bounded, and make the interface feel native on a high-refresh-rate display.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard.png"
width="2400"
height="1600"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_65720109d0b02a45.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_bef71f94e6a17226.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_69cd2a1a5c959a9.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_90165cad42ea4598.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_e5c6a27b39095609.webp 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_a4aec93b81a0f8da.jpg 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_6b3fb61cc0a9dbfe.webp 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_af4a7725d3b77d03.jpg 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_879369758ebcb0fa.webp 2400w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-dashboard_hu_b23cace1a05ee5e4.jpg 2400w"
loading="lazy"
alt="Audio Insight dashboard with all five analyzers"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>
&lt;/p>
&lt;p>The project is still early, but it already has a Spectrum, Spectrogram, Peak/RMS meter, stereo vectorscope and correlation meter, and BS.1770 loudness measurements. Four grid-snapped splitters resize the dashboard tiles, the analysis parameters are adjustable, and a built-in metrics panel makes the renderer&amp;rsquo;s behavior visible instead of leaving performance to intuition.&lt;/p>
&lt;p>This post is about how it works, but mostly about the unexpectedly interesting work required to make a meter move smoothly.&lt;/p>
&lt;h2 id="what-an-audio-plugin-actually-does">What an audio plugin actually does
&lt;/h2>&lt;p>People who use plugins often picture them as little applications inside a DAW. That is a useful mental model for the interface, but not for the audio path.&lt;/p>
&lt;p>An AU or VST3 plugin is code loaded by a host (or, in some hosts, a separate hosting service). The host repeatedly gives the plugin a small block of samples by calling its processing function. At 48 kHz with 512-sample blocks, a new block arrives about every 10.7 milliseconds. The plugin has to finish before the hardware needs the result. Missing that deadline can produce a click or dropout.&lt;/p>
&lt;p>Audio Insight is a transparent effect: it observes supported mono or stereo audio and leaves the samples unchanged. Even so, its callback has to follow the same real-time rules as a compressor or synthesizer. It cannot allocate memory, take a lock, wait for another thread, write a log, open a file, call the UI, or ask the GPU to draw something. Any of those operations can take an unpredictable amount of time.&lt;/p>
&lt;p>The resulting design looks like this:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">host audio callback
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">bounded, non-blocking capture
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">per-instance analysis coordinator
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">shared two-worker analysis pool
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">immutable measurement snapshots
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ↓
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">display-linked Metal renderer
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The callback only captures bounded chunks into preallocated storage and updates the few measurements that must inspect every sample. A per-instance coordinator coalesces work, and all instances loaded in the same plugin module share two analysis workers. There is at most one running or queued job per instance, so opening many plugin windows does not create a thread for every visualization.&lt;/p>
&lt;p>The workers publish immutable snapshots. The UI reads the newest complete snapshot whenever it draws; it never waits for analysis to finish. This separation matters. Analysis targets 60 slices per second, but the latest-wins scheduler can skip stale work rather than build a backlog. Meanwhile, a ProMotion display can render at around 120 Hz. The renderer can advance display motion between discrete analysis updates without running twice as many FFTs.&lt;/p>
&lt;p>All five visualizations share one Metal canvas, drawable, command buffer, and render pass. JUCE supplies the plugin shell and cross-format plumbing, while the visual layer is native Metal. Coordinates and layout use logical points, and drawable and text resources follow the current backing scale. The implementation is therefore designed to support both regular-density and Retina displays, including live backing-scale changes.&lt;/p>
&lt;p>Both paths are highly optimized. The shared transform uses &lt;code>juce::dsp::FFT&lt;/code>, which selects Apple&amp;rsquo;s Accelerate/vDSP implementation on macOS, and Spectrum and Spectrogram reuse each calibrated result. On the GPU side, the Spectrogram stores calibrated dB in a circular 16-bit-float (&lt;code>R16Float&lt;/code>) texture: scrolling remaps texture coordinates, while shader controls recolor retained history without another FFT or a whole-texture copy. High display cadence therefore does not multiply the default 60 Hz FFT workload.&lt;/p>
&lt;p>When the editor is closed, there is nothing to display, so capture, analysis, history, display-link callbacks, and Metal submissions stop. Audio still passes through normally. Reopening the editor begins fresh rather than silently spending host resources on invisible history.&lt;/p>
&lt;h2 id="turning-samples-into-pictures">Turning samples into pictures
&lt;/h2>&lt;p>The analyzers share infrastructure, but each one answers a different question. Here is the calculation path in a little more detail.&lt;/p>
&lt;h3 id="spectrum-what-frequencies-exist-now">Spectrum: what frequencies exist now?
&lt;/h3>&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum.png"
width="1964"
height="686"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_2387749c0d8ba064.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_664cee83eb2c0ad.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_fbdd5b971d8cb430.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_deaf9f5d2535fbbb.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_c52894cca3685b9.webp 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_b740d68172460d99.jpg 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_bdaa1a07fea7d5df.webp 1964w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum_hu_1cda9b55c8a5a78c.jpg 1964w"
loading="lazy"
alt="Audio Insight Spectrum"
class="gallery-image"
data-flex-grow="286"
data-flex-basis="687px"
>
&lt;/p>
&lt;p>The Spectrum takes a short window of recent audio and uses a fast Fourier transform (FFT) to divide it into frequency bins. By default the transform size is \(N=8192\) samples, or about 171 milliseconds at a sample rate \(F_s=48\) kHz. This does &lt;strong>not&lt;/strong> delay the audio by 171 ms; the plugin passes audio through immediately. It means the displayed estimate describes roughly that much recent history.&lt;/p>
&lt;p>The FFT bin centers are separated by \(\Delta f=F_s/N\). With the defaults, that is approximately 5.86 Hz. This number is useful, but it is not the same as saying two tones 5.86 Hz apart can always be resolved: the selected window also determines how widely a tone spreads into nearby bins.&lt;/p>
&lt;p>Before the FFT, samples are multiplied by a periodic five-term flat-top window \(w[n]\). Cutting an arbitrary piece from a continuous waveform creates artificial edges, which spread energy across the spectrum. A window tapers the data to control that leakage. A flat-top window trades some ability to separate nearby tones for better amplitude accuracy, which is a useful default for a measurement tool.&lt;/p>
&lt;p>For channel \(c\), the transform is:&lt;/p>
\[
\begin{aligned}
X_c[k]&amp;=\sum_{n=0}^{N-1}x_c[n]\,w[n]e^{-j2\pi kn/N},\\
f_k&amp;=\frac{kF_s}{N}.
\end{aligned}
\]&lt;p>Audio Insight corrects the window&amp;rsquo;s coherent gain—the amplitude scaling introduced by multiplying by \(w[n]\)—with \(W=\sum_n w[n]\). Real-valued audio has mirrored positive- and negative-frequency FFT bins, but the graph needs only the nonnegative half. In this one-sided view, the DC bin at 0 Hz and the Nyquist bin at \(F_s/2\) use \(1/W\); every bin between them represents both mirrored sides and uses \(2/W\). The calibrated stereo power and level are therefore:&lt;/p>
\[
\begin{aligned}
a_k&amp;=
\begin{cases}
1/W, &amp; k=0\ \text{or}\ k=N/2,\\
2/W, &amp; \text{otherwise},
\end{cases}\\[3pt]
P[k]&amp;=\max_c\left(a_k|X_c[k]|\right)^2,\\
D[k]&amp;=10\log_{10}P[k].
\end{aligned}
\]&lt;p>For mono, the maximum contains only one channel. For stereo, taking the larger channel magnitude avoids first mixing the waveforms to mono, where out-of-phase content could cancel. The calibration makes a bin-centered full-scale sine read 0 dB internally; powers at or below \(10^{-18}\) are displayed at the \(-180\) dB analysis floor.&lt;/p>
&lt;p>Attack and Release then smooth each bin in &lt;strong>linear power&lt;/strong>, not in dB. Given the elapsed time \(\Delta t\) and the selected time constant \(\tau_d\):&lt;/p>
\[
\begin{aligned}
\alpha_d&amp;=
\begin{cases}
0, &amp; d\text{ is Off},\\
e^{-\Delta t/\tau_d}, &amp; d\text{ is enabled},
\end{cases}\\[3pt]
\bar P_t[k]&amp;=\alpha_d\bar P_{t-1}[k]+(1-\alpha_d)P_t[k].
\end{aligned}
\]&lt;p>The direction \(d\) is Attack when \(P_t[k]\geq\bar P_{t-1}[k]\), otherwise Release. An Off direction follows the current FFT immediately. The default Attack is Off, allowing a short burst to appear at once, while the default 250 ms Release lets the trace fall more slowly. Peak hold, when enabled, operates on unsmoothed power instead of \(\bar P\).&lt;/p>
&lt;p>Transforms target a slice rate \(R_s\) using a hop of \(H=\max(1,\operatorname{round}(F_s/R_s))\) new samples. At 48 kHz and 60 slices per second, \(H=800\), so adjacent 8,192-sample windows overlap by about 90.2%. The first result still waits for one complete window, and the latest-wins scheduler may skip stale transforms under load instead of building a backlog.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings.png"
width="2400"
height="1600"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_9787958d4bb832d.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_e4c42b32afd89577.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_b65c00a7ce269ec9.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_4e962807fc7188dc.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_e2311e7526ab5e66.webp 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_c78d3014e83d6dbb.jpg 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_6528c9c2772ad63b.webp 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_9bfee6b347dac769.jpg 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_796a3c92f524a6a9.webp 2400w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrum-settings_hu_e5c6f6a008c1f78b.jpg 2400w"
loading="lazy"
alt="Spectrum Attack and Release controls"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>
&lt;/p>
&lt;p>Spectrum and Spectrogram use the same continuously adjustable frequency scale. For a frequency \(f\) between \(f_0\) and \(f_1\), the scale control \(s\) blends normalized linear and logarithmic coordinates:&lt;/p>
\[
\begin{aligned}
u_{\mathrm{lin}}(f)&amp;=\frac{f-f_0}{f_1-f_0},\\
u_{\log}(f)&amp;=\frac{\ln(f/f_0)}{\ln(f_1/f_0)},\\
u(f,s)&amp;=(1-s)u_{\mathrm{lin}}(f)+s\,u_{\log}(f).
\end{aligned}
\]&lt;p>Here \(f_0=20\) Hz and \(f_1=\min(20\text{ kHz},F_s/2)\). The default is \(s=0.8\). At \(s=0\), equal distances represent equal numbers of hertz. At \(s=1\), equal ratios such as 100→200 Hz and 1→2 kHz occupy equal distances. Values in between preserve more low-frequency detail without compressing the entire treble into a tiny area. Spectrum uses \(x=u\), while Spectrogram uses \(y=1-u\) so high frequencies appear at the top. Axis labels are chosen dynamically: important anchors win first, then extra candidates fill only the space that remains.&lt;/p>
&lt;h3 id="spectrogram-how-did-the-spectrum-change">Spectrogram: how did the spectrum change?
&lt;/h3>&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram.png"
width="1570"
height="758"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_7f3da03990dee5bf.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_e61b0ccc823d3d7d.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_3e3ec7e159f2c15d.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_d8e057932d6094d3.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_ff773f267ecd87d6.webp 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_2135715b92aae3fd.jpg 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_e23e3c8120487b46.webp 1570w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-spectrogram_hu_1b464000e76048f6.jpg 1570w"
loading="lazy"
alt="Audio Insight Spectrogram"
class="gallery-image"
data-flex-grow="207"
data-flex-basis="497px"
>
&lt;/p>
&lt;p>A Spectrum is one slice through time. A Spectrogram keeps those slices and scrolls them sideways, using color for level. Transients become vertical marks, steady tones become horizontal lines, and harmonics become stacks of related lines.&lt;/p>
&lt;p>Each Spectrogram column starts from the same raw power \(P[k]\) as Spectrum, before Spectrum&amp;rsquo;s Attack/Release averaging. Let \(\mathcal K\) contain only usable bin centers from 20 Hz through \(f_1\), and let \(R_f=\min(1024,|\mathcal K|)\) be the texture&amp;rsquo;s frequency-row count. For a usable bin at \(f_k=kF_s/N\), define \(q_k=u(f_k,s)\). Its row is:&lt;/p>
\[
r(k)=\min\left(R_f-1,\left\lfloor R_f q_k\right\rfloor\right).
\]&lt;p>For a row containing one or more bin centers, \(P_r\) is the greatest \(P[k]\) assigned to that row. Taking the maximum, rather than the average, helps a narrow tonal trace survive when several FFT bins land in one display row. If a row contains no bin center—common at low frequencies with a small FFT—the mapper inverse-maps the row center and linearly interpolates the two surrounding bins in power. It is honest interpolation between available samples, not a claim of extra frequency resolution.&lt;/p>
&lt;p>Power at or below \(10^{-18}\) becomes \(-180\) dB; otherwise the mapper stores \(D_r=10\log_{10}P_r\). These values go into a circular Metal texture with one 16-bit floating-point level per cell (R16Float). The texture stores calibrated dB rather than finished colors. In the shader, let \(F\) be the selected floor, \(C\) the ceiling, and \(\eta\) the Color response:&lt;/p>
\[
\begin{aligned}
v&amp;=\operatorname{clamp}\left(
\frac{D_r-F}{C-F},0,1\right),\\
\gamma&amp;=2^\eta,\\
c_{\mathrm{palette}}&amp;=v^\gamma.
\end{aligned}
\]&lt;p>The value \(c_{\mathrm{palette}}\) selects a point in the chosen palette. Response 0 is linear in dB; negative values reveal quieter detail, while positive values suppress low energy and emphasize stronger traces. Because this work happens in the shader, changing palette, range, or response recolors existing history without rerunning the FFT.&lt;/p>
&lt;p>For a history duration \(T\) and requested slice rate \(R_s\), the texture uses \(\min(8192,\lceil TR_s\rceil)\) columns. The default ten seconds at 60 slices per second therefore needs 600 columns. A write index wraps around the texture, and the renderer changes texture coordinates instead of copying the whole image to scroll it. Missing timestamp intervals become black columns rather than stretching old information across time.&lt;/p>
&lt;h3 id="peak-and-rms-how-strong-is-the-signal">Peak and RMS: how strong is the signal?
&lt;/h3>&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-peak-rms.png"
width="534"
height="686"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-peak-rms_hu_52e90250c2611238.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-peak-rms_hu_5a4dbf3bd0e0c780.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-peak-rms_hu_3a0f949a87b5d6b1.webp 534w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-peak-rms_hu_de0186bebe1eb659.jpg 534w"
loading="lazy"
alt="Audio Insight Peak/RMS meter"
class="gallery-image"
data-flex-grow="77"
data-flex-basis="186px"
>
&lt;/p>
&lt;p>Peak and RMS intentionally describe different things.&lt;/p>
&lt;p>Sample peak examines every sample. Its live value has instantaneous attack and a 20 dB/s release:&lt;/p>
\[
\begin{aligned}
\lambda_p&amp;=10^{-20/(20F_s)},\\
p[n]&amp;=\max\left(|x[n]|,\lambda_p p[n-1]\right).
\end{aligned}
\]&lt;p>In other words, a new larger sample wins immediately; otherwise the old indication decays by the amount corresponding to one sample period. A separate hold marker keeps a new maximum for two seconds, then falls at the same 20 dB/s rate. The OVER indicator latches when \(|x[n]|\geq1\), although the label deliberately does not claim that floating-point audio at 0 dBFS proves waveform clipping.&lt;/p>
&lt;p>RMS estimates sustained signal power. With the 300 ms time constant \(\tau=0.300\) s, Audio Insight updates an exponential mean square for every sample:&lt;/p>
\[
\begin{aligned}
\alpha&amp;=e^{-1/(F_s\tau)},\\
q[n]&amp;=\alpha q[n-1]+(1-\alpha)x[n]^2,\\
\operatorname{RMS}[n]&amp;=\sqrt{q[n]},\\
D_{\mathrm{RMS}}[n]&amp;=20\log_{10}\operatorname{RMS}[n].
\end{aligned}
\]&lt;p>This is an exponential response, not a rectangular box containing exactly the latest 300 ms. It also has no AES17 \(+3.01\) dB calibration offset, so a full-scale sine reads approximately \(-3.01\) dBFS RMS. Peak reveals brief extremes; RMS behaves more like a view of sustained energy. The peak remains a &lt;strong>sample peak&lt;/strong>, not an oversampled true-peak/dBTP measurement, so it does not predict a possibly larger value between stored samples.&lt;/p>
&lt;p>These ballistics run on the bounded real-time capture path and inspect every sample. Their meaning therefore does not change if an analysis worker is briefly late.&lt;/p>
&lt;h3 id="stereo-how-are-left-and-right-related">Stereo: how are left and right related?
&lt;/h3>&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-stereo-correlation.png"
width="576"
height="650"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-stereo-correlation_hu_263c226881c3769f.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-stereo-correlation_hu_75cef76ae6f2d730.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-stereo-correlation_hu_23357ccd719a8e4e.webp 576w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-stereo-correlation_hu_81e9cc669ba79053.jpg 576w"
loading="lazy"
alt="Audio Insight Vectorscope and Correlation meter"
class="gallery-image"
data-flex-grow="88"
data-flex-basis="212px"
>
&lt;/p>
&lt;p>The vectorscope turns each stereo sample pair into a point:&lt;/p>
\[
x_{\mathrm{scope}}=\frac{R-L}{2},
\qquad
y_{\mathrm{scope}}=\frac{L+R}{2}.
\]&lt;p>Audio shared equally by both channels has \(x=0\) and lies on the vertical center axis. Opposite-phase audio has \(y=0\) and spreads horizontally. The coordinates remain tied to full scale rather than being normalized independently on every frame, so a quiet signal is not made to look artificially loud.&lt;/p>
&lt;p>The field keeps the latest 250 ms but bounds its GPU data. For \(W_f=\lceil0.25F_s\rceil\) captured frames, the worker selects one pair every:&lt;/p>
\[
d=\left\lceil\frac{W_f}{4096}\right\rceil
\]&lt;p>frames. At 48 kHz, \(W_f=12000\), \(d=3\), and the cloud contains about 4,000 uniformly spaced points. Their opacity fades with age. This decimation changes only the picture; it does not change the correlation measurement.&lt;/p>
&lt;p>The adjacent correlation value uses every sample, with 300 ms exponentially weighted averages:&lt;/p>
\[
\begin{aligned}
\alpha&amp;=e^{-1/(F_s\cdot0.300)},\\
E_n[z]&amp;=\alpha E_{n-1}[z]+(1-\alpha)z[n].
\end{aligned}
\]\[
\rho[n]=\frac{E_n[LR]}{\sqrt{E_n[L^2]E_n[R^2]}}.
\]&lt;p>The three running values \(E[L^2]\), \(E[R^2]\), and \(E[LR]\) advance in source-sample order on the real-time side, and the implementation clamps the final ratio to \([-1,1]\) against numerical error. A value near \(+1\) means the channels are strongly alike, \(0\) means little linear relationship, and a negative value warns that mono playback may cancel important content. If either averaged channel power is below \(10^{-9}\), equivalent to \(-90\) dBFS RMS, correlation is reported as unavailable rather than dividing by a nearly zero value. A genuinely mono input is labeled MONO and likewise does not receive a synthetic \(+1\) correlation.&lt;/p>
&lt;h3 id="loudness-how-loud-does-it-feel-over-time">Loudness: how loud does it feel over time?
&lt;/h3>&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-loudness.png"
width="384"
height="650"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-loudness_hu_7320b9503e1a0dce.webp 384w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-loudness_hu_ca5aed9118a6d0df.jpg 384w"
loading="lazy"
alt="Audio Insight Loudness meters"
class="gallery-image"
data-flex-grow="59"
data-flex-basis="141px"
>
&lt;/p>
&lt;p>Raw peak level is not perceived loudness. Audio Insight implements BS.1770-5 K-weighting with the Momentary, Short-term, and Integrated semantics commonly used with EBU R128. K-weighting is a pair of filters: a high-frequency shelf models the head&amp;rsquo;s acoustic effect, and a high-pass stage reduces the contribution of very low frequencies. The code derives their coefficients for the current sample rate.&lt;/p>
&lt;p>If \(y_c[n]\) is the K-weighted output of channel \(c\), Audio Insight forms the per-sample energy and a window mean:&lt;/p>
\[
\begin{aligned}
e[n]&amp;=\sum_c y_c[n]^2,\\
z_W&amp;=\frac{1}{N_W}\sum_{n\in W}e[n].
\end{aligned}
\]&lt;p>For the supported mono and stereo layouts, every actual channel has unit weight. Mono therefore contributes once; it is never duplicated into synthetic left and right channels. Surround layouts and their channel weights are outside the current scope. Window energy becomes LUFS (Loudness Units relative to Full Scale) using the BS.1770 offset:&lt;/p>
\[
L_W=-0.691+10\log_{10}z_W.
\]&lt;ul>
&lt;li>Momentary loudness covers 400 ms.&lt;/li>
&lt;li>Short-term loudness covers 3 seconds.&lt;/li>
&lt;li>Integrated loudness uses 400 ms blocks completed every 100 ms—75% overlap—from the latest Reset within the current uninterrupted, visible analysis interval.&lt;/li>
&lt;/ul>
&lt;p>Momentary and Short-term are simple ungated window measurements. Integrated loudness applies gates: thresholds that exclude blocks from the long-term average. For each 400 ms block \(i\), let its mean-square energy be \(z_i\) and its loudness be \(L_i=-0.691+10\log_{10}z_i\). The absolute-passing set is:&lt;/p>
\[
\begin{aligned}
\mathcal A&amp;=\{i\mid L_i>-70\ \mathrm{LUFS}\},\\
\mu_{\mathcal A}&amp;=\frac{1}{|\mathcal A|}\sum_{i\in\mathcal A}z_i.
\end{aligned}
\]&lt;p>At each Integrated update, one non-iterative relative threshold is calculated 10 LU below the preliminary absolute-gated mean of the history accumulated so far:&lt;/p>
\[
\begin{aligned}
\Gamma_{\mathrm{rel}}&amp;=-0.691+10\log_{10}\mu_{\mathcal A}-10,\\
\mathcal R&amp;=\{i\in\mathcal A\mid L_i>\Gamma_{\mathrm{rel}}\}.
\end{aligned}
\]&lt;p>Finally:&lt;/p>
\[
\begin{aligned}
\bar z_{\mathcal R}&amp;=\frac{1}{|\mathcal R|}\sum_{i\in\mathcal R}z_i,\\
L_I&amp;=-0.691+10\log_{10}\bar z_{\mathcal R}.
\end{aligned}
\]&lt;p>Both comparisons are strict \(>\), and the relative gate is not iterated repeatedly. This two-stage gate prevents silence and very quiet passages from dragging the program average down indefinitely. The tile&amp;rsquo;s Reset command restarts Integrated loudness while ready Momentary/Short-term values and K-weighting continuity remain intact. Editor reactivation, an audio discontinuity, or a format change resets the complete loudness analyzer.&lt;/p>
&lt;p>The empty cases are explicit too. If \(\mathcal A\) contains no blocks, the preliminary mean and relative gate remain unavailable. If \(\mathcal R\) is empty, Integrated loudness remains \(-\infty\). The implementation never divides by an empty set.&lt;/p>
&lt;p>The implementation does not claim complete EBU Mode compliance: it does not yet include LRA or true peak, for example. The label describes its M/S/I measurement semantics, not a certification.&lt;/p>
&lt;p>There is an interesting performance problem hiding in Integrated loudness. Within one uninterrupted visible measurement, the exact answer can cover 24 hours: up to 864,000 blocks. Rescanning every qualifying block every 100 ms would make the cost grow throughout the measurement.&lt;/p>
&lt;p>The implementation uses a preallocated sorted index called a B+ tree. It contains finite block energies above the absolute gate and keeps aggregate counts and sums in its branches; all completed blocks still count toward the 24-hour limit. A new relative-gate boundary can be answered by finding one boundary leaf and combining a bounded number of branch totals. Capacity for the worst case occupies about 7.25 MiB on arm64, and the structure never allocates while processing.&lt;/p>
&lt;h2 id="smooth-is-a-timing-property-not-an-fps-number">Smooth is a timing property, not an FPS number
&lt;/h2>&lt;p>The renderer uses &lt;code>CAMetalDisplayLink&lt;/code>, which supplies a drawable in step with a display. While visible, Audio Insight requests the active display&amp;rsquo;s exact reported maximum refresh rate, with a 60 Hz fallback. That request is best effort—Core Animation and the compositor still control actual presentation—so measured presentation timestamps are the truth.&lt;/p>
&lt;p>This distinction became important repeatedly. A counter can say 120 callbacks per second while the screen still changes only 60 times. An average can say 120 FPS while an occasional doubled interval makes scrolling visibly hitch. Smoothness is about the complete chain from callback to presentation and about the distribution of frame intervals, not just one large number.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/images/audio-insight-soundsource.png"
loading="lazy"
alt="Audio Insight running inside SoundSource"
>
&lt;/p>
&lt;h3 id="the-plugin-that-crashed-its-host">The plugin that crashed its host
&lt;/h3>&lt;p>The first AU build appeared for a moment in SoundSource and then disappeared. The host reported only that its Audio Unit hosting service had crashed.&lt;/p>
&lt;p>The detailed log led to an assertion in timed drawable presentation. A normal Metal application may call an API such as timed &lt;code>present&lt;/code>, but a drawable delivered by &lt;code>CAMetalDisplayLink&lt;/code> has different presentation ownership. Combining the two caused the hosting process to assert. The correct sequence is to commit the command buffer and call plain &lt;code>present()&lt;/code> on that drawable, while using the display link&amp;rsquo;s target timestamp only for telemetry and scheduling.&lt;/p>
&lt;p>This is one reason plugin development needs testing in real hosts. SoundSource exposed an API misuse that a successful build or unit test had not.&lt;/p>
&lt;h3 id="why-120-display-callbacks-produced-60-frames">Why 120 display callbacks produced 60 frames
&lt;/h3>&lt;p>After the crash was fixed, the display link was firing close to 120 times per second, but only about 60 frames were submitted. The built-in metrics captured the pattern:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Counter&lt;/th>
&lt;th style="text-align: right">Before the fix&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Display-link callbacks&lt;/td>
&lt;td style="text-align: right">6,453&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Metal submissions&lt;/td>
&lt;td style="text-align: right">3,230&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>GPU-backpressure drops&lt;/td>
&lt;td style="text-align: right">3,223&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sampled display-link callback rate&lt;/td>
&lt;td style="text-align: right">~111/s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sampled Metal submission rate&lt;/td>
&lt;td style="text-align: right">~59.6/s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The cumulative counters cover the full telemetry epoch; the two rates are a sample from its final roughly 0.25 seconds. Almost exactly every other display-link callback was being rejected by the in-flight buffer pool.&lt;/p>
&lt;p>The surprising part was that the GPU was not necessarily too slow. Reusable vertex buffers were retained until the drawable was actually presented. The compositor may hold a drawable for several refresh periods even after GPU execution has completed, so all reusable buffers became occupied and the next callback had nowhere to write.&lt;/p>
&lt;p>The fix was to separate two lifetimes. GPU command completion now releases the reusable buffers immediately. A small, independent object survives only to correlate the later presentation timestamp. The renderer no longer holds large working resources hostage to compositor timing.&lt;/p>
&lt;p>In a later point-in-time M1 Max capture from another development build, with the Metrics panel visible, the drawable was 2,400×1,496 pixels at 2× backing scale. The run recorded 1,188 display-link callbacks, 1,188 submissions, and zero GPU-backpressure drops. Across the most recent 240 presented intervals, the average was 8.438 ms, or 118.52 Hz; 237 intervals were the normal 8.333 ms and three doubled to 16.667 ms. Telemetry also counted 11 skipped presentations over the run. That capture used the then-selected 16,384-point FFT; today&amp;rsquo;s default is 8,192.&lt;/p>
&lt;p>The capture demonstrates that the buffer-lifetime bottleneck and its GPU-backpressure drops were gone. It is evidence of approximately display-rate presentation in that run, not a perfect-pacing claim or a controlled comparison with another plugin.&lt;/p>
&lt;h3 id="making-60-hz-data-scroll-on-a-120-hz-display">Making 60 Hz data scroll on a 120 Hz display
&lt;/h3>&lt;p>The Spectrogram exposed a second kind of stutter. New analysis columns arrive at 60 Hz. If the image moves forward only when a complete column arrives, it necessarily steps every other frame on a 120 Hz display.&lt;/p>
&lt;p>The solution was not to double the FFT workload. The renderer advances a fractional scroll head from the target presentation clock while keeping the actual dB cells discrete. A one-slice cushion absorbs ordinary analysis scheduling jitter. If a texture upload is briefly busy, the renderer postpones that upload while continuing to draw the rest of the dashboard.&lt;/p>
&lt;p>The result is much smoother motion from the same 60-slices-per-second target. This also explains why raising thread priority would have been the wrong first response: the main issue was the relationship between two clocks, not a shortage of real-time privileges.&lt;/p>
&lt;h3 id="the-random-resets-that-were-not-random">The random resets that were not random
&lt;/h3>&lt;p>During longer sessions, all graphs would occasionally reset. The recovery was intentional—when audio history has a real gap, temporal analyzers must not pretend the samples on either side were adjacent—but the handoff overflowed far too easily even while host audio was continuous. Sequence tracking then correctly detected the resulting loss.&lt;/p>
&lt;p>The original capture queue had 16 logical slots and consumed one for each host callback. Its time capacity therefore depended on the host&amp;rsquo;s block size. A metrics capture reached all 16 ready slots, discarded 20 queued chunks to make room for newer audio, and recorded three consumer discontinuities followed by three Loudness resets.&lt;/p>
&lt;p>The redesigned queue packs audio across callback boundaries into 128 slots of 256 frames, retaining 32,768 frames regardless of host callback size. That is about 683 ms at 48 kHz, 341 ms at 96 kHz, or 171 ms at 192 kHz. The capacity and overflow behavior are covered by implementation tests; longer post-redesign host runs remain part of validation. A sufficiently long stall can still overflow it. When that happens, latest data wins and temporal analyzers reset, because joining unrelated pieces of audio would produce convincing but false measurements.&lt;/p>
&lt;h2 id="building-observability-into-the-plugin">Building observability into the plugin
&lt;/h2>&lt;p>Apple&amp;rsquo;s Metal HUD is useful for applications that enable it before creating their first Metal device. A plugin usually arrives after its host has already done that, so it cannot reliably switch the HUD on from a settings button. I replaced that idea with a built-in performance panel available in Release builds.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics.png"
width="2400"
height="1600"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_3e35564a57531db1.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_17a99fe9b1840b47.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_a50d2e90557737cb.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_56e8ba0c2aa2fcf5.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_c16f0ce7449dffc6.webp 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_7ee062fc54661c12.jpg 1536w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_91a2ab2ced51d103.webp 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_5058b4d74de2b309.jpg 2048w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_8f5e390e440110af.webp 2400w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics_hu_f2af51aec60caaa4.jpg 2400w"
loading="lazy"
alt="Frame pacing and latency composition in the Metrics panel"
class="gallery-image"
data-flex-grow="150"
data-flex-basis="360px"
>
&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details.png"
width="1030"
height="1492"
srcset="https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_8fc88fe5b45184b.webp 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_6855df2f78d2e266.jpg 480w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_7bb34ff9e83af0ff.webp 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_ece4367265690785.jpg 1024w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_ce43a78db3f44ba9.webp 1030w, https://charlie0129.github.io/blog/p/building-audio-insight/images/audio-insight-metrics-details_hu_392c9d2f73db95ad.jpg 1030w"
loading="lazy"
alt="Metric details"
class="gallery-image"
data-flex-grow="69"
data-flex-basis="165px"
>
&lt;/p>
&lt;p>It reports exact frame pacing over the latest 240 presentation intervals, derived from 241 timestamps; CPU, submit, GPU, and compositor latency composition; display-link scheduling; audio-callback histograms; queue occupancy and discontinuities; analyzer freshness; and raw copyable metrics for offline inspection. The stacked latency bar covers the pipeline from a display-link callback to presentation. It is not a breakdown of an 8.33 ms frame budget: several frames can overlap in flight, so its total can exceed one refresh interval without reducing presentation cadence.&lt;/p>
&lt;p>Graphs move at vblank, headline numbers refresh at no more than 10 Hz, and the full text table refreshes at 4 Hz. That keeps the visual feedback immediate without rebuilding lots of text 120 times per second. Instrumentation turned several vague reports—“it looks a bit laggy,” “it seems to reset”—into specific, actionable failures.&lt;/p>
&lt;h2 id="an-agent-assisted-human-tested-development-loop">An agent-assisted, human-tested development loop
&lt;/h2>&lt;p>I used coding agents to implement much of Audio Insight. That did not remove the need for a tight feedback loop; it made the loop more important.&lt;/p>
&lt;p>The agents could design the threading model, inspect crash logs, add instrumentation, and reason from raw captures. They could build the plugin, but they could not reliably judge how motion felt inside my particular SoundSource setup. At runnable milestones I installed the AU, watched it on the M1 Max, adjusted settings, and returned observations, screenshots, logs, or copied metrics. Those reports led directly to the presentation-lifetime fix, the fractional Spectrogram scroll, dynamic axis labeling, and the queue redesign.&lt;/p>
&lt;p>For visual and real-time software, “the code is correct” and “the product feels right” are different claims. An instrumented implementation plus a person looking at the actual display proved far more useful than guessing at either one in isolation.&lt;/p>
&lt;h2 id="what-is-open-source-today">What is open source today
&lt;/h2>&lt;p>The current code identifies itself as Audio Insight 0.1.0. It targets macOS 15 on arm64 and builds AUv2 and VST3. It uses C++20, CMake, a pinned JUCE submodule for the plugin shell, CPU FFT analysis accelerated by Apple&amp;rsquo;s vDSP on macOS, and a native Metal renderer. Project-owned code is licensed under AGPL-3.0-or-later; JUCE retains its own upstream AGPL terms.&lt;/p>
&lt;p>The current release policy is pragmatic for a small open-source project: builds use ad hoc signing, and Developer ID signing and notarization are out of scope. Users can build from source. For a downloaded bundle, the documented flow is to verify the published checksum, extract it, clear quarantine only on the intended bundle, apply an ad hoc signature, and verify that signature.&lt;/p>
&lt;p>Older macOS versions, Intel/Universal builds, Windows, and AUv3 are architectural possibilities rather than current support promises. Logic compatibility, broader VST3 host coverage, multi-instance stress testing, and several formal performance gates also remain work in progress.&lt;/p>
&lt;p>The source, build instructions, and current limitations are all in the &lt;a class="link" href="https://github.com/charlie0129/audio-insight" target="_blank" rel="noopener"
>Audio Insight repository&lt;/a>. If you use analyzers but have never looked inside one, I hope the code makes the path from samples to pixels a little less mysterious. And if the Spectrogram glides across a 120 Hz display without drawing attention to the renderer, that is exactly the point.&lt;/p></description></item><item><title>What happens when your Kubernetes master has low memory or slow disk?</title><link>https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/</link><pubDate>Thu, 15 Aug 2024 14:54:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/</guid><description>&lt;h2 id="background">Background
&lt;/h2>&lt;p>We want to test some sandbox application on our new cluster. My colleague reported that the performance is not as expected and wanted me to take a look.&lt;/p>
&lt;p>The sandbox application works like this: it starts a python container, runs some scripts, and exits. They executes scripts provided by the user, so the cluster is expected to create and destroy a lot of containers in a short period of time.&lt;/p>
&lt;h2 id="assumptions">Assumptions
&lt;/h2>&lt;p>Considering the characteristics of the application, the bottleneck could be the following:&lt;/p>
&lt;ul>
&lt;li>CPU/Memory of the worker nodes, or the container runtime&lt;/li>
&lt;li>Performance of the master node (apiserver, etcd, and etc)&lt;/li>
&lt;/ul>
&lt;p>Since we have relatively beefy worker nodes, each with 2*Xeon Platinum 8353v (72C144T) and 512GB of memory, the bottleneck is likely the master node (8C 16GB memory).&lt;/p>
&lt;p>Let&amp;rsquo;s verify this assumption.&lt;/p>
&lt;h2 id="observations">Observations
&lt;/h2>&lt;p>I ran k6 to load test the cluster.&lt;/p>
&lt;p>Pods started to pending&amp;hellip;&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/pending-pods.png"
width="324"
height="514"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/pending-pods_hu_e0659d193f6ba0c7.webp 324w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/pending-pods_hu_ba8aa5c4b632e40c.jpg 324w"
loading="lazy"
alt="Pending Pods"
class="gallery-image"
data-flex-grow="63"
data-flex-basis="151px"
>
&lt;/p>
&lt;p>Hmm, it smelled like a etcd issue (the newly-created Pods is not able to be written to the database). I looked at the etcd dashboard. Sure enough, db backend is having some trouble writing the files.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics.png"
width="2618"
height="1066"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_80d6d18cafca0ce4.webp 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_c269990831e72efd.jpg 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_91c5ccc92849dc2c.webp 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_cf32d92f25ea41c1.jpg 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_9127fd9de5aa228a.webp 1536w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_a0885a5cfc438bd8.jpg 1536w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_8c1d60c12d405fae.webp 2048w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_cad034763199ee91.jpg 2048w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_7189bf7d1cc6fc48.webp 2560w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_8085901669bce26f.jpg 2560w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_ef9cffa4f1c7ec0d.webp 2618w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/etcd-metrics_hu_b63db6228ee138a1.jpg 2618w"
loading="lazy"
alt="etcd metrics"
class="gallery-image"
data-flex-grow="245"
data-flex-basis="589px"
>
&lt;/p>
&lt;p>I grabed the the disk stats: the disks on the master node is almost fully utilized! A better disk (SSD) is really needed.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time.png"
width="2624"
height="1324"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_896f8b53e1d20b40.webp 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_852332f2a2d80d7b.jpg 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_ea8ef60e62c81adf.webp 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_e06faae575985720.jpg 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_f42b0df92f42c16.webp 1536w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_77e90f88e10b09b2.jpg 1536w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_f3ce497608a58da8.webp 2048w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_11e03a19ecaada22.jpg 2048w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_75ecda59dd5360c7.webp 2560w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_afdd7e0c319c18b.jpg 2560w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_14566d795e0e6d8a.webp 2624w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/disk-io-time_hu_cc392e1e774a24d8.jpg 2624w"
loading="lazy"
alt="Disk IO time"
class="gallery-image"
data-flex-grow="198"
data-flex-basis="475px"
>
&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat.png"
width="1280"
height="185"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_2d5369d4b44180a7.webp 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_35ff1e9fe8b6ee07.jpg 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_a384d1e2ec522a4b.webp 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_3fff69ee7108264.jpg 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_a395520b5f31a537.webp 1280w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/iostat_hu_4a88be4cca2c1c44.jpg 1280w"
loading="lazy"
alt="iostat"
class="gallery-image"
data-flex-grow="691"
data-flex-basis="1660px"
>
&lt;/p>
&lt;h2 id="side-note">Side note
&lt;/h2>&lt;p>We also experienced unexpected issue when I applied a cilium config, the whole cluster goes down (Thanks god. This is not a production cluster, or this will be a total disaster). At first, I assumed the cilium config that I written is wrong and I reverted, hoping the cluster will be fixed. Nope, all nodes are down. However, some interesting behavior is noticed: all worker nodes are in a ready - notready loop.&lt;/p>
&lt;p>Upon further investigation, I noticed the apiserver is constantly being OOM killed.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled.jpg"
width="1038"
height="232"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_e91b0622f442775d.webp 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_80c2e6f8dbe3044c.jpg 480w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_1f54d78c59f2929.webp 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_f65127f002ff7e84.jpg 1024w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_c90160b6ead6e703.webp 1038w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-oomkilled_hu_e43b58e01016429a.jpg 1038w"
loading="lazy"
alt="apiserver OOM killed"
class="gallery-image"
data-flex-grow="447"
data-flex-basis="1073px"
>
&lt;/p>
&lt;p>The apiserver is using more memory than the node has.&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-ram-usage.jpg"
width="426"
height="454"
srcset="https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-ram-usage_hu_a36ad326df17beb3.webp 426w, https://charlie0129.github.io/blog/p/what-happens-when-k8s-master-have-low-memory-or-slow-disk/images/apiserver-ram-usage_hu_d3fe3e00e88e29b4.jpg 426w"
loading="lazy"
alt="apiserver RAM usage"
class="gallery-image"
data-flex-grow="93"
data-flex-basis="225px"
>
&lt;/p>
&lt;p>It seems something is hitting the apiserver really hard. Under normal circumstances, I will go to the master node to look at the apiserver logs. However, the master node is down (because OOM) and I cannot ssh into it.&lt;/p>
&lt;p>The cluster is stuck in such a loop:&lt;/p>
&lt;ol>
&lt;li>Worker nodes are all starting (cilium starting) and pulling/changed config through apiserver&lt;/li>
&lt;li>apiserver on the master node OOM&amp;rsquo;ed because of bursts of requests&lt;/li>
&lt;li>the apiserver is OOM killed&lt;/li>
&lt;li>worker node cannot report status to the apiserver&lt;/li>
&lt;li>worker node is marked as not ready; cilium pods are terminated because of nodes are not ready&lt;/li>
&lt;li>go to step 1&lt;/li>
&lt;/ol>
&lt;p>Since the cluster cannot properly start in such a scenario, I have to use some dirty fix to start the cluster: just after the apiserver is started, and before the apiserver eats all memory, such that the master node is still alive, quickly SSH into the master node and kill the apiserver process. By doing this a few times, we can span the burst of requests over a longer period of time, avoiding overloading the apiserver (causing it to be killed).&lt;/p>
&lt;h2 id="solution">Solution
&lt;/h2>&lt;p>The solution is simple: replace the master nodes with faster ones (much more memory, NVME SSDs).&lt;/p></description></item><item><title>高速做种指南</title><link>https://charlie0129.github.io/blog/p/high-speed-seeding-guide/</link><pubDate>Wed, 19 Jun 2024 22:11:00 +0800</pubDate><guid>https://charlie0129.github.io/blog/p/high-speed-seeding-guide/</guid><description>&lt;h2 id="前言">前言
&lt;/h2>&lt;p>由于最近接入了 10Gbps 网络，发现在 PT 做种时，传输速度并没有达到预期的速度，于是优化了一波。本文记录了一些针对高速做种的建议、注意点和技巧。&lt;/p>
&lt;h2 id="硬件条件">硬件条件
&lt;/h2>&lt;h3 id="足够快的网络">足够快的网络
&lt;/h3>&lt;p>做种本质上就是网络传输，快速的网络是做种的基础。&lt;/p>
&lt;h4 id="确保上传带宽">确保上传带宽
&lt;/h4>&lt;p>做种嘛，主要是给别人传输数据，吃的是你的上传带宽，跟下载几乎没有关系，这是最重要的。例如原本 50Mbps 的上传情况下，买一条 100Mbps 的宽带比以下所有的优化都重要。&lt;/p>
&lt;p>但是在中国，一般来说家庭宽带的上行速度都是比较低的。例如，电信的 1000M 宽带，上行速度一般不会超过 100Mbps，通常在 50Mbps 左右。这样的话，你的上传速度就被限制在 6.25MB/s 左右，在这个速度下，其实最主要的瓶颈在于你的上传带宽上，不太需要下面说的硬件/软件优化🫠。你可能需要找不少资源来找到高上传带宽的宽带，例如某些地区的移动宽带会给 IPv6 相当大的上传。你还要注意如何不被运营商认为是 PCDN 而封你的号。&lt;/p>
&lt;p>由于本文聊的是高速做种，针对的是接近 1Gbps 的上传带宽的用户。国外的情况暂且不讨论（高速宽带价格更低，很容易购买到超过 1Gbps 的云服务器、seeding box 等），在国内，拥有高速上传带宽的用户主要有几种情况：&lt;/p>
&lt;ul>
&lt;li>企业用户（例如大型公司、数据中心等），使用价格高昂，上下对等（即下载和上传速度一样快），租云服务器也属于这个范畴；&lt;/li>
&lt;li>高校用户，使用价格较低（对用户来说，对学校来说不是这回事），上下对等；&lt;/li>
&lt;li>其他特殊用户，例如某些地区的移动家庭宽带，500M 宽带上行可以达到 200M。&lt;/li>
&lt;/ul>
&lt;p>如果你是这些用户，那么你的上传带宽可能会达到 1Gbps 甚至更高，这时候就需要考虑下面的硬件/软件优化了。不过估计阅读本文的人大部分是高校教育网用户，低廉的价格超大的上传带宽（&lt;/p>
&lt;p>在本文中，我的接入网络为上下 10Gbps 的教育网 CERNET 对等接入。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet.jpg"
width="4032"
height="3024"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_6ac1c937881d3925.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_767c108a59fde344.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_83dc83859f5e5c2a.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_f5250ca49a0565e3.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_8f0e8942732e023a.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_50f707ef55337ac8.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_1d48a8e9598cc9bb.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_95d49a8c1453da20.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_86ce4cb85657c3aa.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_47d74685617f1d3b.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_ac200fdf6fa3e4d6.webp 4032w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/internet_hu_a16cf42efcc048a5.jpg 4032w"
loading="lazy"
alt="10G 网络接入"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;h4 id="外界可联通">外界可联通
&lt;/h4>&lt;p>大部分情况下，你做种时是别人主动连接你的，所以你的网络需要是可被连接的。&lt;/p>
&lt;p>对于 IPv4 来说：&lt;/p>
&lt;ul>
&lt;li>最好的情况是你拥有一个独立的公网 IP ，这是最简单的情况。如果公网 IP 在你的网卡上，那么你不需要做任何设置。如果公网 IP 在你家的光猫上，那么通过简单的端口映射/UPnP 等就能完成。这种情况国内不常见。&lt;/li>
&lt;li>其次如果你的网络是 NAT1（全锥形 NAT） 的（指国内运营商级 NAT，一般你的光猫上能获取 100.x.x.x 这样的 IP），那么通过一些复杂的奇技淫巧（例如 UDP hole punching）也能实现可被连接。&lt;/li>
&lt;li>最差的情况是你的网络是 NAT4（对称形 NAT） 的，这种情况下，你的网络是不可被连接的，除非你使用一些中继服务器来帮助你穿透。不过这种情况下，因为中继服务器的带宽有限，通常不能达到高速做种的要求，这里不讨论。&lt;/li>
&lt;/ul>
&lt;p>如何判断你是 NAT1 还是 NAT4 ？你可以使用 &lt;a class="link" href="https://github.com/talkiq/pystun3" target="_blank" rel="noopener"
>pystun3&lt;/a>。&lt;/p>
&lt;p>如果你使用 IPv6 做种（常见于教育网），运营商（学校）下发的 IPv6 地址一般都是公网 IP，不需要做任何设置，等同于情况一。&lt;/p>
&lt;p>如果你不确定自己的网络是否可被连接，可以阅读文末注意点。&lt;/p>
&lt;h4 id="peering">Peering
&lt;/h4>&lt;p>在数 Gbps 的高速网络下，你所在的运营商网络和你的种子的 peer 之间的连通性也值得考虑（但没那么重要）。运营商各张网之间，例如电信的 163 骨干网，教育网 CERNET ，甚至国外的运营商等，他们之间互联需要走 BGP ，如果你和你需要连接的用户之间连通性不好，那么你的上传速度可能会受到影响。&lt;/p>
&lt;p>国内一般不需要考虑，这种一般在跨国传输的情况下比较明显，如果你所在的网的国际出口带宽不够，连接国外的 peer 时你会发现你的上传速度上不去。&lt;/p>
&lt;h3 id="足够快的硬盘">足够快的硬盘
&lt;/h3>&lt;p>重要性仅次于网络的是硬盘速度，尤其关注 &lt;strong>随机读&lt;/strong>。一种常见的错误认知是：我的机械硬盘顺序读能有 200MiB/s 那么我就能吃满 1Gbps 的上传带宽应该轻轻松松吧。这是完全错误的，由于做种会产生大量的 &lt;strong>随机读&lt;/strong> ，机械硬盘在这方面是最弱鸡的。&lt;/p>
&lt;p>例如，在 1Gbps 左右上传，大文件做种（活跃数据超过我的内存大小 128GB ）的情况下，能稳定 10k IOPS 直达我的磁盘的读取（例如下图 14k IOPS 的读）。一块 7200 RPM 的 SATA 机械硬盘也就 100 IOPS ，一块 15000RPM 的 SAS 机械硬盘也就 200 IOPS 左右，这有着快 2 个数量级的差距。所以，如果你的硬盘是机械硬盘（HDD, spinning rust :p），建议放弃治疗，一旦内存缓存不够了你的上传速度就 gg 了，这辈子都不可能吃满 1Gbps 的，500Mbps 都够呛。根据我的经验， 3*8TB 的 7200RPM 企业级 HDD RAID5 在活跃数据超过内存缓存（128GB）的情况下也就勉勉强强能吃 200Mbps 的上传带宽（此时磁盘已经 100% busy 了），如果缓存更小的话这个数值甚至更低。&lt;/p>
&lt;p>注意：这里不考虑完全使用内存缓存（因为大部分人都没有几百 GB 的内存）一旦种子活跃数据超过可用的内存缓存大小，硬盘就是你的上传速度的瓶颈。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iops.png"
width="958"
height="214"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iops_hu_a62be9a100d0d422.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iops_hu_1c7bb2b6c6347f1c.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iops_hu_8372a631afe5c126.webp 958w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iops_hu_fb7d4a04fe75413b.jpg 958w"
loading="lazy"
alt="1Gbps 上传时 14k IOPS 的读请求"
class="gallery-image"
data-flex-grow="447"
data-flex-basis="1074px"
>
&lt;/p>
&lt;p>&lt;strong>所以高速做种的场景下一定要用 SSD ，最次也需要是 SATA SSD，建议使用更高性能的 NVME SSD 。&lt;/strong>&lt;/p>
&lt;p>本文使用了 3 块 Micron 5400 PRO 1.92TB 的 SSD 组成的 RAID5 。注意，这对于我的网络接入（10 Gbps）的情况下仍然是远远不够的，正常需要高性能 NVME SSD 才行。不过我大部分情况也就 1Gbps 左右，跑不满 10Gbps ，凑合用吧。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd.jpg"
width="4032"
height="3024"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_71768682229bc521.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_d6fee8c78334f34b.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_c001fde81315af0a.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_eaf6ee13d78ed765.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_124fb4010895bbdc.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_6402bceeb8d010e2.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_b9ce0b7a150e838e.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_9654e30bca52624.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_1c9820c7300efd22.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_bc736ac201db4abb.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_cd89dc8be46fa548.webp 4032w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ssd_hu_c9b36e0dd3f1b2c7.jpg 4032w"
loading="lazy"
alt="3*1.92TB SSD"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;p>如果你不确定你的硬盘是否足够快，那么你可以阅读下文判断磁盘 IO 瓶颈的部分。&lt;/p>
&lt;h3 id="足够大的内存">足够大的内存
&lt;/h3>&lt;p>足够大的内存可以提供：&lt;/p>
&lt;ul>
&lt;li>更大的磁盘缓存，减少磁盘压力（高速上传时，SSD 也不一定能跟上）；&lt;/li>
&lt;li>更大的 send buffer ，提高网络传输效率/IO 吞吐；&lt;/li>
&lt;/ul>
&lt;p>一般来说 32GB 左右能够满足需求，本文中使用了 128GB 内存测试。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram.jpg"
width="3798"
height="2848"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_c191358532ddc4c2.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_9d471c4430e49edf.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_43fe4b0fef2e6b69.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_402892627914cb2.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_52d2d5e40026f648.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_d724384199af9004.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_2afcdd7fdb60b33.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_bc29c00f9d8ef12d.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_6d02889ac6f6666c.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_63f9f9dddf15c0b4.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_92a17f68f1a78e67.webp 3798w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ram_hu_3fd62f22e407ab85.jpg 3798w"
loading="lazy"
alt="4*32G 内存"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;h3 id="足够快的网卡">足够快的网卡
&lt;/h3>&lt;p>更好的网卡能够提供更多的 hardware offloading ，减轻 CPU 的负担，提高网络传输效率。一些高端网卡也支持更多的 queue ，提高并发传输能力，例如 CX-4 Lx 支持 64 个 queue ，可以看到这些 queue 上的收发包表示它们在被积极利用（实际上一般软件无法同时用不到这么多，包括 qBittorrent，所以 8 队列的 I350 就够了）。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue.png"
width="2740"
height="1586"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_87ce5e9f2b42c91c.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_62a5a3e65a27f316.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_5b4bbc9df213a117.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_b2a214ba7c5f5ee8.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_542f8196789379d5.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_cf6cd8609c59c3fc.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_bc6b7143feecacd7.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_48256ba81691ab76.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_5652cdd9c2b6a3f8.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_a7833ed4ebdb1735.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_d9217edb15a1e7b7.webp 2740w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/cx4lxqueue_hu_17635e13d599281b.jpg 2740w"
loading="lazy"
alt="网卡队列"
class="gallery-image"
data-flex-grow="172"
data-flex-basis="414px"
>
&lt;/p>
&lt;p>最差也要使用 Intel 的 Gigabit 网卡，例如 I350 、I210 ，更好的可以用 Intel X520/X540 ，甚至 Mellanox ConnectX-4/5/6 等更高端的网卡。不要用 Realtek （小螃蟹）的，在大连接数吃满 1Gbps 的情况下性能很烂。&lt;/p>
&lt;p>例如下图是我的计算机在 700Mbps 下行 1.2Gbps 上行时每秒的收发包数量，约为 170 Kpps 。性能差的网卡是很难达到这个性能的。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps.png"
width="1670"
height="1032"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_77807694310453f9.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_fddbc5eb561ac6c8.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_55fbbdf92302ed4b.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_a5c85b55ef82325b.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_5d7e43d87ebf74d3.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_b40e6c40d339d6fb.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_475920fadb32cc1e.webp 1670w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/kpps_hu_95bc7e52a0b5118f.jpg 1670w"
loading="lazy"
alt="1.5Gbps 时每秒的收发包数量"
class="gallery-image"
data-flex-grow="161"
data-flex-basis="388px"
>
&lt;/p>
&lt;p>本文中使用了 Mellanox ConnectX-4 Lx 25G (CX4121A) 网卡（虽然现在被 NVIDIA 收购了，不过我还是习惯称之为 Mellanox ）。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic.jpg"
width="4032"
height="3024"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_1849ad8ebc2844d1.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_10f8f6bb31259317.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_c863a182290b84f4.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_2264a97cb298ba32.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_b34ed9be158631ed.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_dda5cd1ddcbab175.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_6268e08092eb7de0.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_b19c563ca0f327b3.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_6d8a8d1d0119eb94.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_32cee5281466fa60.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_7d3c99e43441cdc6.webp 4032w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/nic_hu_38190a6091d312bd.jpg 4032w"
loading="lazy"
alt="Mellanox ConnectX-4 Lx 25G"
class="gallery-image"
data-flex-grow="133"
data-flex-basis="320px"
>
&lt;/p>
&lt;h3 id="足够快的-cpu">足够快的 CPU
&lt;/h3>&lt;p>一个正常的现代 CPU 即可，例如 Intel Core i5-12500 。请不要使用树莓派、老掉牙的 CPU（例如 Intel Xeon E5-2403 v2 这种 4C4T 1.8GHz 的垃圾），这种情况下，你的 CPU 仍然是整个系统的瓶颈，特别是在高速上传时，CPU 的负载会很高。网卡越烂，上传速度越快，你就需要越好的 CPU 来处理。&lt;/p>
&lt;p>本文中使用了国产的飞腾 2000+ ，虽然看起来有 64 个 ARM64 核心，但是性能还是比较烂的，不太够说实话，用起来跟只有 10 个核的 i9-10900K 也差的比较多。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-fallback" data-lang="fallback">&lt;span class="line">&lt;span class="cl">Architecture: aarch64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CPU op-mode(s): 64-bit
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Byte Order: Little Endian
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Address sizes: 44 bits physical, 48 bits virtual
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">CPU(s): 64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">On-line CPU(s) list: 0-63
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Thread(s) per core: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Core(s) per socket: 64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Socket(s): 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node(s): 8
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Vendor ID: Phytium
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BIOS Vendor ID: Phytium
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Model: 2
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Model name: FTC662
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BIOS Model name: FT-2000+/64
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Stepping: 0x1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">BogoMIPS: 100.00
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">L1d cache: 2 MiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">L1i cache: 2 MiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">L2 cache: 512 MiB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node0 CPU(s): 0-7
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node1 CPU(s): 8-15
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node2 CPU(s): 16-23
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node3 CPU(s): 24-31
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node4 CPU(s): 32-39
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node5 CPU(s): 40-47
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node6 CPU(s): 48-55
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">NUMA node7 CPU(s): 56-63
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Flags: fp asimd evtstrm crc32 cpuid
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="软件调优">软件调优
&lt;/h2>&lt;h3 id="qbittorrent">qBittorrent
&lt;/h3>&lt;h4 id="基本设置">基本设置
&lt;/h4>&lt;ul>
&lt;li>提升连接限制。如果你的网络/磁盘足够强劲，你可以去除所有的连接限制：&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn.png"
width="1214"
height="392"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_58678337e5f2ed18.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_e5e23de374656a6d.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_c5d1250f5b48fb1d.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_89b1ba58012bc366.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_6442abab20c802f8.webp 1214w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbconn_hu_dd8053d8272ca36c.jpg 1214w"
loading="lazy"
alt="qBittorrent 连接限制"
class="gallery-image"
data-flex-grow="309"
data-flex-basis="743px"
>
&lt;/p>
&lt;ul>
&lt;li>提高队列。磁盘性能足够的情况下，可以同时让多个种子做种，你也可以直接关闭：&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue.png"
width="1214"
height="348"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_51f5f9e10d26bc57.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_1057e3f4efadcddb.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_54ef5b83b29b38cc.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_d94a596255581773.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_4f3838a1b6c6c870.webp 1214w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbqueue_hu_172e404a719f51e3.jpg 1214w"
loading="lazy"
alt="qBittorrent 队列"
class="gallery-image"
data-flex-grow="348"
data-flex-basis="837px"
>
&lt;/p>
&lt;ul>
&lt;li>提高内存限制。虽然目前（2024 年 6 月）这个设置只对 Windows 有效，不过设置了也无妨：&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim.png"
width="1214"
height="326"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_fb53d4fefcd0da47.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_32b1a80a9660462f.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_6d3cf036f929c20d.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_cecaab0e51904a91.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_b6c7fc17dadeb429.webp 1214w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbramlim_hu_8c7f1e8080e36ae6.jpg 1214w"
loading="lazy"
alt="qBittorrent 内存限制"
class="gallery-image"
data-flex-grow="372"
data-flex-basis="893px"
>
&lt;/p>
&lt;h4 id="libtorrent-调优">libtorrent 调优
&lt;/h4>&lt;p>我们针对 libtorrent &amp;gt;=2.0 版本，1 Gbps 左右的对等带宽， 64G 左右内存的用户，其他用户请酌情调整以下参数。&lt;/p>
&lt;ul>
&lt;li>&lt;code>aio_threads&lt;/code> &lt;em>Asynchronous I/O threads&lt;/em> 默认 10 。例如在高速下载时，由于需要把下载的数据都坐 SHA1/SHA256 校验，CPU 可能存在瓶颈。提高这个值可以减轻让多核 CPU 并行处理。常见的说法是设置为 4 *线程数 。由于我使用了 64 核的处理器，256 显得过大了，所以我设置为 64 。&lt;/li>
&lt;li>&lt;code>file_pool_size&lt;/code> &lt;em>File pool size&lt;/em> 默认 40。一个 session 直接同时打开多少个文件。大量做种时，这个值设置的太小会导致频繁的文件打开/关闭，降低性能。而又设置的太大会超过一个进程的文件描述符限制（ulimit -n），导致打开文件失败。我设置为 900 （Linux 默认的文件描述符限制是 1024），留一些给其他的文件使用。&lt;/li>
&lt;li>&lt;code>max_queued_disk_bytes&lt;/code> &lt;em>Disk queue size&lt;/em> 默认 1024 KiB 。这个值表示磁盘 IO 写入队列的大小，如果达到了这个上限，libtorrent 就会停止下载，直到磁盘完成了一部分写入。这个值设置的太小会导致下载性能下降，设置的太大会导致内存占用过高。我设置为 262144 KiB 。&lt;/li>
&lt;li>&lt;code>default-disk-io-constructor&lt;/code> &lt;em>Disk IO type&lt;/em> 默认 &lt;code>mmap_disk_io_constructor&lt;/code> 。这个值表示 libtorrent 使用的磁盘 IO 类型，&lt;code>posix_disk_io_constructor&lt;/code> 是使用 POSIX IO ，&lt;code>mmap&lt;/code> 是使用 mmap ，mmap 在 kernel 里有大量优化，建议只用 mmap （Memory Mapped Files）。如果你看到了大量内存占用，请看文末注意点。&lt;/li>
&lt;li>&lt;code>disk_io_write_mode&lt;/code> 和 &lt;code>disk_io_read_mode&lt;/code> 默认 &lt;code>enable_os_cache&lt;/code> 。维持默认。&lt;/li>
&lt;li>&lt;code>piece_extent_affinity&lt;/code> &lt;em>Use piece extent affinity&lt;/em> 默认 &lt;code>0&lt;/code> 。这个值表示 libtorrent 会尽量下载相邻的 piece ，提高磁盘 IO 吞吐。建议打开。&lt;/li>
&lt;li>&lt;code>suggest_mode&lt;/code> &lt;em>Send upload piece suggestions&lt;/em> 默认 &lt;code>no_piece_suggestions&lt;/code> 。这个值表示会告诉从我这边下载的 peer 建议下载那些在我读缓存里的 piece 而不是其他的没在缓存里的，可以提高做种速度减少磁盘 IO 。建议打开。&lt;/li>
&lt;li>&lt;code>send_buffer_low_watermark&lt;/code> &lt;em>Send buffer low watermark&lt;/em> 默认 10 KiB。最小的目标 send buffer 大小（包括等待磁盘读取的字节数）。这实际上是初始窗口大小，它决定了我们能够多快地提高发送速率。我设置为 4096 KiB。&lt;/li>
&lt;li>&lt;code>send_buffer_watermark&lt;/code> &lt;em>Send buffer watermark&lt;/em> 默认 500 KiB。如果发送缓冲区的字节数少于它，将从磁盘额外读取 16 KiB 的数据到缓冲区。这是上限，所以实际大小可能小于它。过小影响上传速率，过大浪费内存。我设置为 32768 KiB。&lt;/li>
&lt;li>&lt;code>send_buffer_watermark_factor&lt;/code> &lt;em>Send buffer watermark factor&lt;/em> 默认 50。这是个百分比，peer 的上传速率乘以这个因子以获得 实际的 send_buffer_watermark，但是不超过上面的 send_buffer_watermark。对于高速连接，设置得更高可以提高上传性能和磁盘吞吐，设置得过高可能会浪费内存并且偏好读。我的目的是高速做种，我设置为 200。&lt;/li>
&lt;li>&lt;code>connection_speed&lt;/code> &lt;em>Outgoing connections per second&lt;/em> 默认 30。这个值表示每秒最多建立多少个出站连接，影响去下载 peer 那边数据的效率。我设置为 500 。&lt;/li>
&lt;li>&lt;code>listen_queue_size&lt;/code> &lt;em>Socket backlog size&lt;/em> 默认 5 。它是传递给监听 socket listen() 的值，用于指定未处理传入连接的队列长度。当我们不积极等待连接被接受时，这些连接将会被排队。由于我们是大带宽高性能做种，我设置为 3000。&lt;/li>
&lt;li>&lt;code>mixed_mode_algorithm&lt;/code> &lt;em>μTP-TCP mixed mode algorithm&lt;/em> 默认 &lt;code>prefer_tcp&lt;/code> 。这个值用于确定在同时存在 TCP 和 uTP 连接时如何处理这两种连接。我们不需要为 uTP 做出让步，所以设置为 &lt;code>prefer_tcp&lt;/code> 。&lt;/li>
&lt;li>&lt;code>allow_multiple_connections_per_ip&lt;/code> &lt;em>Allow multiple connections from the same IP address&lt;/em> 默认 &lt;code>true&lt;/code> 。这个值表示是否允许同一个 IP 建立多个连接。我们不需要限制，所以设置为 &lt;code>true&lt;/code> 。&lt;/li>
&lt;li>&lt;code>choking_algorithm&lt;/code> &lt;em>Upload slots behavior&lt;/em> 使用 &lt;code>fixed_slots_choker&lt;/code>。&lt;/li>
&lt;li>&lt;code>seed_choking_algorithm&lt;/code> &lt;em>Upload choking algorithm&lt;/em> 使用 &lt;code>fastest_upload&lt;/code>。&lt;/li>
&lt;li>&lt;code>max_out_request_queue&lt;/code> &lt;em>Maximum outstanding requests to a single peer&lt;/em> 默认 500 。表示发给 peer 的最大未处理请求数。我设置为 2000 。&lt;/li>
&lt;/ul>
&lt;h3 id="自动下载自动-announce">自动下载/自动 Announce
&lt;/h3>&lt;p>一般种子流量最大的时候为刚发布之后，为了在种子发布后能够快速下载，抢占最早的上传机会。我们需要自动下载种子，然后立即开始做种。称之为 &lt;a class="link" href="https://www.reddit.com/r/seedboxes/comments/9brx3b/comment/e570unm/" target="_blank" rel="noopener"
>racing&lt;/a> 。&lt;/p>
&lt;p>这里我使用了自己编写的脚本，你也可以用 autodl-irssi 等工具。&lt;/p>
&lt;h2 id="注意点">注意点
&lt;/h2>&lt;h3 id="判断磁盘-io-瓶颈">判断磁盘 IO 瓶颈
&lt;/h3>&lt;p>使用 &lt;code>iostat -xm 1&lt;/code>，找到存放种子数据的盘，特别关注最后的百分比，接近 100% 表示你的磁盘是瓶颈了。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy.png"
width="1592"
height="548"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_8e840d1701b93e6d.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_94e9ad173e6d24c9.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_14f1a8ded2e0aeaa.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_2b548b2ad414ff64.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_6fc427ebc2151ce.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_314aa34dc66cf6b8.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_500cb34a123095c6.webp 1592w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/iobusy_hu_e248c56d4a19ab45.jpg 1592w"
loading="lazy"
alt="iostat 查看磁盘 busy%"
class="gallery-image"
data-flex-grow="290"
data-flex-basis="697px"
>
&lt;/p>
&lt;p>你也可以在 htop 中看到 IO 的情况（在 Setup 的 Meter 中添加 Disk IO ）。htop 中的百分比是所有磁盘的合计，如果的下载盘是软 RAID 你有可能看到超过 100% 的情况，我这里是单个设备（硬 RAID），所以图中 99.1% 表示 IO 已经满了，我的硬盘已经跟不上了。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio.png"
width="1764"
height="1058"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_8f24ef2035839054.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_30d6f43da75aa995.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_df21ce627ddf944a.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_11af5105f7251320.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_32f3c744550c6b9a.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_8dbd2d5f6ad8a85b.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_d099b6907945a3a4.webp 1764w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopio_hu_803d2dbdec47c5fb.jpg 1764w"
loading="lazy"
alt="iostat 查看磁盘 busy%"
class="gallery-image"
data-flex-grow="166"
data-flex-basis="400px"
>
&lt;/p>
&lt;p>上面两张图是我使用的 3 块 SSD 阵列在 1~2 Gbps 上传，IO 爆发时的情况，可以看到其实 IO 已经快满了。如果你发现你的 busy% 接近 100% 了，那么你的磁盘 IO 就是瓶颈了，建议更换更好更快的 SSD。&lt;/p>
&lt;p>如果你用的是机械硬盘，应该是轻轻松松全程 100% ，完全撑不住 :p&lt;/p>
&lt;h3 id="判断网络-io-瓶颈">判断网络 IO 瓶颈
&lt;/h3>&lt;p>首先查看你的上传/下载速度是否超过了你的互联网带宽/物理网络接口的速度。你可以在 htop 中看到（记得在 Setup -&amp;gt; Meters 中添加 Network IO ）如果超过了，那么没什么办法，除非换网络/换网卡。如果没有，可以继续往下看。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet.png"
width="2722"
height="922"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_39b730d379e2537e.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_aff10b94c3300115.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_a365a3b4adb354e.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_af3b6755ca9f1180.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_af0c19b2ef7319a8.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_69ee16580ff6ca.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_278ec5f58bc76014.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_140bd045cd9605b5.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_ff96964e602d8e34.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_3c33761982f188bc.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_86c2f57bbab57aed.webp 2722w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnet_hu_2b75c24c58f08f20.jpg 2722w"
loading="lazy"
alt="查看网络 IO "
class="gallery-image"
data-flex-grow="295"
data-flex-basis="708px"
>
&lt;/p>
&lt;p>如果你看到 softirq 占用过高，那么你的网络 IO 可能是瓶颈了，建议更换更好的 CPU 或网卡。&lt;/p>
&lt;p>例如你可以在 htop 中看到紫色的 softirq 的占用（注意在 Setup -&amp;gt; Display Options 中勾选 Detailed CPU time ）。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi.png"
width="2102"
height="1166"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_3af233a81c09fd40.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_5fd4489618a27bc7.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_980275abcfcfd458.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_c6d3d601f305cd5e.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_94b32dc2125c154f.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_6717b7ce1455fe2d.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_c3cc8d5dfb232735.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_a758ab25c55721a.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_a0f8ebf1b6d4403.webp 2102w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsi_hu_69f253ef2190ffaf.jpg 2102w"
loading="lazy"
alt="查看 softirq"
class="gallery-image"
data-flex-grow="180"
data-flex-basis="432px"
>
&lt;/p>
&lt;p>如果你发现某个/几个核心的 softirq 占满了，那么你的网络 IO 可能是瓶颈了。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull.png"
width="2102"
height="1220"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_4a20a810e483c3ad.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_8383303e10b8a357.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_c48bbaa0dab1f4f4.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_6d14b03095505c1a.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_3bc5b14ec904fc34.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_404bd3920b676aad.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_7d42f4a53a1727b8.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_a2e7e58ddcd5d0fc.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_377b6086792d1fd8.webp 2102w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopsifull_hu_eb0b11315ab54899.jpg 2102w"
loading="lazy"
alt="异常 softirq"
class="gallery-image"
data-flex-grow="172"
data-flex-basis="413px"
>
&lt;/p>
&lt;p>正常情况你不应该看到很高的 softirq 占用。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal.png"
width="2740"
height="1586"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_181ca5070d41f539.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_e34cf6dc9e800900.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_de1953cdfa086a0a.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_ad94b0c41228fd7.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_a5f626a96dc36b4f.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_c855769df5351d79.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_3fb88060b81d7b.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_98568b3595860d40.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_b989fa81ff85e195.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_2380bfeb46069303.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_82aa9f4e705e30b2.webp 2740w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/htopnormal_hu_8ad82b1db7fbcbc3.jpg 2740w"
loading="lazy"
alt="正常 softirq"
class="gallery-image"
data-flex-grow="172"
data-flex-basis="414px"
>
&lt;/p>
&lt;h3 id="判断-cpu-瓶颈">判断 CPU 瓶颈
&lt;/h3>&lt;p>一般来说，通过看 htop 即可。注意，你不仅要看全部的 CPU 核心占用，&lt;strong>还要看单个核心的占用&lt;/strong>。如果你发现某个核占满了（这种情况更易发生）、整体 CPU 占用很高（目前 CPU 核心很多了，这个不太容易发生），那么你的 CPU 可能是瓶颈了。同时，看到 CPU 占用很高时，注意上面一节提到的 softirq 占用，这可能是网络 IO 的瓶颈。&lt;/p>
&lt;p>例如在我的计算机上，我偶尔在 IO 爆发时能见到 CPU 占用 5000%+ 的峰值，这说明我的CPU对我的使用场景来说不太行。（图中其实你会发现我的磁盘IO也满了。。）&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage.png"
width="2722"
height="808"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_9d559c0eb7270955.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_f4a3a2afe1c20c29.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_a7e748e9146256a9.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_82963b4c241d3c9.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_86f3e3b76f78431.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_58a9171ecd19a249.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_c979e156553852b6.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_34d7e33952be4024.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_1d74d058da36678b.webp 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_f1e32d7d9424f117.jpg 2560w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_a4988cfe170de731.webp 2722w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/ft2000cpuusage_hu_e0d2c987d175cb1c.jpg 2722w"
loading="lazy"
alt="FT2000 在偶尔 IO 爆发时 qBittorrent 4000%&amp;#43; 的 CPU 使用率"
class="gallery-image"
data-flex-grow="336"
data-flex-basis="808px"
>
&lt;/p>
&lt;h3 id="qbittorrent-内存占用过大">qBittorrent 内存占用过大？
&lt;/h3>&lt;p>如果你发现 qBittorrent 的内存占用过大，你不应该担心。这是 mmap 的特性，它会把文件映射到内存中，这样可以减少磁盘 IO ，提高性能。如果你关注总的内存占用，你会发现它并不会增加。自从 libtorrnet 2.0 使用 mmap 以来，有很多用户 &lt;a class="link" href="https://github.com/arvidn/libtorrent/issues/6667" target="_blank" rel="noopener"
>反馈内存占用过大&lt;/a> ，但是这是正常的。&lt;/p>
&lt;p>例如我的计算机上，qBittorrent 的内存占用超过了 80GB ，但是总的内存占用只有 5GB。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram.png"
width="2102"
height="1166"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_70a9efbe71026e87.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_e3dfd41e12515fbe.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_ce62f2236ae330c2.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_90c9189215d4c444.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_3f52a51b9e8ceda6.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_13adb9beee421a9a.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_471f9a2421ec67fe.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_187bbb8a73d620e2.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_ba8802af2b1bcc35.webp 2102w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/qbhighram_hu_1968d432d6ec3563.jpg 2102w"
loading="lazy"
alt="qBittorrent 内存占用示例"
class="gallery-image"
data-flex-grow="180"
data-flex-basis="432px"
>
&lt;/p>
&lt;h3 id="检测是否可被连接">检测是否可被连接
&lt;/h3>&lt;p>你可以使用外网 TCPing 来检查，例如一些网站可以替你检测： &lt;a class="link" href="https://www.itdog.cn/tcping" target="_blank" rel="noopener"
>IPv4&lt;/a>、&lt;a class="link" href="https://www.itdog.cn/tcping_ipv6" target="_blank" rel="noopener"
>IPv6&lt;/a> 。输入你的公网 IP 和端口，如果各地均能够 ping 通，那么你是可被连接的。&lt;/p>
&lt;p>&lt;img src="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping.png"
width="2458"
height="1080"
srcset="https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_c796628aa02a8bd7.webp 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_f101d91e676d272f.jpg 480w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_914572234e2e0626.webp 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_d3f4d698c48b4105.jpg 1024w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_ab5d8f75d003e7b0.webp 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_178cf78b979e3329.jpg 1536w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_d6429e75146e855f.webp 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_1070a1e6ddafb629.jpg 2048w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_73fff28e0081cb5a.webp 2458w, https://charlie0129.github.io/blog/p/high-speed-seeding-guide/images/tcping_hu_c118bdf43f78eb78.jpg 2458w"
loading="lazy"
alt="外网 TCPing"
class="gallery-image"
data-flex-grow="227"
data-flex-basis="546px"
>
&lt;/p>
&lt;h3 id="上传仍然不及预期">上传仍然不及预期？
&lt;/h3>&lt;p>如果你各方面都确认没有瓶颈（网络、磁盘、CPU 等）但是上传速度仍然很慢，那么可能是你的种子不够热门，或者说你的 peer 的下行太慢了。这方面除了选择热门的种子（做种人少，下载人多，提高下载的 peer 连接你的机会），其实也没什么办法了。&lt;/p>
&lt;p>因为你的上传是需要 peer 来连接你的，这就不是你能决定的事，可遇而不可求。举个例子，如果想跑满 10Gbps 的上传，那么需要 10 个 peer ，每个都以 1Gbps 的速度从你这拉数据，这是非常困难的。首先，别人的下载速度很少有 1G 的；其次，做种的人几十个，别人不可能只从你这下载，很可能从几十个不同的 peer 那边下载，一下就分散了，一个 peer 到你这只有几十分之一的流量了。&lt;/p>
&lt;p>所以，这种情况下，除了选择合适的种子提高 peer 连接你的机会也没什么办法了。合适的种子通常是刚发布、热门、做种人少、下载人多的种子。&lt;/p></description></item></channel></rss>