<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Performance | AkitaOnRails.com</title><link>https://akitaonrails.github.io/en/tags/performance/</link><description>Fabio Akita's blog — tech, career, and assorted geek topics. English edition.</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 19 Oct 2026 23:20:52 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/en/tags/performance/index.xml" rel="self" type="application/rss+xml"/><item><title>Review: Minisforum MS-S1 Max | AMD AI Max+ 395 with 96GB of VRAM</title><link>https://akitaonrails.github.io/en/2026/03/31/minisforum-ms-s1-max-amd-ai-max-395-review/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2026/03/31/minisforum-ms-s1-max-amd-ai-max-395-review/</guid><pubDate>Tue, 31 Mar 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;If you&amp;rsquo;ve been following my &lt;a href="https://akitaonrails.github.io/en/2024/04/03/my-personal-netflix-with-docker-compose/"&gt;home server posts&lt;/a&gt;, you know I used to run everything on an Intel NUC Core i7 with 32GB of RAM. It worked. But as open source AI models grew, the NUC became the bottleneck. Without a dedicated GPU, any LLM inference would fall back to the CPU and turn unusable.&lt;/p&gt;
&lt;p&gt;I bought a Minisforum MS-S1 Max with the new AMD Ryzen AI Max+ 395 chip for one specific reason: this chip supports up to 128GB of unified RAM, and I can allocate 96GB of it as VRAM for the iGPU. That gives me more VRAM than any consumer gaming card, including the RTX 5090 (32GB). And that changes what I can run locally.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you&rsquo;ve been following my <a href="/en/2024/04/03/my-personal-netflix-with-docker-compose/">home server posts</a>, you know I used to run everything on an Intel NUC Core i7 with 32GB of RAM. It worked. But as open source AI models grew, the NUC became the bottleneck. Without a dedicated GPU, any LLM inference would fall back to the CPU and turn unusable.</p>
<p>I bought a Minisforum MS-S1 Max with the new AMD Ryzen AI Max+ 395 chip for one specific reason: this chip supports up to 128GB of unified RAM, and I can allocate 96GB of it as VRAM for the iGPU. That gives me more VRAM than any consumer gaming card, including the RTX 5090 (32GB). And that changes what I can run locally.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-desk.jpg" alt="Minisforum MS-S1 Max on the desk"  loading="lazy" /></p>
<h2>Why ditch the Intel NUC<span class="hx:absolute hx:-mt-20" id="why-ditch-the-intel-nuc"></span>
    <a href="#why-ditch-the-intel-nuc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The NUC was a fine Docker server for two years. But the limitation was clear: without a GPU with enough VRAM, I couldn&rsquo;t run LLMs locally in any usable way. <a href="https://github.com/akitaonrails/FrankYomik"target="_blank" rel="noopener">Frank Yomik</a>, my automatic manga translation system, needed CPU-based OCR (slow) and would connect remotely to the Ollama running on my desktop (AMD 7950X3D + RTX 5090) for translation. It worked, but it meant my desktop had to be on for the server to do its job.</p>
<p><img src="https://raw.githubusercontent.com/akitaonrails/FrankYomik/master/docs/sample_translate.png" alt="Frank Yomik - automatic manga translation"  loading="lazy" /></p>
<p>With the Minisforum, Frank Yomik now runs entirely on the server. The worker uses ROCm for OCR on the iGPU, and Ollama runs locally with 96GB of VRAM. Zero dependency on the desktop.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-nuc-compare.jpg" alt="Comparison: Intel NUC (left) vs Minisforum MS-S1 Max (right)"  loading="lazy" /></p>
<p>You can get a sense of the size from the photo. The NUC is the tiny cube on the left. The Minisforum is bigger but it&rsquo;s still a mini-PC. It fits on the rack shelf under my Synology NAS without any trouble.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-shelf.jpg" alt="Minisforum installed on the shelf, next to the NAS"  loading="lazy" /></p>
<h2>The specs<span class="hx:absolute hx:-mt-20" id="the-specs"></span>
    <a href="#the-specs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-fastfetch.png" alt="fastfetch on the Minisforum"  loading="lazy" /></p>
<p>The chip is the AMD Ryzen AI Max+ 395: 16 cores / 32 threads Zen 5, with an integrated Radeon 8060S iGPU and 128GB of unified LPDDR5X. In the BIOS, I set the UMA Frame Buffer Size to 96GB, which leaves ~30GB of RAM for the operating system and containers. Plus the kernel parameters for TTM (without them, ROCm only sees 15.5GB even with the BIOS allocation in place).</p>
<p>The operating system is openSUSE MicroOS (more on that in the <a href="/en/2026/03/31/migrating-my-home-server-with-claude-code/">next post</a>). The whole machine pulls under 100W, which is absurd if you&rsquo;re used to dedicated GPUs that draw 450W+ on their own.</p>
<h2>Minisforum vs my desktop: benchmarks<span class="hx:absolute hx:-mt-20" id="minisforum-vs-my-desktop-benchmarks"></span>
    <a href="#minisforum-vs-my-desktop-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I ran a <a href="https://github.com/akitaonrails/homelab-docs/tree/master/benchmarks"target="_blank" rel="noopener">set of benchmarks</a> comparing the Minisforum against my desktop (AMD 7950X3D, 96GB DDR5, RTX 5090 32GB GDDR7). The results are clear.</p>
<h3>CPU<span class="hx:absolute hx:-mt-20" id="cpu"></span>
    <a href="#cpu" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Test</th>
          <th>7950X3D</th>
          <th>AI Max+ 395</th>
          <th>Winner</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prime sieve (single-core)</td>
          <td>0.021s</td>
          <td>0.018s</td>
          <td>Strix Halo +14%</td>
      </tr>
      <tr>
          <td>Float pi (single-core)</td>
          <td>1.335s</td>
          <td>1.706s</td>
          <td>7950X3D +28%</td>
      </tr>
      <tr>
          <td>Multi-core sieve (32 threads)</td>
          <td>0.181s</td>
          <td>0.118s</td>
          <td>Strix Halo +53%</td>
      </tr>
      <tr>
          <td>SHA-256 throughput</td>
          <td>2.714 MB/s</td>
          <td>2.488 MB/s</td>
          <td>7950X3D +9%</td>
      </tr>
      <tr>
          <td>AES-256-CBC throughput</td>
          <td>1.613 MB/s</td>
          <td>1.410 MB/s</td>
          <td>7950X3D +14%</td>
      </tr>
  </tbody>
</table>
<p>Mixed results. The AI Max+ 395 is better at pure parallelism (multi-core sieve), probably thanks to lower latency in the unified memory architecture. The 7950X3D wins at float and crypto because of its higher clocks and the 3D V-Cache.</p>
<h3>LLM inference (models that fit on both)<span class="hx:absolute hx:-mt-20" id="llm-inference-models-that-fit-on-both"></span>
    <a href="#llm-inference-models-that-fit-on-both" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This is where it gets interesting. For models that fit in the 32GB of the RTX 5090, the comparison is purely about memory bandwidth:</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>Size</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>5090 advantage</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>9.1 GB</td>
          <td>155.1</td>
          <td>23.2</td>
          <td>6.7x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>9.3 GB</td>
          <td>138.9</td>
          <td>22.6</td>
          <td>6.1x</td>
      </tr>
      <tr>
          <td>phi4-reasoning</td>
          <td>11.1 GB</td>
          <td>130.2</td>
          <td>19.1</td>
          <td>6.8x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>20.2 GB</td>
          <td>66.9</td>
          <td>10.0</td>
          <td>6.7x</td>
      </tr>
  </tbody>
</table>
<p>The RTX 5090 is ~7x faster. The explanation is simple: GDDR7 has ~1,792 GB/s of bandwidth. LPDDR5X has ~256 GB/s. The ratio (7x) lines up almost exactly with the measured speed difference (6.7x). LLM inference is a problem dominated by memory bandwidth. Whoever reads weights faster, generates tokens faster.</p>
<h3>And what about prompt processing?<span class="hx:absolute hx:-mt-20" id="and-what-about-prompt-processing"></span>
    <a href="#and-what-about-prompt-processing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Model</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>5090 advantage</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>~1,933</td>
          <td>~212</td>
          <td>9.1x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>~1,474</td>
          <td>~155</td>
          <td>9.5x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>~767</td>
          <td>~68</td>
          <td>11.3x</td>
      </tr>
  </tbody>
</table>
<p>Prompt processing is even worse: 7-11x slower. That makes sense, because the prompt has to be processed in full before generating the first token, and it&rsquo;s an even more bandwidth-intensive operation.</p>
<h3>Where the Strix Halo wins: large models<span class="hx:absolute hx:-mt-20" id="where-the-strix-halo-wins-large-models"></span>
    <a href="#where-the-strix-halo-wins-large-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now we get to the reason I bought this PC. Models that don&rsquo;t fit in the RTX 5090:</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>Size</th>
          <th>Strix Halo (tok/s)</th>
          <th>Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>gpt-oss:20b</td>
          <td>13.8 GB MXFP4</td>
          <td>48.9</td>
          <td>MoE, faster than expected</td>
      </tr>
      <tr>
          <td>qwen3.5:35b</td>
          <td>23.9 GB</td>
          <td>43.2</td>
          <td>MoE, only ~4B active params</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>51.7 GB</td>
          <td>29.5</td>
          <td>MoE, 50GB+</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81.4 GB Q4_K_M</td>
          <td>19.2</td>
          <td>122B params, MoE</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>59.9 GB</td>
          <td>17.9</td>
          <td>Full precision bf16</td>
      </tr>
      <tr>
          <td>qwen2.5:72b</td>
          <td>47.4 GB Q4_K_M</td>
          <td>4.5</td>
          <td>Dense 72B, bandwidth-limited</td>
      </tr>
  </tbody>
</table>
<p>qwen3.5:122b with 81GB of weights running at 19 tok/s. On a mini-PC. That&rsquo;s simply not possible on an RTX 5090. On the NVIDIA card, that model would have to offload layers to system RAM, dropping to 2-3 tok/s. In practice, unusable.</p>
<p>The difference between MoE and dense models is brutal. qwen3.5:35b runs at 43 tok/s because, despite having 35B total parameters, only ~4B are active per token. A dense 72B model like qwen2.5:72b has to read 40GB+ of weights per token, and at 256 GB/s of bandwidth, the theoretical maximum is ~6.7 tok/s. The 4.5 measured represent ~67% efficiency, which is what you&rsquo;d expect for an iGPU (overhead from shared bus and drivers).</p>
<h3>Summary: when to use which machine<span class="hx:absolute hx:-mt-20" id="summary-when-to-use-which-machine"></span>
    <a href="#summary-when-to-use-which-machine" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Use case</th>
          <th>Best machine</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Interactive chat/coding (models &lt;32GB)</td>
          <td>RTX 5090 (6-7x faster)</td>
      </tr>
      <tr>
          <td>Large models (50GB+)</td>
          <td>Strix Halo (only option)</td>
      </tr>
      <tr>
          <td>Dense 70B+ models</td>
          <td>Strix Halo (only option)</td>
      </tr>
      <tr>
          <td>Full-precision bf16</td>
          <td>Strix Halo (only option)</td>
      </tr>
      <tr>
          <td>Batch processing with long context</td>
          <td>Strix Halo (more VRAM for KV cache)</td>
      </tr>
      <tr>
          <td>API serving with low latency</td>
          <td>RTX 5090 (sub-150ms TTFT)</td>
      </tr>
  </tbody>
</table>
<h3>A ROCm bug that&rsquo;s still around<span class="hx:absolute hx:-mt-20" id="a-rocm-bug-thats-still-around"></span>
    <a href="#a-rocm-bug-thats-still-around" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Not everything works. Models like deepseek-r1:70b, llama3.3:70b and llama4:scout crash with a ggml bug (<code>GGML_ASSERT(ggml_nbytes(src0) &lt;= INT_MAX) failed</code>). The embedding tensor of these models exceeds 2GB and the ROCm copy kernel uses a 32-bit integer for the size. On CUDA (NVIDIA) it&rsquo;s already been fixed, but on ROCm it hasn&rsquo;t. Waiting for the fix in Ollama 0.20.0+.</p>
<h2>LPDDR5X vs GDDR7: why this difference exists<span class="hx:absolute hx:-mt-20" id="lpddr5x-vs-gddr7-why-this-difference-exists"></span>
    <a href="#lpddr5x-vs-gddr7-why-this-difference-exists" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>The next question is: why is LPDDR5X so much slower?</p>
<p>GDDR7 is dedicated GPU memory. It&rsquo;s soldered onto the graphics card, connected by a wide bus (384 or 512 bits on the RTX 5090) at high clocks. Its only job is to feed data to the GPU. LPDDR5X is unified memory that serves everything: the operating system, applications, and the GPU all at once. The bus is narrower and shared.</p>
<p>In practice: GDDR7 delivers ~1,792 GB/s dedicated to the GPU. LPDDR5X delivers ~256 GB/s that still need to be split between CPU and GPU. LLM inference is basically &ldquo;read all the model weights from memory, multiply by the current token, generate the next token, repeat.&rdquo; Whoever reads faster, generates faster. There&rsquo;s no shortcut.</p>
<p>The Strix Halo&rsquo;s advantage isn&rsquo;t speed. It&rsquo;s capacity. 96GB of VRAM in a 100W chip that costs a fraction of a professional GPU. The RTX 5090 is 7x faster, but it&rsquo;s stuck at 32GB. Models that don&rsquo;t fit, don&rsquo;t run.</p>
<h2>The alternatives: who else does this?<span class="hx:absolute hx:-mt-20" id="the-alternatives-who-else-does-this"></span>
    <a href="#the-alternatives-who-else-does-this" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If 96GB isn&rsquo;t enough or you want more speed, the options are limited.</p>
<p>The Framework Desktop uses the same AI Max+ 395 chip with up to 128GB of RAM. Same platform, same performance, but with the differential of being modular and repairable (it&rsquo;s Framework, after all). In practice it&rsquo;s equivalent to the Minisforum on specs and price.</p>
<p>Above that, the alternative is a Mac Studio with M3 Ultra. The M3 Ultra chip supports up to 512GB of unified memory, with ~819 GB/s of bandwidth (more than 3x the Strix Halo). Apple manufactures the memory chips on the package, so latency and bandwidth are superior. You could potentially allocate ~400GB as VRAM and run models that don&rsquo;t fit anywhere outside of professional GPU servers.</p>
<p>Apple&rsquo;s internal NVMe is also another level: ~7.4 GB/s of sequential read on the M3 Ultra, compared with ~14 GB/s on the Crucial T700 (PCIe 5.0). The T700 is faster on raw throughput, but Apple&rsquo;s NVMe latency tends to be lower on random I/O thanks to the SoC integration.</p>
<table>
  <thead>
      <tr>
          <th>Spec</th>
          <th>Minisforum MS-S1 Max</th>
          <th>Mac Studio M3 Ultra (max)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Max RAM</td>
          <td>128 GB LPDDR5X</td>
          <td>512 GB unified</td>
      </tr>
      <tr>
          <td>Allocatable VRAM</td>
          <td>~96 GB</td>
          <td>~400 GB</td>
      </tr>
      <tr>
          <td>Memory bandwidth</td>
          <td>~256 GB/s</td>
          <td>~819 GB/s</td>
      </tr>
      <tr>
          <td>CPU</td>
          <td>Zen 5, 16C/32T</td>
          <td>Apple M3 Ultra, 32C</td>
      </tr>
      <tr>
          <td>GPU compute</td>
          <td>ROCm (gfx1151, experimental)</td>
          <td>Metal (mlx, mature)</td>
      </tr>
      <tr>
          <td>Power draw</td>
          <td>~100W</td>
          <td>~135W</td>
      </tr>
      <tr>
          <td>NVMe</td>
          <td>PCIe 5.0 (standard slot)</td>
          <td>Custom Apple (~7.4 GB/s)</td>
      </tr>
      <tr>
          <td>Price (US)</td>
          <td>~$1,500-2,000</td>
          <td>~$9,999 (512GB config)</td>
      </tr>
      <tr>
          <td>Estimated price (Brazil)</td>
          <td>~R$ 12,000-15,000</td>
          <td>~R$ 110,000+ (imported)</td>
      </tr>
  </tbody>
</table>
<p>The Brazilian price is the elephant in the room. The Mac Studio&rsquo;s max config costs $9,999 in the US. With import taxes (~60% + state ICMS), it goes past R$ 110,000. The Minisforum with 128GB lands at R$ 12,000-15,000. A nearly 8x price gap buys you a lot.</p>
<p>If you need more than 96GB of VRAM for truly enormous models (DeepSeek-V3 with 671B parameters fits in ~400GB Q4, for example), the Mac Studio with 512GB is the only consumer option. The alternative would be professional NVIDIA A6000 GPUs (48GB VRAM, ~$6,000 each, and you&rsquo;d need several in NVLink). For everything that fits in 96GB, the Minisforum gets the job done at a fraction of the cost.</p>
<h2>And projects that promise to run big LLMs on small GPUs?<span class="hx:absolute hx:-mt-20" id="and-projects-that-promise-to-run-big-llms-on-small-gpus"></span>
    <a href="#and-projects-that-promise-to-run-big-llms-on-small-gpus" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>There&rsquo;s a concept called &ldquo;layer offloading&rdquo; that projects like llama.cpp already support. The idea: if the model doesn&rsquo;t fit fully in VRAM, keep some layers on the GPU and the rest in system RAM. The GPU processes the layers it has, hands off to the CPU to process the rest, and back.</p>
<p>In practice, it doesn&rsquo;t work well. The bottleneck is PCIe: transfer speed between system RAM and GPU VRAM is ~32 GB/s (PCIe 5.0 x16). Each generated token needs to transfer data back and forth. The result is that you drop from 150 tok/s (everything in VRAM) to 2-8 tok/s (partial offload). It&rsquo;s too slow for interactive use.</p>
<p>VRAM is the fundamental limitation because LLM inference is memory-bandwidth-bound, not compute-bound. The GPU has compute to spare. What&rsquo;s missing is the ability to read the model weights fast enough. When part of the weights live in system RAM through PCIe, the entire pipeline waits on the transfer.</p>
<p>That&rsquo;s why unified memory (like in the Strix Halo or Apple Silicon) makes a difference. There&rsquo;s no PCIe in the middle. CPU and GPU access the same physical memory. The Strix Halo&rsquo;s 256 GB/s is slow compared to GDDR7, but it&rsquo;s 8x faster than offloading through PCIe.</p>
<h2>Advances in LLM optimization (up to 2026)<span class="hx:absolute hx:-mt-20" id="advances-in-llm-optimization-up-to-2026"></span>
    <a href="#advances-in-llm-optimization-up-to-2026" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>To understand why some models run so much better than others on the Strix Halo, you have to understand what&rsquo;s changed in the ecosystem over the last two years.</p>
<h3>Mixture of Experts (MoE)<span class="hx:absolute hx:-mt-20" id="mixture-of-experts-moe"></span>
    <a href="#mixture-of-experts-moe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If you run local models, MoE is the advance that matters most. An MoE model has high total parameters (e.g. 122B in qwen3.5:122b), but only activates a fraction of them per token (e.g. ~4B). The inactive weights stay in VRAM but aren&rsquo;t read on every token, which drastically reduces the bandwidth needed.</p>
<p>In the Strix Halo benchmarks, MoE models run 3-10x faster than dense models of the same size. qwen3.5:35b (MoE, ~4B active) runs at 43 tok/s while qwen2.5:72b (dense, 72B active) runs at 4.5 tok/s.</p>
<h3>DeepSeek and training optimization<span class="hx:absolute hx:-mt-20" id="deepseek-and-training-optimization"></span>
    <a href="#deepseek-and-training-optimization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>DeepSeek V3 (December 2024) showed that it was possible to train 671B parameter models at a cost an order of magnitude lower than predicted. They combined MoE with FP8 quantization during training (not just inference), multi-stage training with curriculum learning, and several inter-GPU communication optimizations. The impact: everyone copied. Qwen, GLM, MiniMax, all of them adopted variations of the technique.</p>
<h3>Quantization: from FP16 to Q4 without losing much<span class="hx:absolute hx:-mt-20" id="quantization-from-fp16-to-q4-without-losing-much"></span>
    <a href="#quantization-from-fp16-to-q4-without-losing-much" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quantization compresses model weights from 16 bits (FP16) to smaller formats: 8 bits (Q8), 4 bits (Q4), or even 2 bits. A 70B model that would take ~140GB in FP16 fits in ~40GB at Q4_K_M. Quality loss exists, but in modern formats (GGUF Q4_K_M, AWQ, EXL2) it&rsquo;s small enough for practical use.</p>
<p>GGUF (the llama.cpp format) became the standard for local inference. AWQ and GPTQ are alternatives with more sophisticated calibration, but the ecosystem converged on GGUF because it works on CPU, CUDA and ROCm without recompilation.</p>
<h3>Distillation: smaller models that know more<span class="hx:absolute hx:-mt-20" id="distillation-smaller-models-that-know-more"></span>
    <a href="#distillation-smaller-models-that-know-more" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Distillation is training a small model using the responses of a large model as the teacher. Microsoft&rsquo;s Phi-4 (14B) was trained with distillation from GPT-4 and competes with 70B models on several benchmarks. Qwen3 did the same: qwen3:14b is surprisingly capable for its size.</p>
<h3>Flash Attention and optimized KV Cache<span class="hx:absolute hx:-mt-20" id="flash-attention-and-optimized-kv-cache"></span>
    <a href="#flash-attention-and-optimized-kv-cache" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Flash Attention (Tri Dao, 2022) changed how attention is computed: instead of materializing the full attention matrix in memory, it processes in blocks keeping the data in the GPU&rsquo;s on-chip SRAM, reducing memory consumption from O(n²) to O(n). Without that, contexts of 128K+ tokens would be impractical. It already went through versions 2 and 3, with optimizations for FP8 and async operations on H100. PagedAttention (vLLM, UC Berkeley) did the same for the KV cache during serving: applies virtual memory concepts to the cache, eliminating fragmentation and improving throughput by 2-4x.</p>
<p>In Ollama, I set <code>OLLAMA_FLASH_ATTENTION=1</code> and <code>OLLAMA_KV_CACHE_TYPE=q8_0</code> on the server. The first activates flash attention, the second uses 8-bit KV cache instead of fp16, cutting the bandwidth needed per token in half. These are zero-hardware-cost optimizations that improve throughput measurably.</p>
<h3>What Qwen, Kimi, MiniMax and GLM are doing<span class="hx:absolute hx:-mt-20" id="what-qwen-kimi-minimax-and-glm-are-doing"></span>
    <a href="#what-qwen-kimi-minimax-and-glm-are-doing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Qwen (Alibaba) has consistently been the best price/performance in open source models. Qwen3:14b is dense and strong; Qwen3.5:122b is MoE and runs surprisingly well in 96GB. GLM-4.7 (Zhipu AI) is notable for offering bf16 full precision versions that fit in 96GB. MiniMax experimented with long contexts (up to 4M tokens). Kimi (Moonshot AI) focused on large context windows with linear architectures.</p>
<h3>What runs well in 96GB of VRAM<span class="hx:absolute hx:-mt-20" id="what-runs-well-in-96gb-of-vram"></span>
    <a href="#what-runs-well-in-96gb-of-vram" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>With 96GB on the Strix Halo, the models that work well for daily use:</p>
<table>
  <thead>
      <tr>
          <th>Model</th>
          <th>Size</th>
          <th>tok/s</th>
          <th>Use</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>qwen3.5:35b</td>
          <td>24 GB</td>
          <td>43.2</td>
          <td>General purpose, excellent</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>52 GB</td>
          <td>29.5</td>
          <td>Code, MoE</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81 GB</td>
          <td>19.2</td>
          <td>Heavy but usable</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>60 GB</td>
          <td>17.9</td>
          <td>Full precision</td>
      </tr>
      <tr>
          <td>qwen2.5-coder:32b</td>
          <td>20 GB</td>
          <td>10.2</td>
          <td>Code, dense</td>
      </tr>
      <tr>
          <td>deepseek-r1:32b</td>
          <td>20 GB</td>
          <td>7.4</td>
          <td>Reasoning</td>
      </tr>
  </tbody>
</table>
<p>Dense 70B+ models (deepseek-r1:70b, llama3.3:70b) are still blocked by the ROCm bug I mentioned. When it gets fixed, they should run at ~4-6 tok/s, usable for batch but not for interactive chat.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I bought the Minisforum to run models that don&rsquo;t fit in any gaming GPU. For that, it works. It&rsquo;s not fast. 19 tok/s on a 122B model isn&rsquo;t the experience you get with Claude or ChatGPT. But it&rsquo;s local, it&rsquo;s private, and it runs on my shelf consuming less power than an old lightbulb.</p>
<p>For people asking about the Mac Studio: if you have the budget, it&rsquo;s the best machine for running local LLMs. 512GB of unified memory, 819 GB/s of bandwidth, mature Metal/mlx ecosystem. You can run DeepSeek-V3 in full Q4. But in Brazil, with import duties, it crosses R$ 110k. The Minisforum with 128GB at R$ 12-15k is the realistic option.</p>
<p>And for people who think you can work around the VRAM limitation with layer offloading: you can&rsquo;t. PCIe is too slow. The model has to fit fully in VRAM for inference to be usable. It&rsquo;s the reason gaming GPUs with 32GB of ultra-fast GDDR7 are still capped on model size, and why the unified memory of the Strix Halo and Apple Silicon changed the equation.</p>
<p>In the <a href="/en/2026/03/31/migrating-my-home-server-with-claude-code/">next post</a> I tell the story of how I migrated the entire home server to the Minisforum using Claude Code, the problems I ran into, and how openSUSE MicroOS behaves as a Docker server operating system.</p>
]]></content:encoded><category>hardware</category><category>local-models</category><category>performance</category><category>reviews</category></item><item><title>Your Windows May Be Crippled Without You Knowing. Check This!!</title><link>https://akitaonrails.github.io/en/2025/05/21/your-windows-may-be-crippled-without-you-knowing-check-this/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/05/21/your-windows-may-be-crippled-without-you-knowing-check-this/</guid><pubDate>Wed, 21 May 2025 17:15:00 -0300</pubDate><description>&lt;p&gt;I usually don&amp;rsquo;t bother documenting silly Windows problems, but this one in particular got me really annoyed, so I&amp;rsquo;m going to report the sequence of events. But let me just drop the spoiler right now: open Control Panel and Power Options on your Windows and check if it&amp;rsquo;s not in &amp;ldquo;Power saver&amp;rdquo; mode. Get it out of that garbage, at the very least leave it on Balanced, but if it&amp;rsquo;s a desktop PC bump it up to &amp;ldquo;High Performance&amp;rdquo; or &amp;ldquo;Ultimate Performance&amp;rdquo;. Thank me later.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I usually don&rsquo;t bother documenting silly Windows problems, but this one in particular got me really annoyed, so I&rsquo;m going to report the sequence of events. But let me just drop the spoiler right now: open Control Panel and Power Options on your Windows and check if it&rsquo;s not in &ldquo;Power saver&rdquo; mode. Get it out of that garbage, at the very least leave it on Balanced, but if it&rsquo;s a desktop PC bump it up to &ldquo;High Performance&rdquo; or &ldquo;Ultimate Performance&rdquo;. Thank me later.</p>
<p>Now sit down, here comes the story.</p>
<p>I built a pretty decent PC for my girlfriend: Intel i7 12th Gen 8 cores, 32GB DDR4-2400, NVIDIA RTX 3090, MSI Edge z790 motherboard, NVME and I even hooked up a Synology DS1621+ NAS on a 2.5Gbps network - because she&rsquo;s a content creator and has A LOT of video to edit. Modesty aside, it turned out pretty nice:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a8c82k3ocnpu1jamxb9w0z1fx2cf" alt="pc branco"  loading="lazy" /></p>
<p>Everything worked very well for a long time, but eventually, for no apparent reason, everything started feeling kind of slow. Not completely slow, but sometimes web pages seemed to stutter while loading. I&rsquo;d click on the Windows Start menu and it seemed to freeze up. Sometimes Explorer would hang and I&rsquo;d need to go to Task Manager and restart the process manually. Several super inconvenient things but none of them throwing any explicit &ldquo;error&rdquo;. It just felt &ldquo;weird&rdquo;.</p>
<p>I&rsquo;m a Linux user. My main PC is Manjaro Linux. I only use Windows on my gaming mini-PCs, exclusively to run Steam and some emulators. I avoid installing anything on them at all costs. Because I know how much of a pain it is to diagnose Windows after some weird program invades it.</p>
<p>I went after the usual suspects. She had installed games for streaming, like Valorant, Genshin Impact and stuff like that. I always suspect anti-cheats, the crap from Riot Vanguard or Easy or VAC. I started by uninstalling all the games. But I always think anti-cheat is like malware: once installed, it will never leave the system. I uninstalled everything, but that feeling of kind of freezing, kind of slow, intermittent, remained.</p>
<p>I tried the next suspects: peripheral software, like Elgato, Logitech, Razer stuff that install a bunch of garbage on your machines, and recently I was reading about the malware that was shipping with all of them. Watch this GamersNexus video to understand the house of cards that the whole peripheral industry with RGB has become:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/H_O5JtBqODA"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Even uninstalling all of them, still the same behavior: kind of slow, kind of freezing, unstable.</p>
<p>Let&rsquo;s try to go deeper, and for that you have to install tools like <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/autoruns"target="_blank" rel="noopener">Sysinternals Autoruns</a> to see if there&rsquo;s anything starting up (things hidden in the Registry for example). I tried to disable everything I thought was suspicious.</p>
<p>Nothing. By that point I had also made sure that the newest MSI BIOS firmware was installed. I checked that nothing had been turned off in the BIOS, XMP profile was ok, no fast-boot and stuff like that.</p>
<h3>Is It the Internet?<span class="hx:absolute hx:-mt-20" id="is-it-the-internet"></span>
    <a href="#is-it-the-internet" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Since most of the usage is internet and it seemed &ldquo;slow&rdquo;, I thought it might be network problems. Vivo&rsquo;s router? The plan? Wifi with interference?</p>
<p>Running out of patience I decided to go nuclear on the internet idea. In fact, opening the <a href="https://speed.cloudflare.com/"target="_blank" rel="noopener">Cloudflare Speed Test</a> page I could see that the speed itself was ok, but the latency was VERY high, jitter high and, mainly, PACKET LOSS was over 15%. That&rsquo;s not normal, it has to be 0%.</p>
<p>Taking advantage of a trip to Miami I brought back a TP-LINK BE9300 router for Wi-fi 7, and I also bought a compatible Wi-fi 7 PCIe card. I turned off Vivo&rsquo;s Wi-fi router and bumped the plan up to 800Mbps. And indeed, the internet improved a lot:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ogesvmtex2ek825hel7d4l0f5i30" alt="vivo internet"  loading="lazy" /></p>
<p>Latency dropped, jitter dropped, packet loss went to 0%.</p>
<p>But this didn&rsquo;t fix the problem. Out of nowhere the browser would pause when loading some page. That feeling of kind of slow, kind of freezing. And yes, I tried switching from Edge to Chrome, to Firefox, to everything. Nothing made any difference. The internet really did get faster, but Windows itself didn&rsquo;t seem to improve much and while browsing, it was getting slower and slower. So the problem wasn&rsquo;t the internet.</p>
<p>I was running out of options. Everything that could be manually cleaned in Windows, I cleaned. There was almost nothing left to delete - and I delete things without mercy. But I always had it in my head that it was some malware, something that loads before boot, some &ldquo;ROOTKIT&rdquo;. Anti-cheats are Rootkits by definition. They load even before the kernel and from that point on, the compromised system can no longer be trusted, because no tool will be able to find it.</p>
<p>Assuming at that point it had to be a compromised system, I decided to go with the nuclear option: reinstall everything from scratch. We backed up the data and off I went to reinstall from scratch.</p>
<h3>Reinstalling DID NOT WORK<span class="hx:absolute hx:-mt-20" id="reinstalling-did-not-work"></span>
    <a href="#reinstalling-did-not-work" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now it got worse.</p>
<p>With Windows 11 Pro, freshly installed, guaranteed with no third-party software installed, it still had the same behavior of kind of slow, kind of freezing. The start menu wouldn&rsquo;t let me click on anything. I had to open task manager and kill Explorer manually to get it to work again.</p>
<p>I spent some time staring at Task Manager, but there wasn&rsquo;t any process running that I could suspect further: it was bare-bones Windows. I had even run a <a href="https://github.com/Raphire/Win11Debloat"target="_blank" rel="noopener">Debloater</a> to uninstall unnecessary Microsoft apps like Teams.</p>
<p>But lo and behold, from staring at task manager so much, I saw that I had been ignoring an important piece of data. See if you can spot it:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/pky39kkwcbuyqocwpj15n36i9gai" alt="cpu low"  loading="lazy" /></p>
<p>The image is kind of messed up because I took a photo of the monitor with my phone. The refresh rate clashes with the camera&rsquo;s and you get this &ldquo;grid&rdquo;, but I think you can still read it.</p>
<p>Even when you have almost no program running, it&rsquo;s not natural for the CPU to be at such a low clock, 0.50Ghz - HALF A GIGAHERTZ. This Intel i7 can go above 4Ghz and the base clock is 2Ghz.</p>
<p>I opened browsers, opened tabs, kept monitoring, and it never went above 1.5Ghz. An occasional spike to 2Ghz for 1 second and then it dropped below 1Ghz again. Then I thought: could the AIO be failing??</p>
<p>Looking at Event Viewer you could see when the Start menu (which is part of Explorer.exe) stopped working and froze: it logs it as &ldquo;Hanging Events&rdquo; code 1002.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dtugk013p4decud6zo1dzg3rjhxr" alt="event viewer"  loading="lazy" /></p>
<p>See all those Errors? All Hanging Events, and &ldquo;hanging&rdquo; is exactly the behavior: something seems to &ldquo;hold&rdquo;, &ldquo;freeze&rdquo; and only by killing the process does it come back to working.</p>
<p>And this was happening before and also AFTER reinstalling everything from scratch. Something seemed fundamentally wrong with the hardware itself. With the super low clock, this behavior starts to make sense: it&rsquo;s so slow it can&rsquo;t even process basic things. Click several times on the Start Menu (which will be digging through recent documents, updating the list of applications, opening the search bar, etc) and there&rsquo;s no clock to process all that while there&rsquo;s still Windows Defender in background, browser tabs processing.</p>
<h3>Could It Be Thermal Throttle??<span class="hx:absolute hx:-mt-20" id="could-it-be-thermal-throttle"></span>
    <a href="#could-it-be-thermal-throttle" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Since Windows was wiped clean, it can&rsquo;t be anti-cheat anymore, it can&rsquo;t be some weird anti-virus, it can&rsquo;t be malware, it&rsquo;s not the internet, it had to be something EXTERNAL, that was what made sense.</p>
<p>I opened the PC and left it open so I could feel if the heatsink was getting absurdly hot. If it was, it would mean my AIO (Coolermaster Watercooler) had started to fail.</p>
<p>It&rsquo;s normal for the CPU, when it overheats, to drop the clock to avoid burning the chip. This is called &ldquo;THERMAL THROTTLING&rdquo;.</p>
<p>And again, weird: it was COLD to the touch!!!</p>
<p>No way. I opened the BIOS to re-check fan profiles, but everything was normal:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yl02sk0dug6zj9d1gen877d22gnw" alt="cpu fans"  loading="lazy" /></p>
<p>That&rsquo;s when it finally hit me:</p>
<pre><code>**LET ME CHECK THE POWER SETTINGS**
</code></pre>
<p>On a normal PC/laptop, it should show something like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a5dkm6eag4ebma6ye1i1mnzphy3s" alt="Power Settings"  loading="lazy" /></p>
<p>By default, Windows always boots in &ldquo;Balanced Mode&rdquo;.</p>
<p>But on my girlfriend&rsquo;s PC, <strong>I have no idea why</strong>, there was no Power Mode option!!! And I&rsquo;m on a Windows 11 Pro with a paid license!!</p>
<p>That&rsquo;s weird, but I know that in the old Control Panel, there&rsquo;s a Power Options entry:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/70ymku67f5ufp0pd76jp0b2hqjx2" alt="control panel"  loading="lazy" /></p>
<p>AND THERE&rsquo;S THE PROBLEM:</p>
<p>It was on &ldquo;Power saver&rdquo;!!!</p>
<p>How did that happen? I have absolutely no idea. REMEMBER: I had reinstalled Windows from scratch. This is right after reinstalling!!</p>
<p>On a laptop this profile makes sense, to save battery when you don&rsquo;t have an outlet. But on a desktop PC, which is my case, it makes no sense at all!! I switched to &ldquo;Ultimate Performance&rdquo; and look at the task manager now:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5hg5d85ivuxv9tw5si21wcb3hm5p" alt="cpu high"  loading="lazy" /></p>
<p>NOW WE&rsquo;RE TALKING!! Above 4Ghz!!!</p>
<p>The NORMAL range for this CPU is around 4Ghz, with Turbo Boost of 5Ghz. But in Power saver mode, it NEVER goes above 2Ghz and idles at 500Mhz. In other words, everything was <strong>TEN TIMES SLOWER</strong>, sometimes it tried to boost and was still <strong>FIVE TO TWO TIMES SLOWER</strong>. And it&rsquo;s intermittent!!!</p>
<p>Testing installing apps, browsing, etc, everything is now working normally, no freezing, no feeling of slowness. Start menu doesn&rsquo;t hang anymore - because now there&rsquo;s enough CPU to process the crap it needs in background.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>CHECK POWER OPTIONS IN CONTROL PANEL!!!!</p>
<p>Again: I had JUST REINSTALLED WINDOWS. It was wiped clean and, by default, it came up in Power Saver mode.</p>
<p>I have no idea why it did that, I don&rsquo;t know if it always does that, I don&rsquo;t know if it failed to recognize the MSI motherboard, I don&rsquo;t know what combination of hardware factors can make Windows fall back to this.</p>
<p>But it&rsquo;s ALARMING that it does this without warning. There&rsquo;s no error message. There&rsquo;s no notification. It doesn&rsquo;t show up in Event Viewer. In other words, even a Power User like myself has to randomly suspect &ldquo;hmm, let me check the power profile&rdquo;.</p>
<p>Lots of people with Windows must be feeling this slowness and freezing and thinking &ldquo;hmm, my PC is just crap, I need to save up to buy another one&rdquo;, without knowing that maybe it&rsquo;s just crippled by this power profile and in reality their PC is 2x to 5x, even 10x faster and they don&rsquo;t know it! That&rsquo;s not a small difference!</p>
<p>POWER SAVER MODE is a huge piece of garbage, it has no practical use and it shouldn&rsquo;t even exist. It&rsquo;s one of those legacy things that give Windows a terrible experience - because it doesn&rsquo;t warn you.</p>
<p>Remember those 1002 hanging events error codes? If you go searching on Google, every forum will tell you to run diagnostic programs, uninstall stuff, or reinstall Windows. None of them mention anything about the power profile.</p>
<p>I hope this helps someone else!</p>
]]></content:encoded><category>microsoft</category><category>performance</category><category>hardware</category></item><item><title>Final Attempt to Train an LLM with LoRA. Cannon Shot, But Missing the Fly.</title><link>https://akitaonrails.github.io/en/2025/05/03/final-attempt-to-train-an-llm-with-lora-cannon-shot-missing-the-fly/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/05/03/final-attempt-to-train-an-llm-with-lora-cannon-shot-missing-the-fly/</guid><pubDate>Sat, 03 May 2025 21:15:00 -0300</pubDate><description>&lt;p&gt;Ok, I&amp;rsquo;m stubborn. In the &lt;a href="https://www.akitaonrails.com/en/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"target="_blank" rel="noopener"&gt;previous post&lt;/a&gt; I explained everything I knew about trying to fine-tune a model with LoRA. But it&amp;rsquo;s a tedious, slow process, limited by the &amp;ldquo;poverty&amp;rdquo; of my RTX 4090. In 24GB of VRAM you have to fit the model, things like the KV cache, the training dataset and room to process it all. Squeezing hard, the Qwen3-8B model and 1MB of dataset fit and that&amp;rsquo;s it. It&amp;rsquo;ll use 21GB constantly for more than an hour and the end result, even though the model manages to answer &amp;ldquo;yes, I know Zig 0.14&amp;rdquo;, isn&amp;rsquo;t good — because the 8B base model wasn&amp;rsquo;t great to begin with.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ok, I&rsquo;m stubborn. In the <a href="https://www.akitaonrails.com/en/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"target="_blank" rel="noopener">previous post</a> I explained everything I knew about trying to fine-tune a model with LoRA. But it&rsquo;s a tedious, slow process, limited by the &ldquo;poverty&rdquo; of my RTX 4090. In 24GB of VRAM you have to fit the model, things like the KV cache, the training dataset and room to process it all. Squeezing hard, the Qwen3-8B model and 1MB of dataset fit and that&rsquo;s it. It&rsquo;ll use 21GB constantly for more than an hour and the end result, even though the model manages to answer &ldquo;yes, I know Zig 0.14&rdquo;, isn&rsquo;t good — because the 8B base model wasn&rsquo;t great to begin with.</p>
<p>The way out: migrate my training to a bigger machine. I started by trying an A40, to see if 40GB was enough to fit the Qwen3-32B model for this training. And no, it didn&rsquo;t fit.</p>
<p>&ldquo;Ph0da-se&rdquo;, I thought. I rented the biggest configuration RunPod has:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/9m5uitxvhhbpotu5lkbrw0znd276" alt="H100"  loading="lazy" /></p>
<p>This H100 is already &ldquo;obsolete&rdquo;, in the more modern datacenters there are already H200 Blackwells and other bigger things. But in the world of &ldquo;mortals&rdquo;, an H100 at USD 3 per HOUR will have to do. The good part: it has 80GB of VRAM. And what I wasn&rsquo;t expecting: even that is too little!</p>
<p>And here&rsquo;s the advantage of having done everything organized and pushed to this <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">GitHub repo</a>. Setup was trivial: just pick a pod with a PyTorch 2.8 image with Python 3.10 and CUDA 12.8, which are the latest stable versions. Clone my project, run <code>pip install -r requirements.txt</code> and I could already run my <code>./train-lora.py</code>.</p>
<p>I was going to add back those 15MB of source code from the newer Zig Standard Library, but even on the H100, it didn&rsquo;t fit! Training with just the original 1MB I used yesterday already pushed the machine almost to its maximum:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/iahqr9hzs2aaku3fup7u3cw9navk" alt="74GB"  loading="lazy" /></p>
<p>Look how absurd this is: during the entire training it sat at nearly 75GB out of the 80GB of VRAM. Literally there&rsquo;s no room to train with much more material than that. Either that, or you drop down to a smaller model, like Qwen3-14B. I still don&rsquo;t know whether 32B with less material or 14B with more material would be the better combination. I already know 8B is useless for programming anything complicated. With 14B I had a bit more success in tests, but it still can&rsquo;t do anything very complex. 32B is no guarantee of being much better: Qwen2.5-32B didn&rsquo;t perform better than the 14B, but the new Qwen3-32B &ldquo;looks like&rdquo; it&rsquo;s more stable. That&rsquo;s what I&rsquo;m trying to test with the LoRA.</p>
<p>This was at lunch time. I&rsquo;m writing this post at dinner time. This was the training forecast:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lzrfdl97n96qesnhde33lugidr3i" alt="6h"  loading="lazy" /></p>
<p>This estimate keeps fluctuating, but I think it did take around 5 to 6 hours.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/stoakih27g3o9c1e1d07y14szrf0" alt="lora size"  loading="lazy" /></p>
<p>And at the end of those almost 6 hours, this is what it produced: a 5.1GB LoRA (against yesterday&rsquo;s 3GB). Size isn&rsquo;t everything. Bigger doesn&rsquo;t mean better. Let&rsquo;s see.</p>
<h3>After Training<span class="hx:absolute hx:-mt-20" id="after-training"></span>
    <a href="#after-training" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now that training is done, I can use the same H100 pod to also serve the model with the LoRA, and it would have to be this one anyway. To run it, it goes like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">vllm serve Qwen/Qwen3-32B <span class="se">\
</span></span></span><span class="line"><span class="cl">    --enable-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --lora-modules <span class="nv">ziglora</span><span class="o">=</span>./qwen3-zig-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --max-model-len <span class="m">10000</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And you also have to limit the context size. By default vLLM tries to start with a 40k token context and, even on the H100 with 80GB, it fails initialization with this error:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ykusgib7o6wybhi5rs4zvx2yrp8j" alt="KV Cache error"  loading="lazy" /></p>
<p>There&rsquo;s no room left to load the KV Cache (in short, in the process of generating the next word, it keeps adding a new row and a new column to the matrix, but doesn&rsquo;t need to recompute the previous rows/columns, the result is the same. Hence this cache).</p>
<p>A strange behavior both on my machine and on the H100. I don&rsquo;t know if this happens every time you load a LoRA, or if my LoRA is broken, or if there&rsquo;s some configuration I&rsquo;m missing. But loading with a LoRA, initialization is EXTREMELY SLOW. Bringing up vLLM, which normally takes 1 minute, now takes 5 minutes or more. I don&rsquo;t know what causes this, but this is the kind of server you can&rsquo;t easily shut down and restart. It makes me miss starting up a J2EE server &hellip;</p>
<p>Seriously though, it seems I&rsquo;m not the only one. This <a href="https://github.com/vllm-project/vllm/issues/6876"target="_blank" rel="noopener">issue</a> never got resolved. It &ldquo;could&rdquo; be some bug in vLLM, it &ldquo;could&rdquo; be a combination of factors nobody knows. But neither the GPU nor the CPU is doing anything. CPU doesn&rsquo;t go above 10% usage, GPU looks like it&rsquo;s just waiting. Honestly, at first glance it looks like some I/O problem. Maybe the process of checking the checkpoint shards, pulling from a network volume, is slow. But since I&rsquo;m going to run this only today, I didn&rsquo;t bother to debug it.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mluc626od3h1q0swnv8kepe1aji5" alt="slow load"  loading="lazy" /></p>
<p>Look at this. 8 MINUTES on the part that loads checkpoints. And then it still continues with other phases that also take a bit more time. I&rsquo;ll leave this screenshot here, in case someone has seen it and knows what it could be.</p>
<p>Just to rule it out, I tried bringing up vLLM with just Qwen3 and removing the LoRA entirely and, to my surprise:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2hzvcqrk087i5ov4otx0rxa8erdf" alt="sem lora"  loading="lazy" /></p>
<p>It also took almost 8 minutes. So the problem is not the LoRA. Either it&rsquo;s something in vLLM that can be configured — but I don&rsquo;t know how — or it&rsquo;s a limitation of the Qwen3-32B model itself being this slow to load.</p>
<p>Anyway, with the LoRA, I tried dropping to 16k tokens: it doesn&rsquo;t come up. I tried starting with 10k tokens. And at this point I&rsquo;d say this test has already failed. A Qwen3-32 theoretically has the capacity to support context windows up to 40k tokens. It&rsquo;s not huge, but you can work a bit inside that context.</p>
<p>But below 15k tokens you can no longer use it with tools like Aider, which needs to send long system prompts to instruct the LLM on how to behave so it spits out code the tool can capture. So there it is: a Qwen3-32b + LoRA, on an H100 SXM with 80GB of VRAM, is not really usable.</p>
<p>And I&rsquo;d even be sad or frustrated if a 32B model were really, orders of magnitude better than a 14B, but in my empirical tests: it isn&rsquo;t. It makes just as many errors, and it isn&rsquo;t so much better that it justifies paying that much for your own top-of-the-line infra. All of them barely manage to produce simple code, and even then with errors.</p>
<p>In the end, with 10k the server came up, consuming an absurd 63GB at initialization, without even starting to process anything:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uetjwlzlgy7bpd4itn1qxczl3mhs" alt="63GB"  loading="lazy" /></p>
<p>And &hellip;</p>
<h3>First Tests<span class="hx:absolute hx:-mt-20" id="first-tests"></span>
    <a href="#first-tests" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>After 10 minutes of waiting, I had to hit Ctrl+C and start all over. Because I was dumb. RunPod is really meant to run experimental stuff, I&rsquo;m not sure I&rsquo;d trust it for production. It&rsquo;s more like a VPS than an AWS. And I&rsquo;m hacking it together by bringing up a pod that expects to have only port 8888 mapped to run a Jupyter Notebook server (which I&rsquo;m not running).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/h4pzcdxydfiou5iulc7m67zctb9a" alt="runpod connect"  loading="lazy" /></p>
<p>My idea is to use this port to map vLLM. But by default it starts on port 8000. I forgot to add <code>--port 8888</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>vllm serve Qwen/Qwen3-32b --enable-lora --lora-modules ziglora=./qwen3-zig-lora --max_model_len 10000 --port 8888</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And here we go again, wait another 10 minutes &hellip; &hellip; and finally it came up on the right port and now we can test. First, the curl tests:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p8d3ogzwd7lzpu05o7lkm50f8k4h" alt="curl hello"  loading="lazy" /></p>
<p>And before anyone comments: there&rsquo;s no problem showing my pod&rsquo;s URL in the screenshot. I&rsquo;m going to delete this pod before publishing this post.</p>
<p>By the way, if you bring up a remote model like I did, all you have to do is take the URL and put it in the <code>OPENAI_API_BASE</code> variable and anything in <code>OPENAI_API_KEY</code> and tools like Aider will work the same. The vLLM and Ollama APIs are compatible with OpenAI&rsquo;s.</p>
<p>As you can see, the first question answered similarly to my local test with Qwen3-8B. Nothing special, I already knew that worked. Let&rsquo;s ask it to explain a new feature of Zig 0.14.</p>
<p>By the way, besides being slow to start, it&rsquo;s also slower at generating text, MUCH slower. I must have done something wrong in training, but I felt the same thing with the local LoRA or on the H100. It gets noticeably slower. It did another long &ldquo;think&rdquo;, but the answer this time is more consistent with the release notes (I only cut out the relevant excerpt):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>In Zig 0.14.0, the `@splat` function has been extended to support arrays, making it easier to initialize fixed-length arrays with a single repeated value. This replaces more verbose or unsafe initialization patterns and is type-safe, with the array&#39;s length inferred from the destination type. Here&#39;s a concise explanation and code example:


...

### **@splat for Arrays**  
`@splat(value: T) [N]T` creates an array of length `N` where every element is `value`. The array&#39;s length and element type are determined by the context in which `@splat` is used.  

### **Code Example**  

const Point = struct { x: i32, y: i32 };  

pub fn main() void {  
    // Initialize an array of 5 integers with 42  
    const numbers: [5]i32 = @splat(42);  

    // Initialize an array of 10 booleans with true  
    const flags: [10]bool = @splat(true);  

    // Initialize an array of 7 points with (0, 0)  
    const origin_points: [7]Point = @splat(Point{ .x = 0, .y = 0 });  
}  
....</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The example code also makes a bit more sense. Compared to the 8B version, at least the explanation was better. But this might have been affected by a small tweak I made to the dataset: I duplicated the release notes, to try to put a bit more emphasis on the new features. That probably had an effect too.</p>
<p>Now let&rsquo;s see if it can write code that actually works. My prompt was this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;Can you give me a Zig 0.14 code example of a simple program that acts like an interactive chat, so the user can input messages. then the message is parsed to see if there are commands such as &#39;/list&#39;, which will list the files in the current directory, or &#39;/cat&#39; which will read the file asked, or &#39;/quit&#39; to exit the chat?&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And this happened:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4n3bzm5g0gt46jfmqv55qy0x9tlu" alt="abort"  loading="lazy" /></p>
<p>Remember how I had to keep decreasing the context to get it to come up? And remember how I keep saying that &ldquo;thinking&rdquo; wastes a lot of tokens? Well, there you go. The model self-crashed by thinking too much. I tried a few times and it crashes. To try to avoid this, I tried hitting <code>curl</code> sending <code>enable_thinking=false</code> like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl https://d8zck5aw8eimij-8888.proxy.runpod.net/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;, &#34;enable_thinking&#34;: &#34;false&#34;, &#34;skip_special_tokens&#34;: true,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;...&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">      }&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Temperature, for code, I think can&rsquo;t really be much higher than 0.1. For chit-chat, for less exact things, the default of 0.6 that everyone uses is fine. But for code, I really don&rsquo;t need it to be &ldquo;creative&rdquo; (random, in fact), I want answers as exact as possible.</p>
<p>Anyway, even trying to turn off thinking, something is wrong. Possibly the LoRA I added is making it &ldquo;think in circles&rdquo; and never being able to return, blowing up the context. I tried several times, but it&rsquo;s unable to answer with code.</p>
<h3>Final: I Give Up For Now<span class="hx:absolute hx:-mt-20" id="final-i-give-up-for-now"></span>
    <a href="#final-i-give-up-for-now" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Yes, I broke the LLM, apparently lol 😅</p>
<p>I tried several times and it really can&rsquo;t give me any code back. And I know it&rsquo;s trying, because if I send <code>stream=true</code>, I can see it returning tokens, but at some point it runs out of space somewhere (surely the context, which is small) and it crashes.</p>
<p>The feeling is that my LoRA probably &ldquo;stuck&rdquo; too hard and threw too many parameters out of balance. Now the model starts writing the text and enters circular thinking, never stopping generating more text.</p>
<p>This is definitely an effect of the LoRA and of some mistake in training. If I had to guess, it&rsquo;s because I crammed super long texts into training, without manual markers indicating where each part ends with a <code>&lt;|im_end|&gt;</code> or whatever. Without that, maybe it thinks the whole text is one answer and it has to respond that long. From what I understand, in the case of this Zig training, I would have had to break the documentation, the release notes and everything else into smaller chunks, like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;|im_start|&gt;system
You are a helpful assistant.&lt;|im_end|&gt;
&lt;|im_start|&gt;user
What is Zig?&lt;|im_end|&gt;
&lt;|im_start|&gt;assistant
Zig is a compiled programming language...&lt;|im_end|&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And do that for all 200 thousand characters of documentation. Imagine the work to do that. And avoid sending super long texts in context without markers for where each part starts and ends.</p>
<p>At least, that&rsquo;s my guess. The problem: even if I spend time formatting it this way, then it&rsquo;s another 6 hours of processing, just to find out afterwards whether it made any difference. It probably won&rsquo;t, I need to think of other hypotheses, and each round is 6 to 12 hours between setup and processing.</p>
<p>From this point on it&rsquo;s a slog I don&rsquo;t have the motivation to do. I&rsquo;m leaving this written down here to see if it reaches the hands of real researchers. Maybe it&rsquo;s something an AI researcher would find super obvious. But that&rsquo;s the point: this information isn&rsquo;t easily available publicly anywhere. Every example you find online is too simple. They just say <em>&ldquo;on this line of python you load the dataset, and on the next line training starts&rdquo;</em> — but where are the instructions for HOW to build a dataset and HOW to determine whether it&rsquo;s adequate? Nowhere.</p>
<p>If anyone has tips on how to improve the training or wants a research problem in the field, <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">here is the GitHub</a> with all the code and training material I produced. We need researchers to look at this kind of thing and share more about what they know. I know someone knows, but just digging around on Google didn&rsquo;t get me anywhere.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>performance</category></item><item><title>Accessing Your NAS Using iSCSI Instead of SMB</title><link>https://akitaonrails.github.io/en/2025/04/24/accessing-your-nas-using-iscsi-instead-of-smb/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/24/accessing-your-nas-using-iscsi-instead-of-smb/</guid><pubDate>Thu, 24 Apr 2025 01:00:00 -0300</pubDate><description>&lt;p&gt;If you have a NAS or any kind of file server on your network, odds are your default move is to create a shared folder and mount it on your operating system, whether Windows, Mac, or Linux, using the old, tired SMB (Samba) protocol.&lt;/p&gt;
&lt;p&gt;On Linux, this is a massive pain in the ass, because the SMB protocol (built for Windows) doesn&amp;rsquo;t properly carry concepts like ownership (chown) or permissions (chmod). It doesn&amp;rsquo;t understand things like execute permission (everything is executable). For simple stuff like a Downloads, Videos, or Photos directory, it kind of doesn&amp;rsquo;t matter.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you have a NAS or any kind of file server on your network, odds are your default move is to create a shared folder and mount it on your operating system, whether Windows, Mac, or Linux, using the old, tired SMB (Samba) protocol.</p>
<p>On Linux, this is a massive pain in the ass, because the SMB protocol (built for Windows) doesn&rsquo;t properly carry concepts like ownership (chown) or permissions (chmod). It doesn&rsquo;t understand things like execute permission (everything is executable). For simple stuff like a Downloads, Videos, or Photos directory, it kind of doesn&rsquo;t matter.</p>
<p>But let&rsquo;s say you want to edit source code in projects with Git. It&rsquo;s going to be a nightmare, because SMB overrides the permissions regardless of what&rsquo;s underneath, and every single time it&rsquo;s going to conflict with Git, which will think files were modified (changing permissions counts as a change it tracks), and it&rsquo;ll keep nagging you to commit that, and that will pollute your whole repository (never use Git projects on SMB over the network).</p>
<p>Instead, I&rsquo;m on Linux, my NAS is Linux. The right call is to use a Linux protocol. That protocol is NFS and I showed how I set it up in <a href="https://www.akitaonrails.com/en/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux"target="_blank" rel="noopener">last week&rsquo;s post</a>.</p>
<p>Anyway, I&rsquo;m using NFS and I have way fewer headaches around file permissions. But then I hit another problem: if you saw my recent posts, you saw I&rsquo;ve been experimenting A LOT with Docker. Spinning up new images like there&rsquo;s no tomorrow. My local 2 TB NVMe filled up fast and started complaining about lack of space. So I thought, <em>&ldquo;Ah, I&rsquo;ll just move the Docker storage directory, /var/lib/docker, to a directory mounted via NFS on my NAS&rdquo;</em></p>
<p>Doing that is very easy:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /mnt/nfs/docker
sudo chown root:docker /mnt/nfs/docker
sudo chmod 771       /mnt/nfs/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That creates a new directory inside my NAS and applies the correct permissions.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl stop docker
sudo mv /var/lib/docker /mnt/nfs/docker
# sudo rm -Rf /var/lib/docker (this will free everything up for me)
sudo ln -s /mnt/nfs/docker /var/lib/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That will free up all the occupied space. I don&rsquo;t need to worry about the images because I can rebuild everything from my Dockerfiles. And I don&rsquo;t need to worry about volumes because I had nothing important that wasn&rsquo;t mounted externally, in real directories. Rule: never store important things in Docker volumes. It&rsquo;s bad practice!</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># edit /etc/docker/daemon.json
{
  &#34;data-root&#34;: &#34;/mnt/nfs/docker&#34;
}

sudo systemctl start docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Done, this changes the configuration from <code>/var/lib/docker</code> to the new <code>/mnt/nfs/docker</code> and after restarting the service, it&rsquo;ll start writing everything there. So the post is over, right?</p>
<p>This is as far as most people go. But if you work with this setup for 2 minutes, you&rsquo;ll feel something is VERY wrong.</p>
<p>I tried building a new image. And it was taking ABSURDLY long. It turns out NFS is indeed faster than SMB, but both were built for file servers, not for operating systems. That makes a huge difference.</p>
<p>These protocols are <strong>FILE-BASED</strong>, every operation is based on files. And that&rsquo;s extremely inefficient.</p>
<p>If you watched my videos on file systems, you should already know they are <strong>BLOCK-LEVEL</strong>. At the filesystem level, you work with fixed-size blocks of bytes, organized in some variation of B-TREE structures (depending on the filesystem). Operations happen at the block level. Files are abstractions over collections of blocks.</p>
<p>When you run a <code>chmod -R</code>, which is a recursive permission change, inside a Dockerfile build, for example, even an empty tree will cause recursion on every entry inside, be it hidden files, ACLs, etc. And it needs to look up every single permission of everything in there. That costs network time (because NFS/SMB are network protocols).</p>
<p>That causes latency. Every permission on every file is an individual remote call, an RPC call. Even on the fastest network, like my 10Gbps one, even if each RPC is under 0.1 ms, it quickly becomes a bottleneck. And that&rsquo;s exactly what started happening.</p>
<p>I used <code>sudo nfsiostat 1 10</code> to check. It was showing this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>write: ops/s=2417  kB/s≈295 MiB/s  avg exe=106 ms  avg queue=102 ms</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>An average of 106 ms per RPC. That&rsquo;s an eternity. Average wait time in the queue of 102 ms. Even if I make just 100 RPC calls, it will cost more than 10 seconds in latency alone. And in an OS build, installing packages with thousands of files, that will literally cost an eternity. Running Docker build on NFS/SMB is simply not viable.</p>
<h3>iSCSI<span class="hx:absolute hx:-mt-20" id="iscsi"></span>
    <a href="#iscsi" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Obviously I&rsquo;m not the first to run into this problem and make the mistake of using a file-based protocol for services with high-volume file churn like the Docker cache.</p>
<p>The right move is to create an iSCSI virtual drive on my NAS. If you&rsquo;ve never heard of it, SCSI is an advanced protocol for drives. Cheap PCs in the 90s and 2000s used IDE/ATA, which is a simpler and cheaper protocol. But the first Macs and UNIX workstations from the 80s and 90s always used SCSI, which was vastly superior.</p>
<p>It was <strong>asynchronous</strong>, with queue control. You could send dozens of commands at once and the protocol was smart enough to reorder those commands for better efficiency (for example, skipping a file read if a delete command came before it). IDE was blocking, one command at a time. A single SCSI BUS could handle up to 16 devices. Parallel ATA was 2 per cable. Ridiculous. SCSI had advanced error recovery and reporting systems. It had &ldquo;enterprise&rdquo; features for hot-swap, power management, timeout behaviors, recovery, clustering, etc. And on top of all that it was agnostic to the transport medium, it could be fiber optic, SAS, or even iSCSI, which is SCSI over Ethernet, which is what we&rsquo;re going to use.</p>
<p>IDE/ATA had none of that. It was the protocol for poor-person PC disks. Real workstations, SUN, IRIX, Silicon Graphics, Macs, used SCSI.</p>
<p>More importantly, iSCSI is a block protocol, not a file protocol. Anyway, this varies by NAS manager, but in the case of Synology DSM, which is what I use, it has an application called SAN Manager, which lets you create an iSCSI drive:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fraxoryhuszmp4cmr0ldc8h7hiip" alt="iSCSI create"  loading="lazy" /></p>
<p>Just follow the wizard, give it a name, choose the size, enable CHAP if you need extra security (I don&rsquo;t, I&rsquo;m on a controlled local network). That creates the iSCSI drive (think of it like a remote USB drive) and a LUN (Logical Unit Number). A LUN is simply one of many potential &ldquo;virtual disks&rdquo; that a target can present. A LUN is like a numbered slot in a target&rsquo;s storage array. A target is a disk server. On a SAN you can have several virtual disks and the way to organize that is with LUNs. In our case there&rsquo;s no real need to go deep on LUN details.</p>
<p>It takes a while, but when it finishes you just go back to my Manjaro Linux and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S open-iscsi
sudo systemctl enable --now iscsi </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>That brings up the service to start automatically on every boot. Now to discover my drive on the network:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code></code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m discovery -t sendtargets -p 192.168.0.xx
192.168.0.xx:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[2804:1b3:....:fe18:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[fe80::9209:....:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Every virtual drive has an <strong>IQN</strong> which is like a &ldquo;URL&rdquo; for the drive. Now we log in:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node --login
Login to [iface: default, target: iqn.2000-01.com.synology:TERACHAD.docker.7af6e9116c1, portal: 192.168.0.21,3260] successful.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since I didn&rsquo;t enable CHAP it doesn&rsquo;t ask for a password or anything (in a company it should be enabled, obviously). And done. When you do this I hear on GNOME the same little sound as when you plug a pen drive into the PC and it automatically shows up as a disk. If you run <code>lsblk</code>, it&rsquo;ll show up as a normal <code>/dev/sdX</code> drive. In the GNOME Disks app (or in Windows Disk Management) it really does show up as if it were any other hard drive or SSD. It doesn&rsquo;t behave like a shared folder, it behaves like a real drive. You even have to format it, just like a new pen drive.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/nvgrl3bov311vl4h251dcj1wvchz" alt="Disks"  loading="lazy" /></p>
<p>The formatting itself takes quite a while, because in the process it has to check and map block by block. Since it&rsquo;s over the network, it&rsquo;s slower than on a real drive, obviously. But it&rsquo;s a one-time thing. Even on my 10Gbps network it&rsquo;ll format at around 120 MB/s, and on a 2TB drive that comes out to around 2 hours for a fast format. I forgot you could format with &ldquo;lazy&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>mkfs.ext4 -v -E lazy_itable_init=1,lazy_journal_init=1 /dev/sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Leaving that for next time, now I wait 2 hours. To make sure it mounts automatically at boot, you need to check its <code>node.startup</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node -o show | grep -E &#39;Target:|node.startup&#39;

node.startup = manual
node.startup = manual
node.startup = manual</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s set to &ldquo;manual&rdquo;, to switch it to &ldquo;automatic&rdquo; you do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ sudo iscsiadm -m node \
  -T iqn.2000-01.com.synology:TERACHAD.docker.7af6... \
  -o update -n node.startup -v automatic</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With the iSCSI daemon running and after logging in, besides <code>/dev/sda</code>, if you run <code>ls /dev/disk/by-path</code> you&rsquo;ll find something like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6exxxxx-lun-1 -&gt; ../../sdX</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Since I&rsquo;m using the AUTOFS service and it &ldquo;steals&rdquo; my <code>/mnt</code> directory, I&rsquo;m going to mount the iSCSI drive via fstab at <code>/media/docker</code> by adding this line at the end of <code>/etc/fstab</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
/dev/disk/by-path/ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6e9xxxx-lun-1  /media/docker  ext4      _netdev,nofail,x-systemd.automount,x-systemd.requires=iscsid.service,noatime,nodiratime,commit=60  0 2</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then just reload and mount:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo systemctl daemon-reload
sudo mount -a</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s it. From now on I have a &ldquo;virtual drive&rdquo; on my remote NAS. Now, let&rsquo;s switch Docker over to start using it. I can reconfigure my Docker to cache images and volumes and everything else directly on that drive. Just edit the <code>/etc/docker/daemon.json</code> file with the mount point:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;data-root&#34;: &#34;/media/docker/docker&#34;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Don&rsquo;t forget to create the directory on the new drive with the right permissions (and it does accept permissions, because it&rsquo;s a drive formatted with ext4 like any other):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo mkdir -p /media/docker/docker
sudo chown root:docker /media/docker/docker
sudo chmod 771 /media/docker/docker</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Finally, just restart the Docker service, wipe out <code>/usr/lib/docker</code> to free up space on my main NVMe, and done. It should be WAY faster than NFS, though not as fast as a local disk, of course. Even at 10Gbps, the network introduces latency, there&rsquo;s no way around it. But the heaviest part is really the build process, which has a lot of writes. For loading an already-built image, it should be blazing fast. But at least by using a block protocol instead of a file protocol, latency should drop by at least an order of 10x or more.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xh40fu275xx9q6os00m6j5gyr9w9" alt="docker build"  loading="lazy" /></p>
<p>And is it really faster? <strong>YES</strong>. In this screenshot of the docker build running, on NFS the times on the right were HUNDREDS OF SECONDS for many commands. Now they&rsquo;re back below 1 second like they should be. For all intents and purposes, I&rsquo;m not feeling any difference from what a USB 3.2 Gen 2 external HD would give me.</p>
<p>The MASSIVE difference that a BLOCK-LEVEL virtual drive makes, compared to a FILE-LEVEL NFS/SMB protocol, is brutal, the protocol is gigantic overhead. Incidentally, this is more or less how it works when you go to AWS and hire EBS storage (Elastic BLOCK Store), that&rsquo;s why it&rsquo;s called &ldquo;BLOCK&rdquo;, because it&rsquo;s a BLOCK protocol like iSCSI and not NFS. I hope you learned how much of a difference it makes to understand how protocols and filesystems work. If you haven&rsquo;t watched my videos, there&rsquo;s <a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;pp=gAQB"target="_blank" rel="noopener">this playlist</a> that explains BLOCKS in detail.</p>
<p>But yes, iSCSI has downsides: being a drive, it cannot, or should not, be shared (think of two PCs sharing the same pen drive, it would be a huge sh*tshow). Block devices were built to be used by a single device. Precisely for sharing, that&rsquo;s why protocols like SMB or NFS exist: to manage concurrent access to the same file over the network.</p>
<p>Another downside: the virtual drive is one big BLOB of bits. Without &ldquo;mounting&rdquo; it, there&rsquo;s no way to see the files. On the NAS server, it doesn&rsquo;t show the files. It&rsquo;s like a VirtualBox .vhd file. You need to &ldquo;turn it on&rdquo; and &ldquo;mount&rdquo; it on the &ldquo;PC&rdquo;. That&rsquo;s why I&rsquo;m still going to use NFS for the usual stuff, like Videos or Downloads. And the virtual drive is exclusively for the Docker cache, in this case. That works well. No other PC accesses it. If I had a second Linux machine that needed a Docker cache, I&rsquo;d have to create a second LUN just for it. Remember, it&rsquo;s a drive. Virtual or not, it behaves like a real drive to your system.</p>
]]></content:encoded><category>homelab</category><category>storage-and-backup</category><category>performance</category></item><item><title>Upscaling Old Anime to 4K with AI</title><link>https://akitaonrails.github.io/en/2025/04/19/upscaling-old-anime-to-4k-with-ai/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/19/upscaling-old-anime-to-4k-with-ai/</guid><pubDate>Sat, 19 Apr 2025 15:30:00 -0300</pubDate><description>&lt;p&gt;More than a year ago I was playing around with ErsGAN, generative adversarial networks for the task of &amp;ldquo;upscaling&amp;rdquo; (raising resolution) old anime files I have.&lt;/p&gt;
&lt;p&gt;For collectors, the problem is that a lot of anime from the 90s and earlier never came out, and never will, on Blu-Ray (1080p) or UHD (4K). Only the most famous ones get the &amp;ldquo;remaster&amp;rdquo; treatment (taking the original master tapes and recapturing them at higher resolution). So a ton of old anime is stuck in the DVD era (480p) or VHS (480i).&lt;/p&gt;</description><content:encoded><![CDATA[<p>More than a year ago I was playing around with ErsGAN, generative adversarial networks for the task of &ldquo;upscaling&rdquo; (raising resolution) old anime files I have.</p>
<p>For collectors, the problem is that a lot of anime from the 90s and earlier never came out, and never will, on Blu-Ray (1080p) or UHD (4K). Only the most famous ones get the &ldquo;remaster&rdquo; treatment (taking the original master tapes and recapturing them at higher resolution). So a ton of old anime is stuck in the DVD era (480p) or VHS (480i).</p>
<p>Not every old anime can be upscaled, it can end up looking too &ldquo;washed out&rdquo;. Anyone who downloads torrents has probably seen badly done upscales. But I wanted to be able to test it myself, on my local machine, with my old files. Here&rsquo;s a small example clip:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4)
    </video>
</div>
<p>It&rsquo;s a piece of the 1st episode of the legendary Rurouni Kenshin from 1996. Yes, today we have a remake, which they&rsquo;re redoing from scratch. But for those of us who feel nostalgic about the original, there&rsquo;s no replacement. The DVD quality isn&rsquo;t horrendous, you can still watch it just fine today as is. But I&rsquo;m curious to see how it looks in 4K.</p>
<p>I didn&rsquo;t do any long research, but a year ago I had stumbled onto <a href="https://github.com/xinntao/Real-ESRGAN"target="_blank" rel="noopener"><strong>Real ERSGAN</strong></a>. That project has been discontinued for about 4 years, so I don&rsquo;t know what the most modern option is today. But their pretrained models are still useful.</p>
<p>There are still projects that use this model, the one I know of is <a href="https://github.com/k4yt3x/video2x"target="_blank" rel="noopener"><strong>Video2K</strong></a>. On Windows it apparently even has a nice little graphical interface, but the advantage for Linux is that it runs in Docker. An example would be like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --rm --gpus all \
  -v &#34;$PWD/videos_in&#34;:/input \
  -v &#34;$PWD/videos_out&#34;:/output \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i /input/old_anime.mp4 -o /output/old_anime_4K.mkv \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you have your NVIDIA set up with the Container Toolkit, CUDA and all that (ask ChatGPT), Docker can map the device straight into the container. This makes it much easier to keep my Linux from getting dirty with dependencies and loose configs scattered around. I don&rsquo;t do anything of this kind outside of containers anymore.</p>
<p>But I ran into a problem. If you&rsquo;re using an AMD GPU, just doing this should already work, because this Docker image actually uses Vulkan and not CUDA. In my case I have a primary integrated AMD GPU and a secondary NVIDIA RTX 4090. I tried passing it directly:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ docker run --gpus all --privileged \
  --device=/dev/nvidia0 \
  --device=/dev/nvidiactl \
  --device=/dev/nvidia-uvm \
  --device=/dev/nvidia-modeset \
  --runtime=nvidia \
  -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json \
  -e VK_LAYER_PATH=/usr/share/vulkan/explicit_layer.d \
  -v &#34;$HOME/Downloads/Video2K/videos_in&#34;:/input \
  -v &#34;$HOME/Downloads/Video2K/videos_out&#34;:/output \
  --rm -it \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i &#34;/input/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57).mkv&#34; \
  -o &#34;/output/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57)_upscaled.mkv&#34; \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But it just won&rsquo;t work. If I only say &ldquo;&ndash;gpus all&rdquo; it grabs the iGPU by default. Upscaling a video ends up consuming 100% of my CPU, all 32 threads, but my NVIDIA GPU just sits there watching, doing nothing. If I try to pass &ldquo;/dev/dri/card0&rdquo; or &ldquo;/dev/nvidia0&rdquo;, it errors out and can&rsquo;t even start.</p>
<p>So from here on the solution is only for those who have NVIDIA:</p>
<h3>CUDA Dockerfile<span class="hx:absolute hx:-mt-20" id="cuda-dockerfile"></span>
    <a href="#cuda-dockerfile" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>This time I decided not to be lazy and organize everything into a little project on Github. Clone it directly from there <a href="https://github.com/akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA"target="_blank" rel="noopener">Batch Anime Upscaler Video2K Docker CUDA</a>. Just do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>git clone git@github.com:akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA.git
cd Batch-Anime-Upscaler-Video2K-Docker-CUDA
mkdir input # put your old videos here
mkdir output

docker build --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -t anime-upscaler:latest .</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If nothing goes wrong, this will build a Docker image with everything you need, then it&rsquo;s just a matter of placing your videos in the subdirectory I mentioned, or any other you want, and configuring the run command:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> docker run --gpus all --rm \
  -v &#34;./input&#34;:/input \
  -v &#34;./output&#34;:/output \
  -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s it. It takes a while. With my Ryzen 7950X3D plus RTX 4090, it processes at a rate of around 13 frames per second. So less than half the playback speed. If a video is 20 minutes long, it&rsquo;ll take more than 40 minutes to process each one. It&rsquo;s a good idea to set aside plenty of time, and that&rsquo;s also why this is best reserved for videos you really want to watch.</p>
<p>The original Video2K script supports several parameters, which I left exposed as environment variables in the Dockerfile:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>MODEL=&#34;${MODEL:-realesr-animevideov3}&#34;
SCALE=&#34;${SCALE:-4}&#34;
TILE=&#34;${TILE:-0}&#34;
DENOISE=&#34;${DENOISE:-1.0}&#34;
NUM_PROC=&#34;${NUM_PROC:-1}&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then you can change them like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>docker run --gpus all --rm \
  -e DENOISE=0.5 \
  -v ... \
  anime-upscaler:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As you can see, by default we use the pretrained model &ldquo;realesr-animevideov3&rdquo;, which is less detailed than the 6 billion parameter model but is much more stable between frames, which matters more for video. Most of the other models it supports, like &ldquo;RealESRGAN_x4plus&rdquo; or &ldquo;ESRGAN_x4&rdquo;, are meant for photos and images and not for video. Video demands temporal coherence, which is quite hard.</p>
<p>At most it&rsquo;s worth trying the &ldquo;realesr-general-x4v3&rdquo; model, which they say is a bit lighter, in case your machine is struggling too much to run the default. But then you&rsquo;ll have to modify the Dockerfile to download that model and rebuild:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># === OPTIONAL: Preload additional Real-ESRGAN models ===
# Uncomment any models you want available in the container at build time

# Anime 6-block model (better for anime stills, line art)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus_anime_6B.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth

# Original RealESRGAN 4x general-purpose model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus.pth

# RealESRGAN 2x model (lighter upscale)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x2plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x2plus.pth

# ESRNet (non-GAN model, ultra-smooth)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRNet_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRNet_x4plus.pth

# General-purpose lightweight video-friendly model (with denoise control)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.3.0/realesr-general-x4v3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-general-x4v3.pth

# Anime video model (default in your setup)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-animevideov3.pth

# Original ESRGAN (2018) model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/ESRGAN_x4.pth \
#     -O /opt/Real-ESRGAN/weights/ESRGAN_x4.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I haven&rsquo;t tested all of them, but for anyone who wants to, I think those are the links. Change it in the Dockerfile, run the build, change the MODEL variable, and test on your videos.</p>
<p>What&rsquo;s worth tweaking are things like DENOISE, which is set to &ldquo;1.0&rdquo;, the maximum, but that can leave the image too &ldquo;stretched&rdquo;. It&rsquo;s worth trying smaller values, like &ldquo;0.5&rdquo;. It varies case by case, only by testing.</p>
<p>Another factor is the scale, SCALE, where the default is &ldquo;4x&rdquo;, which turns 1080p into 4K or 720p into 1440p. But sometimes you don&rsquo;t need to go that high, and there&rsquo;s no point because the original image already has very little detail anyway. Maybe 2x is enough. The script supports 2x, 3x, 4x but it caps at 4K resolution, so there&rsquo;s no point trying to turn a video that&rsquo;s already 4K into 8K, for example.</p>
<p>My RTX 4090 and my Ryzen 9 are quite powerful. Processing just one video, I don&rsquo;t see them breaking a sweat at all:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2dz1abq9b1wteho3r1onowmij14v" alt="btop"  loading="lazy" /></p>
<p>Clearly you could run more videos in parallel. You have to be careful when it&rsquo;s unbalanced. If the CPU climbs close to 100% just to separate the frames, having spare GPU capacity doesn&rsquo;t help. With both having headroom, you can make the script spin up more than 1 process on the GPU and send frames in parallel with the &ldquo;-e NUM_PROC=2&rdquo; option. Look at how it now uses more of the GPU and the CPU still has room to spare. Maybe 3 is the maximum, but you have to test, it&rsquo;s not constant the whole time.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qur7lw4ox5s3nymh025u6q446831" alt="btop x2"  loading="lazy" /></p>
<p>This can dramatically reduce the total time. You can choose between increasing the GPU processes via the script or having two separate video directories and spinning up two parallel containers of this program to run at the same time, whichever works best for you.</p>
<h2>Final Result<span class="hx:absolute hx:-mt-20" id="final-result"></span>
    <a href="#final-result" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Let&rsquo;s see how that first clip turned out with 4x upscale and 1.0 denoise:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4)
    </video>
</div>
<p>The difference is HUGE, but whether it ended up &ldquo;GOOD&rdquo; is going to be VERY subjective. Personally I didn&rsquo;t find the original bad to watch. Maybe tweaking DENOISE down a bit, or SCALE to 2x instead of 4x, would already be good enough, I don&rsquo;t know.</p>
<p>For comparison, here&rsquo;s another clip in the original old version:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4)
    </video>
</div>
<p>And again, how it looks after the 4x upscale:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4)
    </video>
</div>
<p>At least it&rsquo;s working as it should, from here on it&rsquo;s a matter of doing some tuning. If anyone has more tips or knows of newer open source upscaling models or projects, send them in the comments below!</p>
<p>Before someone asks, why not use NVIDIA&rsquo;s DLSS 3, which they use to upscale game resolution? Answer: because they don&rsquo;t expose that functionality as an API you can access, it&rsquo;s only for their proprietary drivers in game profiles. They turn it on and tune it case by case. There&rsquo;s no general API in the CUDA lib.</p>
<h2>Post-publication Update<span class="hx:absolute hx:-mt-20" id="post-publication-update"></span>
    <a href="#post-publication-update" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I decided to test the RealERSGAN_x4plus_anime_6B model, which is much heavier than the realesr-animevideov3 I uploaded to the repository.</p>
<p>Change the Dockerfile and add this after the wget for the other model:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth \
     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This model can&rsquo;t be run in parallel even on my RTX 4090, which stays constantly at 100%. If before it ran at around 13 frames per second, with this one it only runs at 3 frames per second. It&rsquo;s 4x heavier and only for really beefy GPUs. If you don&rsquo;t have something like that, stick with &ldquo;realesr-animevideov3&rdquo;, or with the even lighter (lower quality) &ldquo;realesr-general-x4v3&rdquo;. These models depend heavily on GPU power and especially on the amount of VRAM.</p>
<p>To run it, add &ldquo;-e MODEL=RealESRGAN_x4plus_anime_6B&rdquo; to the &ldquo;docker run&rdquo; command.</p>
<p>See how the same clip from before turned out using this model:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4)
    </video>
</div>
<p>For having spent 4x more of my machine&rsquo;s resources to process it, I didn&rsquo;t find the final result that different, and in some parts I think it even has more defects. So that&rsquo;s it, the result will vary, you have to test. It depends a lot on the quality of the original anime, but just because a bigger and theoretically &ldquo;better&rdquo; model exists doesn&rsquo;t mean every result will turn out better.</p>
]]></content:encoded><category>generative-media</category><category>performance</category><category>containers</category></item><item><title>BIOS Configuration of My PC - X670E Aorus Xtreme</title><link>https://akitaonrails.github.io/en/2025/04/17/bios-config-my-pc-x670e-aorus-xtreme/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2025/04/17/bios-config-my-pc-x670e-aorus-xtreme/</guid><pubDate>Thu, 17 Apr 2025 18:50:00 -0300</pubDate><description>&lt;p&gt;This is a note to myself, so I don&amp;rsquo;t forget, and maybe it helps someone else.&lt;/p&gt;
&lt;p&gt;I know every motherboard ships pre-configured with safe and conservative &amp;ldquo;defaults&amp;rdquo;, the most stable rather than the most performant. So if you don&amp;rsquo;t go into the BIOS to tweak things, you&amp;rsquo;re leaving performance on the table.&lt;/p&gt;
&lt;p&gt;I have an AMD Ryzen 9 7950X3D CPU. The processor is a package that works with two &amp;ldquo;dies&amp;rdquo;, I&amp;rsquo;m not sure if &amp;ldquo;chip&amp;rdquo; is a way to visualize it. Each one has 8 cores, totaling 16 cores, and each core has 2 threads, so 32 threads possible in parallel.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This is a note to myself, so I don&rsquo;t forget, and maybe it helps someone else.</p>
<p>I know every motherboard ships pre-configured with safe and conservative &ldquo;defaults&rdquo;, the most stable rather than the most performant. So if you don&rsquo;t go into the BIOS to tweak things, you&rsquo;re leaving performance on the table.</p>
<p>I have an AMD Ryzen 9 7950X3D CPU. The processor is a package that works with two &ldquo;dies&rdquo;, I&rsquo;m not sure if &ldquo;chip&rdquo; is a way to visualize it. Each one has 8 cores, totaling 16 cores, and each core has 2 threads, so 32 threads possible in parallel.</p>
<p>But there&rsquo;s a difference: each die has different L3 caches. One of the dies has only 32MB and the other has 96MB, so heavier programs benefit from running on the cores of the die with more L3, and you can configure that.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/gcbhrzj8wuc38z3lonisck0ooeck" alt="lstopo"  loading="lazy" /></p>
<p>Besides that, it has a base clock of about 3GHz with the ability to turbo boost up to 5.7GHz. They can&rsquo;t all run at that speed or it heats up too fast and may &ldquo;thermal throttle&rdquo;, and then it drops everything below 3GHz. That&rsquo;s why, especially in laptops that don&rsquo;t have adequate thermal capacity, the best option is not overclocking but &ldquo;undervolting&rdquo; (cutting power so it runs less fast). Because it&rsquo;s better for everyone to be a bit less fast than for everyone to be super fast and immediately overheat and slow everyone down. It&rsquo;s complex.</p>
<p>To complicate things further, I have 96GB of DDR5 6000 RAM. Capable of a theoretical maximum of 6GHz, but that&rsquo;s theoretical because everything can desync and cause instabilities up to a kernel panic crash. That&rsquo;s why the motherboard default is the base speed of about 4GHz and the CPU also maxing out at around 4GHz.</p>
<p>But you can push that up a bit without making it unstable. In short, here&rsquo;s what I did:</p>
<h2>PBO - Precision Boost Overdrive<span class="hx:absolute hx:-mt-20" id="pbo---precision-boost-overdrive"></span>
    <a href="#pbo---precision-boost-overdrive" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ryzen CPUs have PBO to turbo boost the core clocks. It usually ships either disabled or in a conservative configuration, so your 5.7GHz CPU may be running at only 4GHz or less and never delivering its maximum.</p>
<p>To adjust this, if you want to get advanced, you need to tweak parameters like PPT (Package Power Tracking) to 200W or more. TDC (Thermal Design Current) to 160A or more. EDC (Electrical Design Current) to 200A or more. And you can set up a curve optimizer, per core, to negative (-10 or -20) to reduce voltage, which can allow higher boost clocks.</p>
<p>But this is very advanced, and you need to know a lot more about electrical stuff before
trying it. Better not risk it and not mess with this. On the screen below, don&rsquo;t leave it on Advanced, leave it on Auto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/vxeurat4imakwzdl6artkzuqymmq" alt="PBO Advanced"  loading="lazy" /></p>
<p>Instead, it&rsquo;s better to mess with PBO Enhancement Presets, which is this screen below:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a1vgigv3ka64xs6nclq9z2hh21w7" alt="PBO Presets"  loading="lazy" /></p>
<p><strong>70/80/90</strong> refers to the maximum temperature we allow the cores to reach. This will depend on whether you have a good cooling solution. Whether it&rsquo;s an AIO with liquid cooling or a <a href="https://noctua.at/en/nh-d15-g2"target="_blank" rel="noopener"><strong>Noctua NH-D15 G2</strong></a>, which is what I use. Noctua fans are extremely silent, I almost never hear them, and they have less maintenance and lower failure potential than liquid cooling. I prefer fewer moving parts to be safe.</p>
<p>[<img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4d10atf2k9rb5pntb9eec5uzqk7u" alt="Noctua NH-D15 G2"  loading="lazy" /></p>
<p>Now those <strong>Level 1 to 5</strong> are the PPT/TDC/EDC tunings I mentioned, but already pre-defined and tested by the manufacturer. 1 is less aggressive, the most stable, while 5 is the most aggressive and can be unstable. The right approach is to start at <strong>90 level 5</strong> and decrease until you find the most stable for your system. This will vary; for instance, the more RAM you have, the less aggressive it seems you can be, especially the faster that RAM is.</p>
<p>In my case, right at the boot of my Manjaro Linux, it gave a kernel panic, which looks something like this:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f25kqct9urmereleunhurknw40d4" alt="kernel panic"  loading="lazy" /></p>
<p>So I went back into the BIOS and tested different levels and, for my configuration, I could only get up to <strong>90 Level 2</strong>. This has been stable and so far I haven&rsquo;t had any random crashes.</p>
<p>Beyond that, recently (early 2025) motherboard manufacturers released firmware updates that opened access to a feature called <strong>X3D Turbo Mode</strong>. If you see this, don&rsquo;t think it&rsquo;s free extra performance. In reality, unless you use the machine more for games, you&rsquo;ll actually feel it got slower.</p>
<p>As I explained before, Ryzen 9 has 1 CCD (core complex), which is the die with 96MB of L3 cache (V-Cache). Turbo mode tries to force more programs onto the CCD that does <strong>NOT</strong> have V-Cache (the 32MB one), which can therefore reach a higher clock boost but has more latency. This is good for games, which usually don&rsquo;t benefit from having more cache but rather from more clock.</p>
<p>But at the same time, programs that need V-Cache will end up landing more often on the Core with <strong>LESS</strong> L3 cache and will then perform worse than before. Examples of that are 3D modeling programs like Blender, exactly what I want to use more now. So test it, but in principle you can leave it off.</p>
<p>This option in my firmware is called &ldquo;X3D Turbo Mode&rdquo; but elsewhere it may be &ldquo;3D V-Cache Performance Optimizer&rdquo;, same thing.</p>
<h2>XMP/EXPO RAM<span class="hx:absolute hx:-mt-20" id="xmpexpo-ram"></span>
    <a href="#xmpexpo-ram" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Now, DDR5 RAM boots at the base clock of about 4.8GHz. But depending on the RAM model, it can go much higher, in my case up to 6GHz. All modern memory also has several settings like frequency, latency, voltage, and more. Again, it&rsquo;s not worth trying to set these by hand. Better to use predefined profiles. Intel calls this support <strong>XMP</strong> and AMD calls it <strong>EXPO</strong>.</p>
<p>I forgot to take a photo of my BIOS, but this slightly older photo from Google Images is the same thing. XMP/EXPO ships disabled (conservative) but you just pick the profile that already exists:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ypwo0rsbr1bmu6w6ujm7b1qyjlof" alt="XMP/EXPO Profile"  loading="lazy" /></p>
<p>Note that just below there are things like <strong>Low Latency Support</strong> and <strong>High Bandwidth Support</strong>. Theoretically to reduce latency and increase transfer bandwidth. All of which sounds great, but in my case it doesn&rsquo;t work. If I try to enable both, my system doesn&rsquo;t boot. I was forced to clear the CMOS to reset the BIOS without that.</p>
<p>The problem: possibly too much memory (96GB). Maybe if you have less, around 32GB, this works, but it seems the more you have, the more unstable it gets if you try to go too fast.</p>
<p>Another problem, on DDR5, the more memory, the slower the Cold Boot, the initial boot when the PC was off. The BIOS has to do a check on the memory to ensure those parameters, response time and so on. And this happens on <strong>EVERY</strong> boot. It can take a few minutes and some people might even think the machine is frozen, but it&rsquo;s just that check.</p>
<p>In the BIOS there&rsquo;s an option called <strong>Memory Context Restore</strong> where it uses the parameters it recorded the last time it did that check. But I don&rsquo;t recommend it; for me it doesn&rsquo;t even boot anymore after enabling it. Theoretically it would cut down the check time and reuse old data, but that data may not match (the values can fluctuate) and that makes the whole system unstable. Better to wait an extra minute on boot than to live with an unstable system.</p>
<p>In advanced memory settings there&rsquo;s one more thing we should change: <strong>DF C-States</strong>, which needs to be <strong>DISABLED</strong>. In theory this feature is to reduce the latency between CPU and memory. But on systems with a lot of RAM (96GB or more like mine) this can also cause instability for not much noticeable gain. I prefer staying stable in this case.</p>
<p>Another setting to change is <strong>Power Supply Idle Control</strong>, which has to be set to &ldquo;Typical Current Idle&rdquo;, and so far (a firmware fix update may come), it&rsquo;s mandatory for Ryzen processors (even AM5). This prevents the &ldquo;Deep Power State&rdquo; sleep state, which can cause random kernel panics.</p>
<p>Same thing for <strong>Global C-State Control</strong>, which also needs to be disabled for the same reason: avoid &ldquo;deep sleep states&rdquo; (C6/C10) when the CPU sits in &ldquo;idle&rdquo; (doing nothing) for too long. These features exist to save energy, but it seems AMD has bugs when the CPU is forced into that state and when it wakes up, it comes back unstable or crashing. So it&rsquo;ll consume more energy when doing nothing (around less than 20W, rather than near zero), but at least it won&rsquo;t be unstable. If you want to save energy, the better option is to shut down and actually cut the power when not using it.</p>
<h2>BIOS Summary (for me)<span class="hx:absolute hx:-mt-20" id="bios-summary-for-me"></span>
    <a href="#bios-summary-for-me" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>PBO Enhancement: <strong>90 Level 2</strong> (clock boost slightly more aggressive than the default)</li>
<li>Power Supply Idle Control: <strong>Typical Current Idle</strong> (prevents the idle crash bug)</li>
<li>Global C-State Control: <strong>Disabled</strong> (avoids deep sleep, which causes instability)</li>
<li>DF C-States: <strong>Disabled</strong> (also avoids sleep, improving Infinity Fabric latency)</li>
<li>XMP/EXPO: <strong>Profile XMP1</strong> if you have it (raises the memory clock)</li>
</ul>
<p>There are 2 important things, especially if you&rsquo;re using discrete GPUs:</p>
<ul>
<li>Above 4G Decoding and Re-Size Bar Support. In my case both are on by default; if yours are off, turn them on.</li>
</ul>
<p>On Linux, you can check if it&rsquo;s enabled. Run the following:</p>
<p><code>sudo lspci -vv | grep -A 15 VGA</code></p>
<p>You should see &ldquo;Resizable BAR&rdquo; like this:</p>
<p><code>Capabilities: [XX] PCI Express, ... Resizable BAR: Supported (Enabled)</code></p>
<p>If it doesn&rsquo;t show up, go to the BIOS and look for it to enable. Without this you&rsquo;re not using the maximum of your video card. I think on iGPUs (which are GPUs already built into the CPU) this option doesn&rsquo;t exist.</p>
<h2>Linux-Zen Kernel<span class="hx:absolute hx:-mt-20" id="linux-zen-kernel"></span>
    <a href="#linux-zen-kernel" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I&rsquo;ve always used the most stable LTS default kernel, currently Linux66, if I&rsquo;m not mistaken. But I remembered I can use the Zen kernel, which is the same default kernel Arch uses with performance tunings to improve latency, especially in multi-thread. It switches to the CFS scheduler with improvements. Theoretically, it improves overall system responsiveness. I think it&rsquo;s worth using. On Arch/Manjaro it&rsquo;s like this:</p>
<p><code>yay -S linux-zen-versioned-bin linux-zen-versioned-headers-bin</code></p>
<p>This will uninstall the old kernel and, if you&rsquo;re using NVIDIA GPUs, it&rsquo;ll also uninstall the driver package. So you also need to install the following package:</p>
<p><code>yay -S nvidia-dkms</code></p>
<p>And then, with everything installed, run the following command:</p>
<p><code>sudo dkms autoinstall</code></p>
<p>This will reinstall the nvidia driver binaries for the new Zen kernel. In Manjaro&rsquo;s case, you don&rsquo;t need to mess with GRUB because in my case it&rsquo;s set to <code>GRUB_DEFAULT=saved</code> in <code>/etc/default/grub</code>. That means when it reboots, it&rsquo;ll record which kernel I last chose, and on the next reboot, if I don&rsquo;t choose anything, it&rsquo;ll load again the last kernel I picked. I prefer it this way over hard-coding which kernel is the default.</p>
<p>Once that&rsquo;s done, on the next boot just check:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ uname -r
6.14.2-zen1-1-zen</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Another thing is to check if <strong>CPPC</strong> is active. That&rsquo;s <strong>Collaborative Processor Performance Control</strong>, a feature that exposes to the OS kernel a ranking of cores (which has the highest boost, which has more cache, etc). Remember the X3D Turbo Mode I mentioned earlier? Same concept: to help the OS pick which core to send each program to so it runs better.</p>
<p>Not every motherboard firmware exposes this as an option; in the case of my Gigabyte X670E, it doesn&rsquo;t exist. That probably means it&rsquo;s already on by default. To check if it&rsquo;s on, just go to the Linux terminal and run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>cat /sys/devices/system/cpu/cpu*/cpufreq/amd_pstate_highest_perf</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If it returns any result, it means it&rsquo;s on and the firmware is informing the kernel about the characteristics of each core, so the scheduler has more information to manage where to send each thread.</p>
<p>Another way to check:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ls /sys/devices/system/cpu/cpu0/cpufreq/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It needs to return a list containing names like these:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>amd_pstate_highest_perf
amd_pstate_nominal_perf
amd_pstate_preferred_core</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Which, again, is a sign that Linux is recognizing CPPC properly.</p>
<p>Manjaro itself, besides the stable kernels, has the &ldquo;linux-rt&rdquo; versions which are for real-time systems. Technically, this is meant not just for lower latency, but for stable and more predictable latencies. <strong>DO NOT USE THIS KIND OF KERNEL</strong>. This is for factory systems or things like that which need processing determinism and have no tolerance for random latency. It doesn&rsquo;t mean it&rsquo;ll run more responsively; it might even run slower, but the important thing is that it&rsquo;s a predictable slow.</p>
<p>Others might mention the <strong>Linux Liquorix</strong> kernel, which many compare to Zen. But while Zen is based on the same kernel Arch uses, Liquorix&rsquo;s is based on the Debian/Ubuntu version. So it&rsquo;s not recommended on Arch/Manjaro. On top of that it&rsquo;s more experimental and theoretically less stable too. Use it if you&rsquo;re on Ubuntu or derivatives. On Arch, better stick with Zen.</p>
<h2>check_ryzen_perf.sh<span class="hx:absolute hx:-mt-20" id="check_ryzen_perfsh"></span>
    <a href="#check_ryzen_perfsh" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>If you have a Ryzen system, copy the following script and run it on your machine:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🔍 Verifying Ryzen 7950X3D Tuning on Linux&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPU Driver: &#34;</span>
</span></span><span class="line"><span class="cl">grep . /sys/devices/system/cpu/cpu0/cpufreq/scaling_driver
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ EPP Mode: &#34;</span>
</span></span><span class="line"><span class="cl">cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ CPPC Detected: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_highest_perf <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Preferred Core Ranking: &#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[</span> -f /sys/devices/system/cpu/cpu0/cpufreq/amd_pstate_prefcore_ranking <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;Yes&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;No&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -n <span class="s2">&#34;✅ Running Kernel: &#34;</span>
</span></span><span class="line"><span class="cl">uname -r
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> -e <span class="s2">&#34;\n✅ All key Linux-side Ryzen tuning options appear active.\n&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>On my machine it now produces this result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>🔍 Verifying Ryzen 7950X3D Tuning on Linux
✅ CPU Driver: amd-pstate-epp
✅ EPP Mode: performance
✅ CPPC Detected: Yes
✅ Preferred Core Ranking: Yes
✅ Running Kernel: 6.14.2-zen1-1-zen

✅ All key Linux-side Ryzen tuning options appear active.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Saying that, theoretically, all the performance bits seem to be active and working. If one of them isn&rsquo;t, ask ChatGPT what to do for your setup.</p>
<h2>Checking the BIOS Version<span class="hx:absolute hx:-mt-20" id="checking-the-bios-version"></span>
    <a href="#checking-the-bios-version" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Many motherboard manufacturers offer software, for Windows, to automatically download and update the firmware. But in Gigabyte&rsquo;s case, there&rsquo;s no Linux support. To check which version is currently installed, without having to enter the BIOS, on Linux there&rsquo;s this command:</p>
<p><code>sudo dmidecode -s bios-version</code></p>
<p>In my case, it now returns F33 (March 2025 version). Now, to compare whether that&rsquo;s the newest version, I just have to manually go to Gigabyte&rsquo;s crappy site, which in my case is <a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support"target="_blank" rel="noopener">this link</a>. For your specific model, and manufacturer of course, this link will vary.</p>
<p><a href="https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-1x/support#support-dl"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/72l1if05q22u1ogowfzws9rtz95j" alt="x670e support page"  loading="lazy" /></a></p>
<p>To make it worse, it seems the content is loaded asynchronously by javascript, so if you try to use <code>wget</code> or <code>curl</code> from the command line, the content won&rsquo;t have loaded yet.</p>
<p>I decided to write a dumb script to solve that. First, in case you don&rsquo;t use <code>mise</code> like I do (see my Github project <a href="https://github.com/akitaonrails/omakub-mj"target="_blank" rel="noopener">Omakub-MJ</a> for a setup just like mine), install Node manually like this:</p>
<p><code>sudo pacman -S nodejs npm</code></p>
<p>Create some directory for the project and from there do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>npm init -y
npm install -g playwright
npx playwright install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Create a file <code>check_bios_playwright.js</code> with this content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">const</span> <span class="p">{</span> <span class="nx">chromium</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;playwright&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="kr">async</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">browser</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">chromium</span><span class="p">.</span><span class="nx">launch</span><span class="p">({</span> <span class="nx">headless</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">page</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">newPage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Load support page
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="kr">goto</span><span class="p">(</span><span class="s1">&#39;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">waitUntil</span><span class="o">:</span> <span class="s1">&#39;domcontentloaded&#39;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Click the BIOS tab manually (it triggers JS to fetch content)
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">click</span><span class="p">(</span><span class="s1">&#39;id=bios-count&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Wait for BIOS version element to load
</span></span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">waitForSelector</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="p">{</span> <span class="nx">timeout</span><span class="o">:</span> <span class="mi">60000</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Grab the first BIOS version listed
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">biosVersion</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">page</span><span class="p">.</span><span class="nx">$eval</span><span class="p">(</span><span class="s1">&#39;.div-table-body-BIOS .download-version&#39;</span><span class="p">,</span> <span class="nx">el</span> <span class="p">=&gt;</span> <span class="nx">el</span><span class="p">.</span><span class="nx">innerText</span><span class="p">.</span><span class="nx">trim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;🌐 Latest BIOS Version on Gigabyte site:&#39;</span><span class="p">,</span> <span class="nx">biosVersion</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">await</span> <span class="nx">browser</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">})();</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now create a bash script <code>check_bios_update.sh</code> with this content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get directory of this script (absolute path)</span>
</span></span><span class="line"><span class="cl"><span class="nv">SCRIPT_DIR</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span><span class="nb">cd</span> <span class="s2">&#34;</span><span class="k">$(</span>dirname <span class="s2">&#34;</span><span class="si">${</span><span class="nv">BASH_SOURCE</span><span class="p">[0]</span><span class="si">}</span><span class="s2">&#34;</span><span class="k">)</span><span class="s2">&#34;</span> <span class="o">&amp;&amp;</span> <span class="nb">pwd</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run the Playwright JS script in the same directory</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span>node <span class="s2">&#34;</span><span class="nv">$SCRIPT_DIR</span><span class="s2">/check_bios_playwright.js&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Output</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🖥  Current BIOS: </span><span class="nv">$CURRENT</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;🌐 Latest BIOS:  </span><span class="nv">$LATEST</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;✅ You are up to date.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;⬆  BIOS update available!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Do a <code>chmod +x</code> to make it executable and, if all goes well, you&rsquo;ll get a response like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[sudo] password for akitaonrails:
🖥  Current BIOS: F33
🌐 Latest BIOS:  F33
✅ You are up to date.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s a cannon shot to kill a fly, but this will use headless Chromium to pull the page, load the javascript, and then use CSS selectors to find the BIOS version, compare it to the one on your machine, and see if it&rsquo;s up to date.</p>
<p>If it isn&rsquo;t, there&rsquo;s no way around it: you have to manually go to the site, download the ZIP file, extract it onto a pen drive, and boot into the BIOS to update manually. But at least I can quickly check from time to time with one command line now.</p>
<h2>Automatic BIOS Version Check<span class="hx:absolute hx:-mt-20" id="automatic-bios-version-check"></span>
    <a href="#automatic-bios-version-check" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I decided I don&rsquo;t want to remember to run that script manually, so the best thing is to create a user systemd service and check automatically every week. If there&rsquo;s a new version, create a notification to GNOME so I can see it visually and automatically open the download page for me.</p>
<p>First, I need to give my user access to the dmidecode command to check my current BIOS version without having to type a password with <code>sudo</code>. To do that, let&rsquo;s create a new sudoers rule like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo visudo -f /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;ll open an empty file where I fill in like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo /usr/sbin/dmidecode -s bios-version</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And don&rsquo;t forget to set the correct permissions on this file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo chmod 0440 /etc/sudoers.d/bios-check</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>With that it won&rsquo;t ask for a password to run that command anymore, since we&rsquo;re going to put it in a background service running automatically. Obviously it&rsquo;s not safe to allow running <code>sudo</code> without a password, so we restrict it exclusively to allow it only on that specific binary and nothing else.</p>
<p>In the same directory as the other files with playwright installed and so on, I create a <code>bios-update-check.sh</code> with this content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Path to your Playwright BIOS scraper script</span>
</span></span><span class="line"><span class="cl"><span class="nv">JS_CHECKER</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$HOME</span><span class="s2">/bios-check/check_bios_playwright.js&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NODE_BIN</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>which node<span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># URL of your motherboard&#39;s BIOS support page</span>
</span></span><span class="line"><span class="cl"><span class="nv">URL</span><span class="o">=</span><span class="s2">&#34;https://www.gigabyte.com/Motherboard/X670E-AORUS-XTREME-rev-10/support#support-dl-bios&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get current BIOS version</span>
</span></span><span class="line"><span class="cl"><span class="nv">CURRENT</span><span class="o">=</span><span class="k">$(</span>sudo dmidecode -s bios-version <span class="p">|</span> tr -d <span class="s1">&#39;[:space:]&#39;</span> <span class="p">|</span> tr <span class="s1">&#39;[:lower:]&#39;</span> <span class="s1">&#39;[:upper:]&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get latest BIOS version from Gigabyte site</span>
</span></span><span class="line"><span class="cl"><span class="nv">LATEST</span><span class="o">=</span><span class="k">$(</span><span class="nv">$NODE_BIN</span> <span class="s2">&#34;</span><span class="nv">$JS_CHECKER</span><span class="s2">&#34;</span> <span class="p">|</span> grep -o <span class="s1">&#39;F[0-9]\+&#39;</span> <span class="p">|</span> head -1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> -z <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send <span class="s2">&#34;BIOS Check&#34;</span> <span class="s2">&#34;⚠️ Failed to fetch latest BIOS version from Gigabyte&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$CURRENT</span><span class="s2">&#34;</span> !<span class="o">=</span> <span class="s2">&#34;</span><span class="nv">$LATEST</span><span class="s2">&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">  notify-send -u normal <span class="s2">&#34;🔔 BIOS Update Available!&#34;</span> <span class="s2">&#34;Current: </span><span class="nv">$CURRENT</span><span class="s2"> → Latest: </span><span class="nv">$LATEST</span><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Opening the download page...&#34;</span> --app-name<span class="o">=</span><span class="s2">&#34;BIOS Checker&#34;</span>
</span></span><span class="line"><span class="cl">  xdg-open <span class="s2">&#34;</span><span class="nv">$URL</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s the same as the previous script but now using <code>notify-send</code> to send messages to Gnome (in KDE it&rsquo;s different) and using <code>xdg-open</code> to open the URL in the system&rsquo;s default browser. Configure differently for your system. Don&rsquo;t forget to make it executable and test:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>chmod &#43;x ~/.local/bin/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now to create the systemd service for my user (different from creating one for the system that runs with more permissions. I only need this running at my user level), so I create the file <code>~/.config/systemd/user/bios-check.service</code> with the following content:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Weekly BIOS version checker

[Service]
Type=oneshot
ExecStart=/home/akitaonrails/.local/bin/bios-check/bios-update-check.sh</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now the timer service to run every week, called <code>~/.config/systemd/user/bios-check.timer</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[Unit]
Description=Run BIOS version check weekly

[Timer]
OnCalendar=weekly
Persistent=true

[Install]
WantedBy=default.target</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And enable the timer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>systemctl --user daemon-reexec
systemctl --user daemon-reload
systemctl --user enable --now bios-check.timer</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>By Systemd convention, a timer [blabla].timer will execute the service with the same name [blabla].service. That&rsquo;s why you need both. If you enable the service directly, it&rsquo;ll run only once and exit. To know if it&rsquo;s enabled correctly do the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>✦ ❯ systemctl --user list-timers

NEXT                          LEFT LAST PASSED UNIT             ACTIVATES
Mon 2025-04-21 00:00:00 -03 2 days -         - bios-check.timer bios-check.service

1 timers listed.
Pass --all to see loaded but inactive timers, too.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s how you can automate some things on your system and get more out of your Gnome.</p>
<h2>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>I only started this journey this time because it occurred to me that it had been months since I&rsquo;d last updated the motherboard firmware, and then I took the chance to tune some things I hadn&rsquo;t done before. Things like PBO were on automatic (so I was leaving performance behind; I remember that last time it had been unstable but I didn&rsquo;t dig in to find out why), things like XMP/EXPO were already on. But now I think I really enabled everything I could.</p>
<p>It had also been years since I tried out the Linux-Zen kernel, so I took the chance to test it now. And as I said, so far it&rsquo;s stable. Since I&rsquo;ve started learning Blender and pulling more CPU and memory, I figured it was worth spending time trying to extract the most from my machine. 3D modeling is totally CPU/RAM, more than GPU.</p>
<p>Oh yes, speaking of Blender, one last thing. Remember my Ryzen has 1 CCD, which is that 1 die with 96MB of L3 cache. The best thing to do is create affinity for Blender with that die and prevent it from landing on the core with only 32MB of L3 cache.</p>
<p>On Linux, to do that, here&rsquo;s the command line:</p>
<p><code>taskset -c 0-15 blender-4.3 &amp;</code></p>
<p>The taskset command will hint to the scheduler that blender should stay on threads 0 to 15 (where the V-Cache is) and not 16 to 31 (which only have 32MB). That way, in theory, I&rsquo;m giving Blender the best chances to run at the best possible performance on this machine. Less important programs like the browser, the terminal, if they land on the 32MB core, no problem.</p>
<p>I do the same thing in libvirt/qemu when I run emulated Windows: I configure libvirt to bring up the VM tied to the 96MB CCD and leave the other core for the Linux host. Speaking of BIOS, by the way, to run VMs you have to enable SVM and IOMMU, which usually ship disabled because virtual machine support steals a bit of the CPU&rsquo;s performance. Maximum performance is with them <strong>disabled</strong>. If you&rsquo;re not going to use VMs, leave them off.</p>
<p>And if anyone noticed that I&rsquo;m explicitly running Blender 4.3 instead of the newer Blender 4.4, it&rsquo;s because some addons still don&rsquo;t work on the newer version, so it&rsquo;s better to stay one version back to keep more compatibility with addons that don&rsquo;t update fast.</p>
]]></content:encoded><category>hardware</category><category>performance</category><category>linux</category></item><item><title>Windows Subsystem for Linux is good, but not enough yet</title><link>https://akitaonrails.github.io/en/2017/09/20/windows-subsystem-for-linux-is-good-but-not-enough-yet/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/09/20/windows-subsystem-for-linux-is-good-but-not-enough-yet/</guid><pubDate>Wed, 20 Sep 2017 14:43:00 -0300</pubDate><description>&lt;p&gt;I wrote a premature post in July of 2016 titled &lt;a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener"&gt;&amp;ldquo;The Year of Linux on the Desktop - It&amp;rsquo;s Usable!&amp;rdquo;&lt;/a&gt; and, indeed, it was (barely) usable.&lt;/p&gt;
&lt;p&gt;The real &amp;ldquo;Year of Linux on the Desktop&amp;rdquo; is still not here. It&amp;rsquo;s getting closer as I will explain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; this won&amp;rsquo;t be &amp;lsquo;yet-another-WSL-install-blog-post&amp;rsquo;, there are already several out there, for example &lt;a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener"&gt;this one&lt;/a&gt; or &lt;a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener"&gt;this other one&lt;/a&gt;. And you can always &lt;a href="http://lmgtfy.com/?q=windows&amp;#43;10&amp;#43;creator&amp;#43;update&amp;#43;install&amp;#43;WSL"target="_blank" rel="noopener"&gt;Google it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Back in early 2016 Microsoft announced and launched a very alpha version of their current Windows Subsystem for Linux (WSL). It booted and ran some software, but it was awfully buggy. Fair enough as it was still an Insider preview only (by the way, avoid Insider Preview builds, they BSOD all the time as it&amp;rsquo;s in active development).&lt;/p&gt;</description><content:encoded><![CDATA[<p>I wrote a premature post in July of 2016 titled <a href="http://www.akitaonrails.com/en/2016/07/26/o-ano-do-linux-no-desktop-esta-usavel"target="_blank" rel="noopener">&ldquo;The Year of Linux on the Desktop - It&rsquo;s Usable!&rdquo;</a> and, indeed, it was (barely) usable.</p>
<p>The real &ldquo;Year of Linux on the Desktop&rdquo; is still not here. It&rsquo;s getting closer as I will explain.</p>
<p><strong>TL;DR</strong> this won&rsquo;t be &lsquo;yet-another-WSL-install-blog-post&rsquo;, there are already several out there, for example <a href="https://medium.com/xtrememl/why-how-to-use-windows-10-wsl-built-in-linux-for-machine-learning-6a225f4bbd3a"target="_blank" rel="noopener">this one</a> or <a href="https://www.neowin.net/news/bash-plus-windows-10-equals-linux-gui-apps-on-the-windows-desktop"target="_blank" rel="noopener">this other one</a>. And you can always <a href="http://lmgtfy.com/?q=windows&#43;10&#43;creator&#43;update&#43;install&#43;WSL"target="_blank" rel="noopener">Google it</a>.</p>
<p>Back in early 2016 Microsoft announced and launched a very alpha version of their current Windows Subsystem for Linux (WSL). It booted and ran some software, but it was awfully buggy. Fair enough as it was still an Insider preview only (by the way, avoid Insider Preview builds, they BSOD all the time as it&rsquo;s in active development).</p>
<p>A year has passed, and we got to the Windows 10 Anniversary Update and, finally, the much-anticipated <strong>Creators Update</strong>. This is very polished in comparison.</p>
<p>If you&rsquo;re unaware of what the WSL is, I highly recommend that you read the <a href="https://blogs.msdn.microsoft.com/wsl/"target="_blank" rel="noopener">articles at Microsoft&rsquo;s Developer Blog</a> and Jessie Frazelle&rsquo;s awesome <a href="https://blog.jessfraz.com/post/windows-for-linux-nerds/"target="_blank" rel="noopener">Windows for Linux Nerds blog post</a>. In a nutshell, WSL wraps the Windows kernel system calls and maps them as Linux Kernel system calls, fooling ELF64 unmodified Linux compatible binaries to run as if they were on a real Linux-based system. The kicker is that there is no Linux kernel running! It also wraps Windows services such as filesystem, networking, into appropriate devices that Linux can access.</p>
<p>So, it&rsquo;s not any kind of virtualization. There is no Hyper-V involved. It&rsquo;s enabling Windows to boot ELF64 binaries up directly, without recompilation, without modification.</p>
<p>Unlike in OS X where we need to tweak source code and recompile to run on Darwin (the Mach/BSD-based operating system layer), we can download Ubuntu binaries (or any other distro for that matter, there are Fedora and Suse coming soon to the Windows App Store) and run them <strong>directly</strong>.</p>
<p>Even more interesting, you can run system level daemons (yes, Postgresql, Memcached, Redis, etc all load and run in the background) and you can even run X (GUI) programs!</p>
<p>Don&rsquo;t get me wrong, I commend everybody that is working hard at Microsoft to make this happen. It is a technical tour de force in itself.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/657/2017-09-14.png" alt="Windows and X side-by-side"  loading="lazy" /></p>
<p>Take a good look at the screenshot above. This is a sight to behold: native GNOME-VIM running natively side-by-side with Microsoft Edge loading a web site running out of a Rails app running within WSL, accessing PostgreSQL.</p>
<p>But, is it good for real people?</p>
<h3>How good is WSL?<span class="hx:absolute hx:-mt-20" id="how-good-is-wsl"></span>
    <a href="#how-good-is-wsl" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>TL;DR</strong> it&rsquo;s not nearly as smooth as running a pure Linux-distro, and the integration is not nearly as good as OS X in terms of usability as Windows was not redesigned in any meaningful way to be a UNIX system like OS X.</p>
<p>In an ideal world, Windows should replace its entire NT underpinnings and either build their own BSD-inspired infrastructure (like Darwin) or create their own Linux distro using off-the-shelf Linux kernel as the main driver (probably can&rsquo;t because of GPL).</p>
<p>But this is unrealistic. Apple could do it in a time when the Mac installed base was tiny, so alienating some people wouldn&rsquo;t hurt so much. Windows is just too big to fail. It is and will remain a maintenance nightmare of epic proportions.</p>
<p>That being said, WSL in itself is a miracle, so I can&rsquo;t complain. Microsoft had a really harsh decade in the turn of the century. Windows 10 is a return to form, and 2 big iterations later, it&rsquo;s finally becoming reasonable again. Maybe we&rsquo;re a couple of upgrades away from a really usable WSL. Remains to be seen.</p>
<p>Apple had its miracle in the turn of the century, and OS X and iOS are terrific OSes. Best of the breed. And by being able to reboot the entire ecosystem from scratch is not something anyone can do.</p>
<p>Linux-distros had their own ups and downs. No ecosystem can evolve healthily and in a cohesive way being that fragmented. But each major distro was able to brute force their own ecosystems. Canonical has prevailed where many failed but they are still not a definitive winner, and it feels like they&rsquo;re <a href="https://arstechnica.com/information-technology/2017/04/ubuntu-unity-is-dead-desktop-will-switch-back-to-gnome-next-year/"target="_blank" rel="noopener">losing their steam</a>. RedHat enjoyed the craze of the dot-com era and was able to fly high in the Enterprise market. There is no &ldquo;best&rdquo; distro. It&rsquo;s again, a matter of taste and ego, most of the time for most of the people.</p>
<p>And the OSS community still wastes an insurmountable amount of time in crazy disputes over stuff like the <a href="https://www.reddit.com/r/linuxmasterrace/comments/616wxo/what_is_with_all_the_controversy_with_systemd/"target="_blank" rel="noopener">Systemd controversy</a> or the time it is taking to get over X and finally get into Wayland. OSS has pros and cons, just like everything else.</p>
<p>But I digress, going back to WSL. Yes, as promised almost anything you can do in a native Ubuntu 16.04 can be done in this new environment. Even the ridiculous <code>cmd.exe</code> was heavily upgraded to the 21st century. Still nowhere near a default Terminal on Linux or OS X, but getting close. Actually, I&rsquo;d recommend installing <a href="https://github.com/goreliu/wsl-terminal"target="_blank" rel="noopener">WSL Terminal</a> as a replacement, and despite the terrible &ldquo;Bash on Ubuntu on Windows&rdquo; moniker, I&rsquo;d recommend installing ZSH instead and launching it over Bash for extra usability features.</p>
<p>You can Google around for endless tutorials on how to customize your environment, Vim, tweak PostgreSQL and so forth. So again I am not repeating those here. A caveat is that I have to <code>sudo /etc/init.d/PostgreSQL start</code> to actually run daemons. They won&rsquo;t autoload on a restart, keep that in mind.</p>
<p>And now you can install a Windows X Server such as <a href="https://sourceforge.net/projects/xming/"target="_blank" rel="noopener">xming</a> and <code>export DISPLAY=:0</code> and you now <code>sudo apt install gvim</code> and load the graphical version of Gvim, for instance. Or a full GNOME or XFCE desktop if that&rsquo;s your thing. You can run X based applications side-by-side to Windows apps, and that&rsquo;s super neat. I like to install <a href="https://github.com/skwp/dotfiles"target="_blank" rel="noopener">YADR</a> which is one of those super customized dotfiles collection and it works just fine.</p>
<p>I am now using <a href="https://github.com/asdf-vm/asdf"target="_blank" rel="noopener">ASDF</a> to install and manage multiple versions of Ruby, Node.js, Elixir, etc. And it also just works.</p>
<p>I was able to move my SSH keys from my previous <a href="http://www.akitaonrails.com/en/2017/01/10/arch-linux-a-melhor-distro-de-todas"target="_blank" rel="noopener">Arch Linux</a> install. And then I can clone from my Git repositories. Now, here comes the <strong>big caveat</strong>. I cloned and configured a Rails project. This is the fastest I can get running an entire RSpec suite with PhantomJS acceptance tests:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 5 minutes 35 seconds (files took 48.53 seconds to load)
888 examples, 0 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is running on my <a href="http://www.akitaonrails.com/en/2017/01/31/do-macbook-pro-para-o-dell-xps-arch-linux-para-usuarios-criativos"target="_blank" rel="noopener">Dell XPS 9550</a> with Core i7 Skylake (4 cores, 8 threads), 16 GB of RAM and NVME2 SSD. I asked a co-worker to run it on his very old Mac Mini 2012 with Core i5 (probably Haswell), 10 GB of RAM, normal SSD. And this is his test run:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 37.6 seconds (files took 9.73 seconds to load)
888 examples, 0 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Yep, this is shocking. The filesystem emulation is <a href="https://meta.discourse.org/t/installation-notes-for-discourse-on-bash-for-windows/48141/3?u=sam"target="_blank" rel="noopener">notoriously slow</a> under WSL (at least 4 times slower as you can see in the numbers above).</p>
<p>This other test ran in a slightly newer hardware (still underpowered compared to my Dell XPS) running on Linux Mint:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 3 minutes 2.4 seconds (files took 22.69 seconds to load)
888 examples, 1 failure</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I ran the same suite a couple of times to make sure, but it can&rsquo;t go below the 5 minute mark and loading files is always around 1 minute while in other OSes it&rsquo;s 2 to 4 times faster.</p>
<p>If your development workflow is I/O bound, you will suffer. Webpack compilation, which is slow, gets even slower. Npm/Yarn installs take forever. Installing stuff from source is super slow.</p>
<p>It&rsquo;s not unusable, but this slowness will get old fast.</p>
<h3>What about Virtualization?<span class="hx:absolute hx:-mt-20" id="what-about-virtualization"></span>
    <a href="#what-about-virtualization" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>After I saw the dismal filesystem performance shown above, I wondered <em>&ldquo;and what about running everything under good old Virtualbox?&rdquo;</em></p>
<p>In theory, WSL &ldquo;should&rdquo; be a thinner layer than Virtualbox, but the worst offender in terms of overhead seems to be the filesystem abstraction layer, and not necessarily access to CPU or RAM. My instinct would first suggest that a Virtual Drive, which is a single file mounted as a virtual hard drive, would perform much better.</p>
<p>So here I go, installing Virtualbox and the same set of tools (asdf, etc).</p>
<p>Mounting a shared folder directly to the one in WSL doesn&rsquo;t work properly, seems like the mounting protocol doesn&rsquo;t understand dot files, so they don&rsquo;t show up. One has to tar the folder and untar within the machine to preserve hidden files and permissions.</p>
<p>This is running the same Rspec suite over Virtualbox with 4 Cores (out of the 8 my machine has) and 4GB of RAM:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.4 seconds (files took 6.33 seconds to load)
888 examples, 2 failures</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, we&rsquo;re down from 5:35 min to 2:03 min and file loading from 49 seconds down to 6 seconds! So around 2.5 times faster execution and a whopping 8 times increase in file loading! This is ridiculous and it makes way more sense. My 2016 machine, within Virtualbox, still outperforms 2011/2012 bare hardware. And WSL is in the bottom of the list.</p>
<p>Forget synthetic benchmarks, this is a real-world case mixing CPU and I/O bound executions. Just for the sake of comparison, I tried to see if I could squeeze a bit more performance if I try to run over VMWare Workstation. This is the result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Finished in 2 minutes 3.3 seconds (files took 6.21 seconds to load)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Almost identical. To me, it says that Virtualbox came a long way in being comparable to commercial, historically more robust solutions such as VMware or Parallels, so I will stick with Virtualbox for the time being.</p>
<p>I tried another test. This is <code>asdf</code> installing the brand new Ruby 2.4.2 from source. First over WSL (running with <code>time</code>):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>229.38s user 244.59s system 113% CPU 6:57.51 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the same install from Virtualbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>295,39s user 22,43s system 135% CPU 3:53,87 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Again, a mixed scenario of CPU and I/O bound executions. And I am at least twice as fast on Virtualbox.</p>
<p>If you do want to see more controlled scenarios running synthetic benchmark comparison, I recommend you read <a href="http://www.phoronix.com/scan.php?page=article&amp;item=intel-7900x-wsl&amp;num=1"target="_blank" rel="noopener">Phoronix&rsquo;s take on WSL vs Virtualbox</a></p>
<h3>The &ldquo;Year of Linux on the Desktop&rdquo; Conundrum<span class="hx:absolute hx:-mt-20" id="the-year-of-linux-on-the-desktop-conundrum"></span>
    <a href="#the-year-of-linux-on-the-desktop-conundrum" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The Lack of commercial software is one problem but one of the reasons is the lack of backward binary compatibility.</p>
<p>For all its problems, Microsoft remained committed to a very difficult concept: you can actually run unmodified binaries from Windows 95 in the current Windows 10. Most of the time they &ldquo;just work&rdquo;. In Linux distros, every binary is compiled against very specific library headers such as the kernel itself, Glibc and many others. Whenever one of those change, all binaries must be recompiled to run. Which is why you have the concept of &ldquo;distros&rdquo; where they recompile and test a whole set of software. Long-Term Support (LTS) is basically locking those dependencies versions for a couple of years to have software running without the need to recompile so much. But in bleeding-edge distros, you can&rsquo;t avoid having to recompile a lot of things many times over.</p>
<p>Windows carries the old dependency binaries around. OS X does the same to a degree. And OS X goes one step further by providing the concept of &ldquo;fat binaries&rdquo; (or &ldquo;Universal Binaries&rdquo;) where you have a package with multiple binaries that target different hardware architectures (32-bit vs 64 bit, Intel vs ARM vs PowerPC).</p>
<p>Mostly because of this support, commercial complex applications like Office or Adobe&rsquo;s Creative Suite can run on many different machines, across many different OS versions, over many years without upgrading. In Linux distros, it&rsquo;s a major hassle for binaries. Which is why having source code available is so important and it&rsquo;s a philosophical discussion over the whole &ldquo;Free Software&rdquo; concept, as envisioned by Richard Stallman.</p>
<p>This is an unsolvable dilemma. Linux distros don&rsquo;t have binary backward compatibility out of the box. Developers must rely on tools such as the <a href="https://lvc.github.io/abi-compliance-checker/"target="_blank" rel="noopener">ABI Compliance Checker</a>. Try to use <a href="http://www.airs.com/blog/archives/220"target="_blank" rel="noopener">symbol versioning</a> from glibc and so forth, but it&rsquo;s not realistic to expect volunteers collaborating their free time to open source to keep maintaining old versions of software for many years down the road. It is a fair compromise. Do you want the best, faster, most secure? Upgrade often, but expect old, unmaintained software to break.</p>
<p>So, without releasing the source code, you can&rsquo;t have good compatible binaries for every single major distribution, across many different versions going back many years.</p>
<h3>Which is right for you?<span class="hx:absolute hx:-mt-20" id="which-is-right-for-you"></span>
    <a href="#which-is-right-for-you" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There is no point in trying to benchmark OSes. They are all generally good enough.</p>
<p>Macs are more expensive (sometimes by a whopping 1 grand or more). There is no denying that. The compromise is that Apple&rsquo;s hardware, OS X and the ecosystem are generally the better all around, and the &quot; fit and finish&quot; of the products are really usually better from a user experience perspective.</p>
<p>For the best mix of mainstream software and open source software, you won&rsquo;t find a better alternative. If you can afford a Mac, get a Mac.</p>
<p>Gamers have to stick with Windows. Again, there is no denying that. The best games are on Windows, period. The best combination of hardware supports Windows. No Linux tweaking will give you the raw juice you need to squeeze out 4K 60fps out of the newest AMD and NVIDIA cards through DirectX. And the Mac hardware is capped, and you can&rsquo;t add the newest GPUs. If you want to play the latest AAA games the way they were envisioned to perform, get custom hardware and Windows 10.</p>
<p>Web Developers are luckier. You can&rsquo;t go wrong, it&rsquo;s literally a matter of taste which OS you choose. You will be able to deliver the same results using the same tools. And unless you&rsquo;re targeting specific Microsoft infrastructure (SQL Server, Active Directory, etc), you can install any Linux distro and rejoice.</p>
<p>Desktop App Developers must target their audience. If you want to build Windows desktop apps, you have to be on Windows. You can use .NET/Xamarin or Java to build cross-platform compatible binaries, though. But you guys are out of luck because ideally, you have to have all 3 to ensure your software is performing as you want.</p>
<p>Mobile App Developers are also out of luck. You can do Android on Windows or Linux, but you have to have a Mac to develop for the Apple App Store. You can&rsquo;t escape that, and good luck waiting for Apple to release their tools for other platforms.</p>
<p>Creative Professionals can choose either Windows or OS X. That depends on the tooling. If you&rsquo;re an Adobe guy, either will do just fine, but you will probably want a custom workstation. Until Apple remodels the ridiculous Mac Pro, you won&rsquo;t find the best hardware for the buck at Apple. The current iMac 5K is good, but higher than that is not available. For example, if you need the power of 2 NVIDIA Titan or Quadro cards running in parallel, Apple can&rsquo;t help you there.</p>
<p>If you do require a Final Cut Pro, Logic Pro, Motion 5 workflow, the best option you will get is the iMac 5K.</p>
<p>I am not a full-time developer anymore. And I mix my time running a company, doing presentations, experimenting with tech and doing research. My ideal setup is actually a MacBook Pro. But I am forcing myself to live out of the Apple ecosystems, and it hurts. My web developer side can stay in Linux (Manjaro GNOME is my favorite nowadays) and be happy. But when my Creative side needs tooling, Gimp, Inkscape, Kdenlive, are just not good enough. I am too used to Photoshop, Illustrator, Final Cut/Premiere, Motion 5/After Effects. LibreOffice Impress is terrible to do complex presentations, and  those HTML/JS based slides software are good just for simple stuff. Try to do serious graphics and animation there and you will suffer in hell.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/a59U6kRJHLg"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>If you&rsquo;re a Creative, you <strong>need</strong> Color Calibrated screens (Adobe RGB space), you need the raw processing power to do a quick render and advanced post-processing. Adobe video editors will prefer Windows. Final Cut video editors are well served on Macs, even though the new MacBook Pro&rsquo;s cooling system is terrible:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/6TWbXV5xeYE"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>And if you&rsquo;re a full-time video editor, you can afford either high-end PC or Mac configurations, so there is not much dispute here.</p>
<p>If you&rsquo;re a video editor on a budget, you will <strong>not</strong> have a good 4K editing workflow on Linux, period. There is poor support to unlock the full potential of modern GPUs, no good Cuda or OpenCL support, and most video editing tools don&rsquo;t use them. Blender - which is primarily a 3D modeling and animation tool - is considered one of the best video editing tools available on Linux, which says a lot. The sad state of GPU support on Linux compromises Creative usage. The only other non-linear video editing tool that can achieve professional level workflows is <a href="https://www.linux.com/learn/pro-level-video-editing-lightworks-linux"target="_blank" rel="noopener">LightWorks</a> and it&rsquo;s not open source, requires the proprietary NVIDIA binaries, and <a href="https://www.lwks.com/index.php?option=com_shop&amp;view=shop&amp;Itemid=205"target="_blank" rel="noopener">will cost</a> you.</p>
<p>If you&rsquo;re going that way, it&rsquo;s probably better to stick with either Windows or OS X at this point.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I digressed quite a bit, but my conclusion is simple:</p>
<ul>
<li>WSL is not there yet. We have to wait another couple upgrades. When you hear that they improved the filesystem abstraction by a few orders of magnitude, then we can try again. And keep in mind that there are tons of <a href="https://github.com/Microsoft/BashOnWindows/issues"target="_blank" rel="noopener">open issues</a>, so your mileage will vary.</li>
<li>If you&rsquo;re a Web Developer, you&rsquo;re safe on any Linux distro.</li>
<li>If you&rsquo;re a mixed professional in need of both development toolkits and creative tools, use a Mac or Windows 10 with a fully virtualized Linux distro.</li>
<li>If you&rsquo;re a Gamer, Windows.</li>
<li>If you&rsquo;re a full-time creative professional, either Macs or PCs will do just fine.</li>
</ul>
<p>For me, I will stick with my Dell XPS running Windows 10 and Manjaro GNOME over Virtualbox. Virtualbox is fast enough that it won&rsquo;t hurt my workflow. And the creative tools can appreciate the access to my NVIDIA Cuda cores without any problem, so I can render with Blender and make Premiere render 4K video quickly enough.</p>
]]></content:encoded><category>linux</category><category>microsoft</category><category>performance</category></item><item><title>Ex Manga Downloadr - Part 7: Properly dealing with large Collections</title><link>https://akitaonrails.github.io/en/2017/06/16/ex-manga-downloadr-part-7-properly-dealing-with-large-collections/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/06/16/ex-manga-downloadr-part-7-properly-dealing-with-large-collections/</guid><pubDate>Fri, 16 Jun 2017 15:10:00 -0300</pubDate><description>&lt;p&gt;In my &lt;a href="http://www.akitaonrails.com/en/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener"&gt;previous post&lt;/a&gt; I was able to simplify a lot of the original code through the use of &lt;a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener"&gt;Flow&lt;/a&gt;. But the downside is that the running time actually increased a lot.&lt;/p&gt;
&lt;p&gt;José Valim kindly stepped in and posted a &lt;a href="http://www.akitaonrails.com/en/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener"&gt;valuable comment&lt;/a&gt;, which I will paste here:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Have you tried reducing the &lt;code&gt;@max_demand&lt;/code&gt; instead? &lt;code&gt;@max_demand&lt;/code&gt; is how much data you exchange between stages. If you set it to 60, it means you are sending 60 items to one stage, 60 items to the other and so on. That gives you poor balancing for small collections as there is a chance all items end-up in the same stage. You actually want to reduce &lt;code&gt;max_demand&lt;/code&gt; to 1 or 2 so each stage gets small batches and request more than needed. Another parameter you usually tune is the &lt;code&gt;stages: ...&lt;/code&gt; option, you should set that to the amount of connections you had in poolboy in the past.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In my <a href="http://www.akitaonrails.com/en/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow"target="_blank" rel="noopener">previous post</a> I was able to simplify a lot of the original code through the use of <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a>. But the downside is that the running time actually increased a lot.</p>
<p>José Valim kindly stepped in and posted a <a href="http://www.akitaonrails.com/en/2017/06/13/ex-manga-downloadr-parte-6-a-ascensao-do-flow#comment-3360301947"target="_blank" rel="noopener">valuable comment</a>, which I will paste here:</p>
<blockquote>
  <p>Have you tried reducing the <code>@max_demand</code> instead? <code>@max_demand</code> is how much data you exchange between stages. If you set it to 60, it means you are sending 60 items to one stage, 60 items to the other and so on. That gives you poor balancing for small collections as there is a chance all items end-up in the same stage. You actually want to reduce <code>max_demand</code> to 1 or 2 so each stage gets small batches and request more than needed. Another parameter you usually tune is the <code>stages: ...</code> option, you should set that to the amount of connections you had in poolboy in the past.</p>

</blockquote>
<blockquote>
  <p>However, I believe you don&rsquo;t need to use Flow at all. Elixir v1.4 has something called <a href="https://hexdocs.pm/elixir/Task.html#async_stream/5"target="_blank" rel="noopener"><code>Task.async_stream</code></a> which is a mixture of poolboy + task async, which is a definitely better replacement to what you had. We have added it to Elixir after implementing Flow as we realized you can get a lot of mileage out of <code>Task.async_stream</code> without needing to jump to a full solution like Flow. If using <code>Task.async_stream</code>, the <code>max_concurrency</code> option controls your pool size.</p>

</blockquote>
<p>And, obviously, he is right. I misunderstood how Flow works. It&rsquo;s meant to be used when you have a bazillion items to process in parallel. Particularly processing units that can have high variance and, hence, a lot of back-pressure not only because there is a lot of items to process, but because their running times can vary dramatically. So, it&rsquo;s one of those cases of bringing a cannon to kill a fly.</p>
<p>What I wasn&rsquo;t aware is the existence of <code>Task.async_stream</code> and its companion <a href="https://hexdocs.pm/elixir/Task.Supervisor.html#async_stream/4"target="_blank" rel="noopener"><code>Task.Supervisor.async_stream</code></a> if I need to add more control.</p>
<p>Let&rsquo;s backtrack a bit.</p>
<h3>Dealing with collections in Elixir<span class="hx:absolute hx:-mt-20" id="dealing-with-collections-in-elixir"></span>
    <a href="#dealing-with-collections-in-elixir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Erlang is a beast. It provides all the building blocks of a real-time, highly-concurrent, operating system! Really, what it provides out of the box is way more than any other language/platform/virtual machine, ever. You don&rsquo;t get that much for free on Java, or .NET or anything. You have to assemble the pieces manually, spend hundreds of hours tweaking, and still pray a lot to have everything working seamlessly.</p>
<p>So, you have distributed systems to build? There is no other option, really. Do Erlang, or suffer in hell.</p>
<p>Then, Elixir steps this up a notch creating a very reasonable and simple to use standard library that makes the coding part actually enjoyable. This is a killer combo. You need to do the next Whatsapp? You need to do the next Waze? You need to rebuild Cassandra from scratch? You need to create stuff like Apache Spark? Do Elixir.</p>
<p>In Erlang, you need to solve everything using GenServer. It&rsquo;s a neat abstraction from OTP. You are <a href="http://www.akitaonrails.com/en/en/2015/11/22/observando-processos-em-elixir-the-little-elixir-otp-guidebook/"target="_blank" rel="noopener">required to understand OTP</a> intimately. There is no shortcut here. There is no Erlang without OTP.</p>
<p>That said, you can start simple and scale without so much hassle.</p>
<p>Usually, everything starts with Collections, or more correctly, some kind of <a href="https://hexdocs.pm/elixir/Enum.html#content"target="_blank" rel="noopener">Enumeration</a>.</p>
<p>Just like my simple <code>Workflow.pages/1</code> function which iterates through a list of chapter links, fetch each link, parse the returning HTML and extracts the collection of page links within that chapter, reducing the sub-lists into a full list of page links.</p>
<p>If I know the collection is small (less than 100 items, for example) I would just do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And that&rsquo;s it. This is linear. It will sequentially process just one link at a time. The more chapter links, the longer it will take. Usually I want to process this in parallel. But I can&rsquo;t fire a parallel process for each chapter link, because if I receive 1,000 chapter links and fire them all, it will be a Denial of Service and I will certainly receive hundreds of time outs.</p>
<p>You can run into 2 main problems when you need to iterate through a big collection.</p>
<ul>
<li>
<p>If your collection is humongous (imagine a gigabyte long text file that you need to iterate line by line). For that you use <a href="https://hexdocs.pm/elixir/Stream.html#content"target="_blank" rel="noopener"><code>Stream</code></a> instead of <code>Enum</code>. All functions look almost exactly the same, but you will not have to load the entire collection into memory and you will not keep duplicating it.</p>
</li>
<li>
<p>If your processing unit takes a long time. Now that you solved not blowing off your memory usage, what if you have slow jobs while iterating through each item in the collection? That&rsquo;s our case, where the collection is rather small, but the processing time is long as it&rsquo;s fetching from an external source on the internet. It can take milisseconds, it can take a few seconds.</p>
</li>
</ul>
<p>One way to control this is through the use of &ldquo;batches&rdquo;, something along these lines:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">chunk</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pages_download</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">	<span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">concat</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages_download</span><span class="p">(</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="n">results</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">end</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="n">results</span>	
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is just for the example, I have not compiled this snippet to see if it works, but you get the idea of chunking the big list and processing each chunk through <code>Task.async</code> and <code>Task.await</code>.</p>
<p>Again, for small lists, this should be ok (thousands) and where each item does not take too much to process.</p>
<p>Now, this is not very good. Because each chunk must finish before the next chunk begins. Which is why the ideal solution is to keep a constant amount of jobs running at any given time. To that end, we need a Pool, which is what I explained in <a href="http://www.akitaonrails.com/en/en/2015/11/19/ex-manga-downloadr-parte-2-poolboy-ao-resgate/"target="_blank" rel="noopener">Part 2: Poolboy to the rescue!</a>.</p>
<p>But implementing the proper way to keep the pool entirely filled requires some boring juggling between Poolboy transactions and <code>Task.Supervisor.async</code>. Which is why I was interested in the new <code>Flow</code> usage.</p>
<p>The code does come clean, but as I explained before, this is not the proper use case for Flow. It&rsquo;s better when you have to iterate over tens of thousands of items or even an infinite stream (you have incoming traffic of requests in need of parallel processing, for example).</p>
<p>So, finally, there is a compromise. The solution between the simple <code>Task.async</code> and <code>Flow</code> is <code>Task.async_stream</code> which works like a pool implementation, where it keeps a <code>max_concurrency</code> of jobs running in a stream. The final code becomes way more elegant like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Task</span><span class="o">.</span><span class="n">async_stream</span><span class="p">(</span><span class="no">MangaWrapper</span><span class="p">,</span> <span class="ss">:chapter_page</span><span class="p">,</span> <span class="o">[</span><span class="n">source</span><span class="o">]</span><span class="p">,</span> <span class="ss">max_concurrency</span><span class="p">:</span> <span class="vi">@max_demand</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">to_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">}},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And this is the <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/517183261e998ab40f6e5bc793b4db9adcf899e3"target="_blank" rel="noopener">final commit</a> with the aforementioned changes.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The implementation with <code>Task.async_stream</code> is super simple and the times finally became the same as before.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>84,16s user 20,80s system 138% cpu 1:15,94 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Way better than the more than 3 minutes it was taking with Flow. And this is not because Flow is slow, it&rsquo;s because I was not using it correctly, probably shooting a big chunk into a single GenStage and creating a bottleneck. Again, only use Flow if you have enough items to put hundreds of them into several parallel GenStages. We are talking about collections with tens of thousands of items, not my meager pages list.</p>
<p>There is a small tweak though. To fetch all chapter and page links I am using a <code>max_concurrency</code> of 100. Timeout is the default at 5000 (5 seconds). That works because the returning HTML is not so big and we can parallelize that much on a high bandwidth connection.</p>
<p>But the images downloading procedure at <code>Workflow.process_downloads</code> I had to cut <code>max_concurrency</code> in half and increase the <code>timeout</code> up to 30 seconds to make sure it wouldn&rsquo;t crash.</p>
<p>Because this is a simple implementation there is no crash recovery and no retry routine. I would have to replace this implementation with <code>Task.Supervisor.async_stream</code> to regain some control. My original implementation was more cumbersome but I had places to add retry mechanisms. So again, it&rsquo;s a compromise between ease of use and control, always. The more control you have, the worse the code becomes.</p>
<p>This is a simple example exercise, so I will keep it at that for the time being.</p>
]]></content:encoded><category>elixir-and-erlang</category><category>performance</category></item><item><title>Ex Manga Downloadr - Part 6: The Rise of FLOW</title><link>https://akitaonrails.github.io/en/2017/06/13/ex-manga-downloadr-part-6-the-rise-of-flow/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/06/13/ex-manga-downloadr-part-6-the-rise-of-flow/</guid><pubDate>Tue, 13 Jun 2017 15:59:00 -0300</pubDate><description>&lt;p&gt;It&amp;rsquo;s been way more than a year since I posted about my pet project &lt;a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener"&gt;Ex Manga Downloadr&lt;/a&gt;. Since then I just did small updates to keep up with the current Elixir and libraries versions.&lt;/p&gt;
&lt;p&gt;As a quick recap, the exercise is that I want to scrape MangaReader.net for a bunch of images, organized in pages and chapters, and in the end it should compile organized PDFs so I can load them on a Kindle device.&lt;/p&gt;</description><content:encoded><![CDATA[<p>It&rsquo;s been way more than a year since I posted about my pet project <a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Ex Manga Downloadr</a>. Since then I just did small updates to keep up with the current Elixir and libraries versions.</p>
<p>As a quick recap, the exercise is that I want to scrape MangaReader.net for a bunch of images, organized in pages and chapters, and in the end it should compile organized PDFs so I can load them on a Kindle device.</p>
<p>Web scraping is a simple loop of HTTP GETs over a ton of URLs, scraping the HTML, and fetching more URLs to download.</p>
<p>In many simple languages, people usually solve this naively in 2 ways:</p>
<ul>
<li>A simple nested loop. One single thread, sequential fetches. So if you have 5,000 links and each links takes 10 seconds to fetch, it&rsquo;s basically 10 * 5,000 = 50,000 seconds, which is a stupid long time.</li>
<li>A simple spawn of processes, fibers, threads or parallel I/O in a reactor, all at once. An attempt to parallelize all fetches at once.</li>
</ul>
<p>Everybody probably agrees that the first option is stupid. Now, the second one is tricky.</p>
<p>The tricky part is <strong>CONTROL</strong>.</p>
<p>Anyone in Go would say <em>&ldquo;oh, this is easy, just put a loop and spawn a bunch of goroutines&rdquo;</em> or anyone in Node.js would say <em>&ldquo;oh, this is easy, just put a loop, make the fetch - they all will run asynchronously - and add callbacks, a basic async/await.&rdquo;</em></p>
<p>They&rsquo;re not wrong, but this is still too naive an implementation. It&rsquo;s trivial to trigger hundreds or thousands of parallel requests. Now, what happens if one fails and you have to retry? What happens if the MangaReader has a throttling system that will either start cutting down connections or timing them out? Or if your internet bandwidth is just not enough, and after a certain amount of requests you start having diminishing returns and time outs?</p>
<p>The message is: it&rsquo;s damn trivial to spawn parallel stuff. it&rsquo;s damn complicated to control parallel stuff.</p>
<p>This is why, in my first implementation in Elixir, I introduced a complicated implementation using a combination of a custom GenServer, Elixir&rsquo;s own Task infrastructure for async/await pattern, and <a href="https://github.com/devinus/poolboy"target="_blank" rel="noopener">Poolboy</a> to control the rate of the parallelism. This is how you control the bottleneck out to slow resources: using pools and queues (which is why every good database has a connection pool, remember <a href="https://sourceforge.net/projects/c3p0/"target="_blank" rel="noopener">C3P0</a>?)</p>
<p>This is one snippet of my older implementation:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">chapter_page</span><span class="p">(</span><span class="o">[</span><span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="no">Task</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="no">Fetcher</span><span class="o">.</span><span class="n">TaskSupervisor</span><span class="p">,</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:chapter_page</span><span class="p">,</span> <span class="n">chapter_link</span><span class="p">,</span> <span class="n">source</span><span class="p">},</span> <span class="vi">@genserver_call_timeout</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">,</span> <span class="vi">@task_async_timeout</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Yes, it&rsquo;s very ugly, and there are boilerplates for the GenServer, the custom Supervisor to initialize Poolboy and so on. And the higher level workflow code looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, inside the <code>Worker</code> module each public method wraps the GenServer internal calls into a <code>Task async</code> and in the collection iteration we add <code>Task.await</code> to actually wait for all parallel calls to finish, so we can finally reduce the results.</p>
<p>Elixir now comes with this very interesting <a href="https://elixir-lang.org/blog/2016/07/14/announcing-genstage/"target="_blank" rel="noopener"><code>GenStage</code></a> infrastructure that promises to replace <code>GenEvent</code> and the use case is when you have a producer-consumer situation with back-pressure. Basically when you have slow endpoints and you would end up having to control bottlenecks.</p>
<p>Then, <a href="https://github.com/elixir-lang/flow"target="_blank" rel="noopener">Flow</a> is an easier high abstraction that you can use almost the same way you would use <code>Enum</code> in your collections, but instead of sequential mapping, it takes care of parallel traversing and control of batches. So the code is very similar but without you having to control the parallelization controls manually.</p>
<p>This is the <a href="https://github.com/akitaonrails/ex_manga_downloadr/commit/b117f5236098f6d37e332633acb787be46a09d84"target="_blank" rel="noopener">full commit</a> where I could remove Poolboy, remove my custom GenServer, reimplement the Worker as simple module of functions and then the workflow could get rid off the async/await pattern and use Flow instead:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">pages</span><span class="p">({</span><span class="n">chapter_list</span><span class="p">,</span> <span class="n">source</span><span class="p">})</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">   <span class="n">pages_list</span> <span class="o">=</span> <span class="n">chapter_list</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">from_enumerable</span><span class="p">(</span><span class="ss">max_demand</span><span class="p">:</span> <span class="vi">@max_demand</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">MangaWrapper</span><span class="o">.</span><span class="n">chapter_page</span><span class="p">(</span><span class="o">[&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="n">source</span><span class="o">]</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">partition</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Flow</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="o">[]</span> <span class="k">end</span><span class="p">,</span> <span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">list</span><span class="p">},</span> <span class="n">acc</span> <span class="o">-&gt;</span> <span class="n">acc</span> <span class="o">++</span> <span class="n">list</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">to_list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span><span class="n">pages_list</span><span class="p">,</span> <span class="n">source</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The only boilerplate left is the <code>Flow.from_enumerable()</code> and <code>Flow.partition()</code> wrapping the <code>Flow.map</code>, and that&rsquo;s it!</p>
<p>Notice I configured <code>@max_demand</code> to be 60. You must tweak it to be larger or smaller depending on your internet connection, you have to experiment it. By default, Flow will trigger 500 processes in parallel, which is way too much for web scraping on a normal home internet connection and you will suffer diminishing returns. That&rsquo;s what I had to do previously with Poolboy, by initiating a pool of around 60 transactions at most.</p>
<p>Unfortunately not everything is as straight forward as it seems. Running this new version on the test mode I get this result:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>58,85s user 13,93s system 37% cpu 3:13,78 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So a total time of more than 3 minutes, using around 37% of the available CPU.</p>
<p>My immediate previous version using all the shenanigans of Poolboy, Task.Supervisor, GenServer, etc still gives me this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>100,67s user 20,83s system 152% cpu 1:19,92 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Less than <strong>HALF</strong> the time, albeit using all my CPU cores. So my custom implementation still uses my resources to the maximum. There is still something in the Flow implementation I didn&rsquo;t quite get right. I already tried to bump up the <code>max_demand</code> from 60 up to 100 but that didn&rsquo;t improve anything. Leaving it to the default 500 slows everything down to more than 7 minutes.</p>
<p>All in all, at least it makes the implementation far easier on the eyes (hence, way easier to maintain), but either the Flow implementation has bottlenecks or I am using it wrong at this point. If you know what it is, let me know in the comments section below.</p>
]]></content:encoded><category>elixir-and-erlang</category><category>performance</category><category>distributed-systems</category></item><item><title>Optimizing Linux for Slow Computers</title><link>https://akitaonrails.github.io/en/2017/01/17/optimizing-linux-for-slow-computers/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2017/01/17/optimizing-linux-for-slow-computers/</guid><pubDate>Tue, 17 Jan 2017 18:15:00 -0200</pubDate><description>&lt;p&gt;I&amp;rsquo;ve been researching a lot about Linux on the Desktop these days as you may see from my posts about &lt;a href="http://www.akitaonrails.com/en/2017/01/06/customizando-o-fedora-25-para-desenvolvedores"target="_blank" rel="noopener"&gt;Fedora 25&lt;/a&gt; and &lt;a href="http://www.akitaonrails.com/en/2017/01/10/arch-linux-a-melhor-distro-de-todas"target="_blank" rel="noopener"&gt;Arch Linux&lt;/a&gt; (recommended!).&lt;/p&gt;
&lt;p&gt;There are some things you must have in mind when migrating to Linux systems.&lt;/p&gt;
&lt;p&gt;Even if you have a top of the line Intel Core i7 Kaby Lake, 32GB of RAM, 2TB M.2 SSDs, you may still benefit from the optimizations I will talk about.&lt;/p&gt;
&lt;p&gt;One of the best resources is &lt;a href="https://wiki.archlinux.org/index.php/Improving_performance"target="_blank" rel="noopener"&gt;Arch Linux&amp;rsquo;s Wiki page on &amp;ldquo;Improving Performance&amp;rdquo;&lt;/a&gt;. You don&amp;rsquo;t need everything there but it&amp;rsquo;s a comprehensive resource that any enthusiast must read.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve been researching a lot about Linux on the Desktop these days as you may see from my posts about <a href="http://www.akitaonrails.com/en/2017/01/06/customizando-o-fedora-25-para-desenvolvedores"target="_blank" rel="noopener">Fedora 25</a> and <a href="http://www.akitaonrails.com/en/2017/01/10/arch-linux-a-melhor-distro-de-todas"target="_blank" rel="noopener">Arch Linux</a> (recommended!).</p>
<p>There are some things you must have in mind when migrating to Linux systems.</p>
<p>Even if you have a top of the line Intel Core i7 Kaby Lake, 32GB of RAM, 2TB M.2 SSDs, you may still benefit from the optimizations I will talk about.</p>
<p>One of the best resources is <a href="https://wiki.archlinux.org/index.php/Improving_performance"target="_blank" rel="noopener">Arch Linux&rsquo;s Wiki page on &ldquo;Improving Performance&rdquo;</a>. You don&rsquo;t need everything there but it&rsquo;s a comprehensive resource that any enthusiast must read.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/607/Screenshot_from_2017-01-17_16-55-46.png" alt="htop and iotop"  loading="lazy" /></p>
<h3>It&rsquo;s about Responsiveness, not performance!<span class="hx:absolute hx:-mt-20" id="its-about-responsiveness-not-performance"></span>
    <a href="#its-about-responsiveness-not-performance" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Most people&rsquo;s concerns are first about performance, and this is a mistake. Linux is plenty fast, but for many people it doesn&rsquo;t feel that way on the Desktop.</p>
<p>When tuning a server, you&rsquo;ll really want to tweak for performance and high throughput. That&rsquo;s where most Linux configurations really shine over the competition: they come better tuned to get the most out of server configurations.</p>
<p>But in a Desktop you don&rsquo;t want that. For example, you&rsquo;re copying a 20GB file to your old USB thumb drive, or you&rsquo;re unzipping a large file, or you&rsquo;re compiling that large package from source, or you leave Dropbox in the background syncing gigabytes of files from their servers. Or you&rsquo;re doing &ldquo;nothing&rdquo; (in the foreground at least, but Gnome Tracker is heavily indexing your files in the background) and your environment stutters, hangs for a few seconds, and keeps doing that every so often.</p>
<p>And you&rsquo;re left wondering why Linux is so bad compared to Windows or macOS where you don&rsquo;t see the same behavior on similar hardware.</p>
<blockquote>
  <p>There is a term that is still misunderstood: <strong>real-time</strong>.</p>

</blockquote>
<p>Being real-time does not mean &ldquo;computing super fast&rdquo;, it means &ldquo;being predictable&rdquo;. If something needs to happen in a certain frequency, it doesn&rsquo;t matter if each cycle takes 1 second as long as it consistently takes the same 1 second - in all deadlines. If you have a &ldquo;fast&rdquo; machine that computes faster at 10 milliseconds every cycle, but every so often, randomly hangs for a couple of seconds, this is not real-time. And for media creation, it&rsquo;s a disaster.</p>
<p>There is hard real-time where one peak or valley can be considered a catastrophic failure, and soft real-time where you can handle a few peaks, but not so much. Hard real time requirements are rare, unless you&rsquo;re developing systems for nuclear plants you may miss a few deadlines.</p>
<p>Most responsiveness issues are related to soft real-time situations. You can handle a few sparse peaks here and there, but no more than a few. And this is how you should do your research: not Googling for &ldquo;linux performance&rdquo; but for &ldquo;linux real-time&rdquo; or &ldquo;linux responsiveness&rdquo;.</p>
<p>You will also find that there are niche professionals with special distros just for audio recording and editing, for example <a href="http://libremusicproduction.com/articles/advantages-choosing-audio-orientated-linux-distribution"target="_blank" rel="noopener">AVLinux or KXStudio</a>.</p>
<p>macOS is particularly good for media creators precisely because it&rsquo;s highly tuned for soft real-time, which is critical for software such as Logic Pro. And for the same reason, it&rsquo;s a poor server OS. You will notice that the default Quicktime screen recording is super smooth, you rarely see stutters.</p>
<p>But you don&rsquo;t need to use an audio specific distro or a hard real-time kernel. Critical audio distros don&rsquo;t use PulseAudio, but normal users will not be so concerned about it. We can tune it to find a good balance between responsiveness and performance. If you really want to go hard-core, you may want to read the <a href="http://wiki.linuxaudio.org/wiki/real_time_info"target="_blank" rel="noopener">Linux Audio Wiki on Real Time</a>, but it&rsquo;s out of the scope of this article.</p>
<h3>What are the real Bottlenecks?<span class="hx:absolute hx:-mt-20" id="what-are-the-real-bottlenecks"></span>
    <a href="#what-are-the-real-bottlenecks" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A &ldquo;slow&rdquo; computer is not necessarily sporting old CPUs. I am doing my tests on a very old Lenovo Thinkcentre Edge 71z tower desktop. It has an old 2nd generation Intel Core 2.4Ghz 4 cores SandyBridge CPU. We just saw the release of the 7th generation Kaby Lake processors, so one might assume that nothing would run on such an old CPU, but you would be wrong.</p>
<p>CPU is usually not a serious bottleneck unless you&rsquo;re doing really intensive computation, such as video compression, data sciences, genetics, neural networks, etc.</p>
<p>For a casual user or even a heavy-weight developer, any processor better than the 1st generation Intel Core series is plenty.</p>
<p>GPU is also rarely a bottleneck unless you&rsquo;re doing heavy gaming or 4K renderings. Most of the time you don&rsquo;t really need a USD 7000 dedicated GTX 1080.</p>
<p>By the way, this is not necessary for most systems, but just to be on the safe side do this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -S mesa-demos
glxinfo | grep direct</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>You should see <code>direct rendering: Yes</code>. If not, refer to your distro documentation, because this means you&rsquo;re not compositing through the GPU and you&rsquo;re wasting CPU cycles rendering your screen!</p>
<p>If you try to measure your CPU and GPU usages, you will realize that most of the time they are actually idle! That&rsquo;s right, you&rsquo;re mostly underusing your machine cores.</p>
<p>The bottleneck usually boils down to I/O.</p>
<h3>RAM vs SWAP<span class="hx:absolute hx:-mt-20" id="ram-vs-swap"></span>
    <a href="#ram-vs-swap" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now, you&rsquo;re opening your shiny Chromium browser. Anyone fooling around for a few minutes will open an average of a dozen or more tabs, without breaking a sweat.</p>
<p>It&rsquo;s super easy to eat up all 8GB of the average machine. Whenever that happens, the OS will have to start offloading data to disk, which is orders of magnitude slower.</p>
<p>If application data is offloaded to disk and you alt-tab to it later, the OS will reach a &ldquo;page fault&rdquo;, and it will have to load from disk, from the swap file/partition. And again, this will have the effect of blocking your actions. The environment may stutter for a second or more, making it <strong>unresponsive</strong>.</p>
<p>The very first thing you may want to do is install an extension such as <a href="https://chrome.google.com/webstore/detail/the-great-suspender/klbibkeccnjlkjkiokjodocebajanakg?hl=en"target="_blank" rel="noopener">The Great Suspender</a>. It will simply close all tabs but the one you&rsquo;re reading right now. When you change to another tab it will reload it. The effect is that you&rsquo;re not using RAM if you really don&rsquo;t need to.</p>
<p><img src="https://lh6.googleusercontent.com/PCpWlL8C4bi0yPT1zvOmRwZFd1BaweIiwSw9hmJoUZ4BDA9InMR_fEaC4XNrFTyWW2m_yC8HIw=s640-h400-e365" alt="The Great Suspender"  loading="lazy" /></p>
<p>This extension alone can save you a couple of GIGABYTES of RAM, which is no small thing if you have 8GB or less.</p>
<p>The other thing to consider is that Linux comes pre-configured to balance out offloading application data to swap to accomodate filesystem cache. So, if you&rsquo;re unzipping a large file, some of that data will go to RAM cache and application data will move to the disk. After you finish unzipping, you alt-tab to applications and boom: page faults, unresponsiveness.</p>
<p>So you want to configure the OS to more aggressively keep your application state in RAM, and <a href="https://rudd-o.com/linux-and-free-software/tales-from-responsivenessland-why-linux-feels-slow-and-how-to-fix-that"target="_blank" rel="noopener">this is how you do it</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo tee -a /etc/sysctl.d/99-sysctl.conf &lt;&lt;-EOF
vm.swappiness=1
vm.vfs_cache_pressure=50
EOF</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>While on the topic of storage, you will find some older kernels making your machine become unresponsive when dealing with <a href="http://unix.stackexchange.com/questions/107703/why-is-my-pc-freezing-while-im-copying-a-file-to-a-pendrive/107722#107722"target="_blank" rel="noopener">slower storage</a>, such as USB drives or SD cards. This is how you tweak it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo tee -a /etc/sysctl.d/99-sysctl.conf &lt;&lt;-EOF
vm.dirty_background_bytes=16777216
vm.dirty_bytes=50331648
EOF</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If you don&rsquo;t want to reboot right now, you can run this in a Terminal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo sysctl -w vm.swappiness=1
sudo sysctl -w vm.vfs_cache_pressure=50
sudo sysctl -w vm.dirty_background_bytes=16777216 
sudo sysctl -w vm.dirty_bytes=50331648</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Don&rsquo;t go too far on tuning, for example, never disable a file system journaling. It increases performance at the risk of putting your data at risk of corruption.</p>
<h3>Schedulers<span class="hx:absolute hx:-mt-20" id="schedulers"></span>
    <a href="#schedulers" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Why was &ldquo;Linux not ready for the Desktop&rdquo; years ago?</p>
<p>Because it took too many years to finally start tackling low latency, inexpensive thread switching, better scheduling. You have to thank <a href="https://en.wikipedia.org/wiki/Con_Kolivas"target="_blank" rel="noopener">Con Kolivas</a>, <a href="https://en.wikipedia.org/wiki/Ingo_Moln%C3%A1r"target="_blank" rel="noopener">Ingo Molnár, Thomas Gleixner</a>. The Linux kernel development is known to be super difficult to deal with and Con Kolivas is one of its victims, but his work lives on to allow us to have better Desktop experiences these days.</p>
<p>There are Process Schedulers and I/O Schedulers. The first is responsible to manage the <a href="https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO"target="_blank" rel="noopener">Preemption</a> of the kernel, how it switches between different computational tasks, the low-level equivalent of you &ldquo;alt-tabbing&rdquo; through apps, so to speak.</p>
<p>I/O Schedulers deal with sharing slow I/O resources with competing processes needing to read from disk, write to RAM, etc.</p>
<p>The recent history of process scheduler for the Desktop boils down to Con Kolivas&rsquo; works on fair scheduling, leading to Ingo Molnár&rsquo;s Completely Fair Scheduler (CFS) which is the default in most distros nowadays and the continuing work of Kolivas on Staircase, Rotating Staircase Deadline, Staircase Deadline, Brain Fuck Scheduler (BFS), and the most recent Multiple Queue Skiplist Scheduler (MuQSS).</p>
<p>Then there are I/O Schedulers. For the most part you will be dealing with the Completely Fair Queueing (CFQ). Most development on this side is attributed to Jens Axboe, also responsible for the Deadline Scheduler and Noop Scheduler. Then there is the controversial evolution called <a href="http://algo.ing.unimo.it/people/paolo/disk_sched/bfq-history.php"target="_blank" rel="noopener">Budget Fair Queueing</a> (BFQ).</p>
<p>When you have SSDs (and this is why you want SSDs), you will more likely choose NOOP (or <a href="https://wiki.debian.org/SSDOptimization#Low-Latency_IO-Scheduler"target="_blank" rel="noopener">Deadline</a>) just because there is no need to waste computation time managing complex I/O queues for SSDs as they can easily handle up to tens of thousands of I/O operations concurrently without breaking a sweat.</p>
<p>But if you have to use mechanical hard-drives, particularly the old and super slow 5.400rpm ones, you will want to manage the I/O queue efficiently, touching the spinning plates as little as possible. And in this case, you will really want to use something like BFQ (or at least leave it at the default CFQ).</p>
<p>You can check which I/O Scheduler you&rsquo;re running like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ cat /sys/block/sda/queue/scheduler
noop deadline cfq [bfq] </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In the example above you will see that <code>[bfq]</code> is the one active, but you can change it on the fly to test it out if you want.</p>
<p>To take advantage of those newest schedulers to better optimize slow computers, your best bet is to install Linux Zen kernel, a version of <a href="https://liquorix.net/"target="_blank" rel="noopener">Liquorix</a>. It includes the MuQSS scheduler instead of CFS and BFQ instead of CFQ, while also adding more tweaks for responsiveness like proper QoS over TCP to avoid TCP congestion.</p>
<p>In Arch Linux it&rsquo;s a simple thing to do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacman -Sy linux-zen
sudo grub-mkconfig -o /boot/grub/grub.cfg</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>For Ubuntu, you may want to refer to <a href="https://liquorix.net/#install"target="_blank" rel="noopener">Liquorix&rsquo;s Install Page</a> as it depends on your CPU, but most likely you will install on 64-bit machines:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo apt-get install liquorix-keyring
apt-get install linux-image-liquorix-amd64 linux-headers-liquorix-amd64</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Is GNOME 3 too slow?<span class="hx:absolute hx:-mt-20" id="is-gnome-3-too-slow"></span>
    <a href="#is-gnome-3-too-slow" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I always heard that GNOME and even KDE are too slow, you should just use XFCE (or LXQt, or MATE).</p>
<p>And it always striked me as one of those things people just keep repeating until it becomes the official canon.</p>
<p>As an engineer, I dislike thinking that way. Defying the canon is more like what an engineer should do.</p>
<p>GNOME 3.22 is an ever evolving environment and ecosystem. It&rsquo;s good looking by default, no need to tweak it too much. And it has several built-in conveniences such as GNOME Online Accounts, Tracker for indexing files and making them easily searchable, GNOME Photos that sync from Google Photos, and so on. Every nicety we like about a system like macOS.</p>
<p>Convenience is a trade-off of performance and responsiveness. So high-end machines will benefit from the convenience and old machines will suffer because of the extra &ldquo;bloat&rdquo; in the background.</p>
<p>How do you maintain some of the convenience on old hardware?</p>
<p>Again, you must understand what&rsquo;s going on. The first thing you must install is <strong>htop</strong> and <strong>iotop</strong>. The first is good to see what processes running in the background may be eating up your CPU or RAM. The second is good to see what processes are bloating your I/O queues doing background file/network operations.</p>
<p>What I found out in my system were 2 main offenders: Dropbox and Tracker.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/606/Screenshot_from_2017-01-17_16-15-32.png" alt="iotop"  loading="lazy" /></p>
<p>Dropbox is optional, but most people nowadays use it. Out of the box it is a hideous monster, one of the worst pieces of software you&rsquo;re forced to live with.</p>
<p>The first time you install and it has to download everything, your machine will go down to its knees. Nothing to do about that, just remember to install it Friday night and leave it running in the office over the weekend.</p>
<p>Then, edit the <code>/usr/share/applications/dropbox.desktop</code> and replace the <code>Exec=dropbox</code> line with this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Exec=ionice -c 3 -n 7 dropbox start -i &amp;&amp; cpulimit -b -e dropbox -l 10</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This &ldquo;should&rdquo; tune down Dropbox to have the least amount of CPU time and only have I/O when the system is idle.</p>
<p>Another way is to install <a href="https://github.com/Nefelim4ag/Ananicy/blob/master/README.md"target="_blank" rel="noopener">Ananicy</a>. It is a daemon that promises to automatically set NICE and IOCLASS of selected processes just like using <code>ionice</code> and <code>cpulimit</code> above. You can install it in Arch like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>sudo pacaur -S ananicy-git</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And if you <code>cat /etc/ananicy.d/dropbox.rules</code> you will see a rule set like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Dropbox client: https://www.dropbox.com
NAME=dropbox       NICE=19     IOCLASS=idle</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Which is basically what we did in the <code>Exec</code> line tweak. I didn&rsquo;t test Ananicy enough but if it does what&rsquo;s promised, it&rsquo;s even easier as it comes with pre-configured rules for applications such as make, VLC, transmission, etc.</p>
<p>Then, there is <strong>Tracker</strong>. The purpose of this tool is to index your files so you can search them fast and easily through GNOME applications such as the Nautilus File Manager.</p>
<p>Again, the first time you install your new GNOME environment it will be very expensive to build the first index pass, especially if you&rsquo;re downloading tons of files from Dropbox. Do it on a Friday night!</p>
<p>But you should also tune it down to only run when your system is idle. Run Alt-F2 and type <code>tracker-preferences</code>, then configure it to look like this:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/605/Screenshot_from_2017-01-17_15-32-38.png" alt="tracker-preferences"  loading="lazy" /></p>
<p>In the same applet, also configure it to ignore <code>log</code> directories and <code>*.log</code> file patterns!</p>
<p>Only those 2 things should make your machine WAY more responsive when using slow mechanical drives. I noticed that <code>gnome-photos</code> keeps running in the background and consuming some I/O, it is probably trying to sync your online photos from Google if you set <a href="https://wiki.gnome.org/Projects/GnomeOnlineAccounts"target="_blank" rel="noopener">GNOME Online Accounts</a>.</p>
<blockquote>
  <p>Dropbox, Tracker, Gnome-Photos, will all cause your initial experience to suck. But if you have patience - and a fast internet connection - they should settle down after the initial heavy sync.</p>

</blockquote>
<p>GNOME has other services in the background, namely:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>gnome-session
gnome-shell
gnome-settings-daemon
gnome-online-accounts
evolution-data-server
gjs-console</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>There must be more depending on optional apps you installed. Gnome-Shell and GJS are easily the worst of the bunch. You can&rsquo;t do much about them because they&rsquo;re the Core of GNOME. GJS in particular enables Javascript-based extensions and everything Javascript is slow. The only thing you can do is avoid installing too many GNOME Extensions.</p>
<p>By the way, if you install XFCE alongside GNOME, you may be surprised that many of the same background services will start up in the background! Install one or the other, not both in the same system.</p>
<p>On the other hand, a bare-bone Arch install with the XFCE4 package set will start up consuming around 150MB of RAM!! But of course, you lose all the niceties that comes pre-installed with GNOME. And also of course: you start Chromium, open a few tabs, and there goes all the RAM anyway.</p>
<p>If your goal is just to save resources, the choice is not between XFCE or LXQt, it is between having Facebook permanently opened in a browser tab or not. The reality is that the main offender of RAM is the Web as a whole. Half a dozen tabs and you eat up more than 1 Gigabyte, and it keeps going up. That&rsquo;s why my main first recommendation is to install The Great Suspender.</p>
<p>You will run far worse apps in the foreground. For example, Spotify, Franz, Atom, to name a few. If it&rsquo;s a hybrid app that loads a browser to load a web application, it is going to be heavy.</p>
<p>And I&rsquo;d choose a <a href="https://wiki.archlinux.org/index.php/wayland#Window_managers_and_desktop_shells"target="_blank" rel="noopener">forward thinking desktop manager</a> that already supports Wayland/Weston instead of X11. You do want to throw away the <a href="http://www.phoronix.com/scan.php?page=article&amp;item=x_wayland_situation&amp;num=1"target="_blank" rel="noopener">bad X11 legacy</a> as soon as possible.</p>
<h3>Summary<span class="hx:absolute hx:-mt-20" id="summary"></span>
    <a href="#summary" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As a shorter TL;DR remember to do the following:</p>
<ul>
<li>Tune your swappiness and cache pressure settings to avoid page faults when using your foreground applications. It&rsquo;s a trade-off of performance vs responsiveness.</li>
<li>Install the Linux-Zen or Liquorix kernels (depending on your distro) to have access to the better MuQSS process scheduler and BFQ I/O schedulers. If you&rsquo;re using an SSD you will want to check if you&rsquo;re using NOOP or Deadline I/O schedulers instead. Also check for <a href="https://wiki.archlinux.org/index.php/Solid_State_Drives#TRIM"target="_blank" rel="noopener">TRIM support</a>.</li>
<li>Make Dropbox and Tracker play nice with your system. Configure both to only run when you are not using the system (when idle). Maybe install Ananicy and rejoice.</li>
<li>Do not choose a Desktop Manager because of performance concerns. If you&rsquo;re using Chromium or other web based applications, you&rsquo;re already doomed. So don&rsquo;t panic and use GNOME 3.22.</li>
</ul>
<p>When I installed both Fedora 25 and Arch Linux I felt them sluggish. When I used Ubuntu 14.04 for months in a better hardware, I also felt it sluggish compared to macOS in similar hardware, but I never attempted this level of tuning before.</p>
<p>The main reason being the heavy initial sync of applications such as Dropbox, Tracker, Gnome-Photos.</p>
<p>The second reason is the better tuning of I/O scheduling and Swap settings because of the use of a mechanical harddrive. If you use an SSD you probably don&rsquo;t suffer nearly as much.</p>
<p>Bottomline: if you can, buy a good SSD. If you have PCI Express x4, then do even better and buy a M.2 SSD such as the <a href="http://www.samsung.com/semiconductor/minisite/ssd/product/consumer/950pro.html"target="_blank" rel="noopener">Samsung 950 EVO M.2</a>. The best thing you can do is have more than 8GB of RAM (16GB being a good sweet spot) and a really fast SSD (preferably with a BUS that doesn&rsquo;t suffer from SATA 3&rsquo;s bottlenecks).</p>
<p>With all the tweaks and tunings, I am happy to report that my years old Lenovo tower is running as smoothly as it can, being responsive enough even in higher loads, with this shit slow mechanical hard drive. And as a bonus, if you choose to stay in GNOME 3, install the nice <a href="https://github.com/nana-4/Flat-Plat"target="_blank" rel="noopener">Flat Plat</a> theme, heavily inspired by Material Design, and the <a href="https://snwh.org/paper"target="_blank" rel="noopener">Paper</a> or <a href="https://snwh.org/moka"target="_blank" rel="noopener">Moka</a> icon sets.</p>
]]></content:encoded><category>linux</category><category>performance</category><category>hardware</category></item><item><title>Trying to match C-based Fast Blank with Crystal</title><link>https://akitaonrails.github.io/en/2016/07/06/trying-to-match-c-based-fast-blank-with-crystal/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/07/06/trying-to-match-c-based-fast-blank-with-crystal/</guid><pubDate>Wed, 06 Jul 2016 17:10:00 -0300</pubDate><description>&lt;p&gt;In my eyes, Crystal may become the ideal solution to make our beloved Ruby gems faster. Up until now we have been using C-based extensions to accelerate CPU-bound code in Ruby. Nokogiri, for example, is a wrapper to provide a nice API on top of libxml, which is a huge library in C.&lt;/p&gt;
&lt;p&gt;But there are many opportunities to accelerate Rails applications as well. For example, we just saw the release of the &lt;a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener"&gt;gem &amp;ldquo;faster_path&amp;rdquo;&lt;/a&gt;, this time written in Rust and bridged through FFI (Foreign Function Interface). The author&amp;rsquo;s claim is that Sprockets has to compute lots of paths and making this library, natively compiled and optimized with Rust, added a huge improvement in the asset pipeline task.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In my eyes, Crystal may become the ideal solution to make our beloved Ruby gems faster. Up until now we have been using C-based extensions to accelerate CPU-bound code in Ruby. Nokogiri, for example, is a wrapper to provide a nice API on top of libxml, which is a huge library in C.</p>
<p>But there are many opportunities to accelerate Rails applications as well. For example, we just saw the release of the <a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener">gem &ldquo;faster_path&rdquo;</a>, this time written in Rust and bridged through FFI (Foreign Function Interface). The author&rsquo;s claim is that Sprockets has to compute lots of paths and making this library, natively compiled and optimized with Rust, added a huge improvement in the asset pipeline task.</p>
<p>Sam Saffron, from Discourse, also built a very small gem called <a href="https://github.com/SamSaffron/fast_blank"target="_blank" rel="noopener">&ldquo;fast_blank&rdquo;</a> which is a tiny library written in C that reimplements ActiveSupport&rsquo;s <code>String#blank?</code> method to be up to 9x faster. Because Rails digests volumes of strings, checking if they are blank every time, this adds some performance (depends on your app, of course).</p>
<p>The Holy Grail to native-level performance is to be able to write close-to-Ruby code instead of having to hack low-level C or having the high learning curve of a language such as Rust. More than that, I&rsquo;d like to avoid having to use FFI. I am not an expert in FFI but I remember understanding that it adds overhead to make the bindings.</p>
<p>By the way, it&rsquo;s important to disclose right now: I am not a C expert by any means of the imagination, far from that. So I have very little experience dealing with hardcore C development. Which is again, why this possibility of writing in Crystal is even more appealing to me. So if you are a C expert and you spot something silly I am saying about it, please let me know in the comments section below.</p>
<p>My exercise is to rewrite the C-based Fast Blank gem in Crystal, add it to the same Gem to compile under Crystal if it&rsquo;s available or fall back to C, and make the specs pass so it&rsquo;s a seamless transition for the user.</p>
<p>To achieve that I had to:</p>
<ul>
<li>Extend the Gem&rsquo;s extconf.rb to generate different Makefiles (for C and Crystal) that are able to compile under OS X or Linux (Ubuntu at least) - OK</li>
<li>Make the specs pass in the Crystal version - Almost (it&rsquo;s ok for all intents and purposes but an edge case)</li>
<li>Make the performance be faster than Ruby and close to C - Not so much yet (under OS X the performance is quite good, but under Ubuntu it doesn&rsquo;t scale so well for large strings)</li>
</ul>
<p>You can check out the results so far on <a href="https://github.com/akitaonrails/fast_blank/tree/crystal_version"target="_blank" rel="noopener">my fork over Github</a> and follow the <a href="https://github.com/SamSaffron/fast_blank/pull/20"target="_blank" rel="noopener">Pull Request discussion as well</a>.</p>
<h3>Comparing C and Crystal<span class="hx:absolute hx:-mt-20" id="comparing-c-and-crystal"></span>
    <a href="#comparing-c-and-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Just to get us started, let&rsquo;s check out a snippet of Sam&rsquo;s original C version:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">s</span> <span class="o">||</span> <span class="nf">RSTRING_LEN</span><span class="p">(</span><span class="n">str</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qtrue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">e</span> <span class="o">=</span> <span class="nf">RSTRING_END</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="n">s</span> <span class="o">&lt;</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cc</span> <span class="o">=</span> <span class="nf">rb_enc_codepoint_len</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">rb_isspace</span><span class="p">(</span><span class="n">cc</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">cc</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qfalse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">Qtrue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Yeah, quite scary, I know. Now let&rsquo;s see the Crystal version:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">struct</span> <span class="no">Char</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># same way C Ruby implements it</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">is_blank</span>
</span></span><span class="line"><span class="cl">    <span class="nb">self</span> <span class="o">==</span> <span class="s1">&#39; &#39;</span> <span class="o">||</span> <span class="p">(</span><span class="s1">&#39;\t&#39;</span> <span class="o">&lt;=</span> <span class="nb">self</span> <span class="o">&lt;=</span> <span class="s1">&#39;\r&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span> <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="nb">self</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">each_char</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="k">return</span> <span class="kp">false</span> <span class="k">if</span> <span class="o">!</span><span class="n">char</span><span class="o">.</span><span class="n">is_blank</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Hell yeah! If you&rsquo;re a rubyist I bet you can understand 100% of the snippet above. It&rsquo;s not &ldquo;exactly&rdquo; the same thing (as the specs are not fully passing yet), but it&rsquo;s damn close.</p>
<h3>The Quest for a Makefile to Crystal<span class="hx:absolute hx:-mt-20" id="the-quest-for-a-makefile-to-crystal"></span>
    <a href="#the-quest-for-a-makefile-to-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I&rsquo;ve researched many <a href="https://github.com/akitaonrails/ruby_ext_in_crystal_math"target="_blank" rel="noopener">experimental Github repos</a> and Gists out there. But I didn&rsquo;t find one that has it all so I decided to tweak what I found until I got to this version:</p>
<p>Obs: again, I am not a C expert. If you have experience with Makefiles I know this one can be refactored to something nicer than this. Let me know in the comments below.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nl">PLATFORM</span> <span class="p">:</span><span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">uname</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Linux&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">UNAME</span> <span class="o">=</span> <span class="s">&#34;$(shell llvm-config --host-target)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_BIN</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">readlink</span> <span class="o">-</span><span class="n">f</span> <span class="err">`</span><span class="n">which</span> <span class="n">crystal</span><span class="err">`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRARY_PATH</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">))</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">embedded</span><span class="o">/</span><span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBCRYSTAL</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">)</span> <span class="p">)</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">libcrystal</span><span class="p">.</span><span class="n">a</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRUBY</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">ruby</span> <span class="o">-</span><span class="n">e</span> <span class="s">&#34;puts RbConfig::CONFIG[&#39;libdir&#39;]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBS</span> <span class="o">=</span> <span class="o">-</span><span class="n">lpcre</span> <span class="o">-</span><span class="n">lgc</span> <span class="o">-</span><span class="n">lpthread</span> <span class="o">-</span><span class="n">levent</span> <span class="o">-</span><span class="n">lrt</span> <span class="o">-</span><span class="n">ldl</span>
</span></span><span class="line"><span class="cl"><span class="n">LDFLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">rdynamic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">so</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="o">-</span><span class="n">shared</span> <span class="err">$</span><span class="o">^</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBCRYSTAL</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LDFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBS</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRARY_PATH</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRUBY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="n">build</span> <span class="o">--</span><span class="n">cross</span><span class="o">-</span><span class="n">compile</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">target</span> <span class="err">$</span><span class="p">(</span><span class="n">UNAME</span><span class="p">)</span> <span class="err">$</span><span class="o">&lt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nl">PHONY</span><span class="p">:</span> <span class="n">clean</span>
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="n">bc_flags</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Darwin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_FLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">dynamic</span> <span class="o">-</span><span class="n">bundle</span> <span class="o">-</span><span class="n">Wl</span><span class="p">,</span><span class="o">-</span><span class="n">undefined</span><span class="p">,</span><span class="n">dynamic_lookup</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">bundle</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="err">$</span><span class="o">^</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">link</span><span class="o">-</span><span class="n">flags</span> <span class="s">&#34;$(CRYSTAL_FLAGS)&#34;</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">log</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Most people using Crystal are on OS X, including the creators of Crystal. LLVM is under Apple&rsquo;s umbrella and their entire ecosystem relies heavily on LLVM. They spent many years migrating the C front-end first, then the C back-end away from GNU&rsquo;s standard GCC to Clang. And they were able to make both Objective-C and Swift compile down to LLVM&rsquo;s IR and that&rsquo;s how both can interact back and forth natively.</p>
<p>Then, they improved the ARM backend support and that&rsquo;s how they can have an entire iOS &ldquo;Simulator&rdquo; (not a dog slow emulator like Android) where the iOS apps are natively compiled to run over Intel&rsquo;s x86_64 processor while in development and then quickly recompile to ARM when ready to package to the App Store.</p>
<p>This way you can run natively, test quickly, without the slowness of an emulated environment. By the way, I will say this once: Google&rsquo;s biggest mistake is not supporting LLVM as they should, and reinventing the wheel instead. If they had, Go could already be used to implement for Android and Chromebooks as well as x86 based servers and they could put away all the Java/Oracle debacle.</p>
<p>But I digress.</p>
<p>In OS X you can pass a &ldquo;<code>-bundle</code>&rdquo; link-flag to crystal and it will probably use clang underneath to generate the binary bundle.</p>
<p>On Ubuntu crystal just compiles down to an object file (.o) and you have to manually invoke GCC with the &ldquo;<code>-shared</code>&rdquo; option to create a shared-object. To do that we have to use the <a href="https://crystal-lang.org/docs/syntax_and_semantics/cross-compilation.html"target="_blank" rel="noopener">&quot;&ndash;cross-compile&quot;</a> and pass an LLVM target triplet so it generates the .o (this requires the <code>llvm-config</code> tool).</p>
<p>Shared Libraries (.so) and Loadable Modules (.bundle) are different beasts, check <a href="http://docstore.mik.ua/orelly/unix3/mac/ch05_03.htm"target="_blank" rel="noopener">this documentation</a> out for more details.</p>
<p>Keep in mind that benchmarking binaries built with different compilers can make a difference. I am not an expert but out of pure anecdote I believe Ruby under RVM on OS X is compiled using OS X&rsquo;s default Clang. On Ubuntu it&rsquo;s compiled under GCC. This seems to make Ruby on OS X &ldquo;so slightly&rdquo; inefficient in synthetic benchmarks.</p>
<p>On the other hand, Crystal binaries linked with GCC feel &ldquo;so slightly&rdquo; inefficient on Ubuntu, while Ruby on Ubuntu feels a bit faster, having been compiled and linked with GCC.</p>
<p>So when we compare Fast Blank/OS X/bit faster with Ruby/OS X/slower against Fast Blank/Ubuntu/bit slower with Ruby/Ubuntu/bit faster, it seems to give a wider advantage to the OS X benchmark comparison against the Ubuntu benchmark, even though individual computation times are not so far from each other.</p>
<p>I will come back to this point in the benchmarks section.</p>
<p>Finally, every time you have a rubygem with a native extension, you will find this bit in their gemspec files:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Gem</span><span class="o">::</span><span class="no">Specification</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;fast_blank&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">extensions</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;ext/fast_blank/extconf.rb&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>When the gem is installed through <code>gem install</code> or <code>bundle install</code> it will run this script to generate a proper <code>Makefile</code>. In a pure C extension it will use the built-in &ldquo;mkmf&rdquo; library to generate it.</p>
<p>In our case, if we have Crystal installed, we want to use the Crystal version, so I tweaked the <code>extconf.rb</code> to be like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;mkmf&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;VERSION&#39;</span><span class="o">]</span> <span class="o">!=</span> <span class="s2">&#34;C&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;crystal&#39;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;llvm-config&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Very dirty patching</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create_makefile</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">srcprefix</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;Makefile&#34;</span><span class="p">,</span> <span class="s2">&#34;wb&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cr_makefile</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">),</span> <span class="s2">&#34;../src/Makefile&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">print</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">cr_makefile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">close</span> <span class="k">if</span> <span class="n">mfile</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;Crystal version of the Makefile copied&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">create_makefile</span> <span class="s1">&#39;fast_blank&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, if it finds <code>crystal</code> and <code>llvm-config</code> (which in OS X you have to add the proper path like this: <code>export PATH=$(brew --prefix llvm)/bin:$PATH</code>).</p>
<p>The <code>Rakefile</code> in this project declares the standard <code>:compile</code> task as the first one to run, and it will execute the <code>extconf.rb</code>, which will generate the proper <code>Makefile</code> and run the <code>make</code> command to compile and link the proper library in the proper <code>lib/</code> path.</p>
<p>So we will end up with <code>lib/fast_blank.bundle</code> on OS X and <code>lib/fast_blank.so</code> on Ubuntu. From there we can just have <code>require &quot;fast_blank&quot;</code> from any Ruby file in the gem and it will have access to the publicly exported C function mappings from the Crystal library.</p>
<h3>Mapping C-Ruby to Crystal<span class="hx:absolute hx:-mt-20" id="mapping-c-ruby-to-crystal"></span>
    <a href="#mapping-c-ruby-to-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now, any direct C extension - without FFI, fiddle or other &ldquo;bridges&rdquo; - will ALWAYS have a much better advantage.</p>
<p>The reason is that you literally have to &ldquo;copy&rdquo; data from C-Ruby to Crystal/Rust/Go or whatever other language you&rsquo;re binding. While with a C-based extension you can operate directly in the memory space with the data without having to move it or copy it away.</p>
<p>For example. First, you have to bind the C functions from C-Ruby to Crystal. And we accomplish that with <a href="https://github.com/phoffer/crystalized_ruby/blob/master/src/lib_ruby.cr"target="_blank" rel="noopener">Paul Hoffer&rsquo;s Crystalized Ruby</a> mappings. It&rsquo;s an experimental repository that I helped a bit to clean up in order for him to later extract this mapping library into its own Shard (shards are the same as gems for Crystal). For now I had to simply copy the file over to my Fast Blank.</p>
<p>Some of the relevant bits are like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">lib</span> <span class="no">LibRuby</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">VALUE</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">METHOD_FUNC</span> <span class="o">=</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">ID</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># strings</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">value</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">value_ptr</span> <span class="p">:</span> <span class="no">VALUE</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_utf8_encoding</span><span class="p">()</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">enc</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># exception handling</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_rescue</span><span class="p">(</span><span class="n">func</span> <span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">args</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">,</span> <span class="ss">callback</span><span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="ss">value</span><span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="no">RUBY_UTF</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_utf8_encoding</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">to_ruby</span>
</span></span><span class="line"><span class="cl">    <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="no">RUBY_UTF</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">from_ruby</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># FIXME there is still an unhandled problem: then we receive \u0000 from Ruby it raises &#34;string contains null bytes&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># so we catch it with rb_rescue, but then we can&#39;t generate a Pointer(UInt8) that represents the unicode 0, instead we return a plain blank string</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># but then the specs fail</span>
</span></span><span class="line"><span class="cl">    <span class="kp">new</span><span class="p">(</span><span class="n">c_str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">cr_str_from_rb_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">    <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Then I can use these mappings and helpers to build a &ldquo;Wrapper&rdquo; class in Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./lib_ruby&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./string_extension&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">StringExtensionWrapper</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank_as?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank_as?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">crystal_value</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And this &ldquo;Wrapper&rdquo; depends on the &ldquo;pure&rdquo; Crystal library itself like with the snippets for the Char struct and String class extensions I showed in the first section of the article above.</p>
<p>Finally, I have a main &ldquo;fast_blank.cr&rdquo; file that externs those Wrapper functions so C-Ruby can see them as plain String methods:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./string_extension_wrapper.cr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fun</span> <span class="n">init</span> <span class="o">=</span> <span class="no">Init_fast_blank</span>
</span></span><span class="line"><span class="cl">  <span class="no">GC</span><span class="o">.</span><span class="n">init</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibCrystalMain</span><span class="o">.</span><span class="n">__crystal_main</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="no">Pointer</span><span class="p">(</span><span class="no">Pointer</span><span class="p">(</span><span class="no">UInt8</span><span class="p">))</span><span class="o">.</span><span class="n">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">string</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_class</span><span class="p">(</span><span class="s2">&#34;String&#34;</span><span class="p">,</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_cObject</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank_as?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank_as?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This is mostly boilerplate. But now check out what I am having to do in the wrapper, in this particular snippet:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl"><span class="k">rescue</span>
</span></span><span class="line"><span class="cl">  <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I am receiving a C-Ruby String casted as a pointer (VALUE) then I go through the lib_ruby.cr mappings to get the C-Ruby string data and copy it over into a new instance of Crystal&rsquo;s internal String representation. So at any given time I have 2 copies of the same string, one in the C-Ruby memory space and another in the Crystal memory space.</p>
<p>This happens with all FFI-like extensions but it doesn&rsquo;t happen to the pure C implementation. In Sam Saffron&rsquo;s C implementation it directly works with the same address in C-Ruby&rsquo;s memory space:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It receives a pointer (direct memory address) and goes. And this is a huge advantage for the C version. When you have a big volume of medium to large sized strings being copied over from C-Ruby to Crystal, it adds a noticeable overhead that can&rsquo;t be removed.</p>
<h3>String mapping Caveat<span class="hx:absolute hx:-mt-20" id="string-mapping-caveat"></span>
    <a href="#string-mapping-caveat" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I still have a problem though. There is one edge case I was not able to overcome yet (help is most welcome). When C-Ruby passes a unicode <code>&quot;\u0000&quot;</code> I am unable to create the same character in Crystal and I end up passing just an empty string (&quot;&quot;) which is not the same thing.</p>
<p>The way to deal with it is to receive a Ruby String (VALUE) and get the C-String from it this way:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</span><span class="p">))</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>If the &ldquo;str&rdquo; is the &ldquo;\u0000&rdquo; (under Ruby 2.2.5 at least) C-Ruby raises a &ldquo;string contains null bytes&rdquo; exception. Which is why I rescue from this exception like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>When an exception is triggered I have to pass the pointer to another function to rescue from it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">  <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But this is not correct, I am just passing the pointer to a &ldquo;0&rdquo; character, which is &ldquo;empty&rdquo;. Therefore, specs are not passing correctly:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Failures:

  1) String provides a parity with active support function
     Failure/Error: expect(&#34;#{i.to_s(16)} #{c.blank_as?}&#34;).to eq(&#34;#{i.to_s(16)} #{c.blank2?}&#34;)

       expected: &#34;0 false&#34;
            got: &#34;0 true&#34;

       (compared using ==)
     # ./spec/fast_blank_spec.rb:22:in `block (3 levels) in &lt;top (required)&gt;&#39;
     # ./spec/fast_blank_spec.rb:19:in `times&#39;
     # ./spec/fast_blank_spec.rb:19:in `block (2 levels) in &lt;top (required)&gt;&#39;

  2) String treats  correctly
     Failure/Error: expect(&#34;\u0000&#34;.blank_as?).to be_falsey
       expected: falsey value
            got: true
     # ./spec/fast_blank_spec.rb:47:in `block (2 levels) in &lt;top (required)&gt;&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ary gave a simple tip later, I will add it to the conclusion below.</p>
<h3>The Synthetic Benchmarks (careful on how you interpret them!)<span class="hx:absolute hx:-mt-20" id="the-synthetic-benchmarks-careful-on-how-you-interpret-them"></span>
    <a href="#the-synthetic-benchmarks-careful-on-how-you-interpret-them" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The <a href="https://github.com/rails/rails/blob/2a371368c91789a4d689d6a84eb20b238c37678a/activesupport/lib/active_support/core_ext/object/blank.rb#L101"target="_blank" rel="noopener">original Rails ActiveSupport implementation of String#blank?</a> looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 0x3000: fullwidth whitespace</span>
</span></span><span class="line"><span class="cl">  <span class="no">NON_WHITESPACE_REGEXP</span> <span class="o">=</span> <span class="s">%r![^\s#{[0x3000].pack(&#34;U&#34;)}]!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># A string is blank if it&#39;s empty or contains whitespaces only:</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;&#34;.blank?                 # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;   &#34;.blank?              # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;　&#34;.blank?               # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34; something here &#34;.blank? # =&gt; false</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1.8 does not takes [:space:] properly</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">encoding_aware?</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="sr">/[^[:space:]]/</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="no">NON_WHITESPACE_REGEXP</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s mainly a regular expression comparison, which can be a bit slow. Sam&rsquo;s version is a more straightforward loop through the string to compare each character with what&rsquo;s considered &ldquo;blank&rdquo;. There are many unicode codepoints that are considered blank, some are not, which is why the C and Crystal versions are similar, but they are different from Rails&rsquo; version.</p>
<p>In the Fast Blank gem there is a <code>benchmark</code> Ruby script to compare the C-extension against Rails&rsquo; Regex based implementation.</p>
<p>The Regex implementation is called <strong>&ldquo;Slow Blank&rdquo;</strong>. It&rsquo;s particularly slow if you pass a real empty String, so in the benchmark Sam added a <strong>&ldquo;New Slow Blank&rdquo;</strong> that checks through <code>String#empty?</code> first, and this version is faster in this edge case.</p>
<p>The fast C version is called <strong>&ldquo;Fast Blank&rdquo;</strong> but although you can consider it &ldquo;correct&rdquo; it&rsquo;s not compatible with all the edge cases from Rails. So he implemented a <code>String#blank_as?</code> which is compatible with Rails. Sam calls it <strong>&ldquo;Fast Activesupport&rdquo;</strong>.</p>
<p>In my Crystal version I did the same, having both <code>String#blank?</code> and <code>String#blank_as?</code>.</p>
<p>So, without further ado, here is the <strong>C Version over OS X</strong> benchmark for empty strings, and we exercise each function many times within a few seconds to have more accurate results (check out Evan Phoenix&rsquo;s <a href="https://github.com/evanphx/benchmark-ips"target="_blank" rel="noopener">&ldquo;benchmark/ips&rdquo;</a> to understand the &ldquo;iteration per second&rdquo; methodology).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   191.708k i/100ms
  Fast ActiveSupport   209.628k i/100ms
          Slow Blank    61.487k i/100ms
      New Slow Blank   203.165k i/100ms
Calculating _______________________________________
          Fast Blank     20.479M (± 9.3%) i/s -    101.414M in   5.001177s
  Fast ActiveSupport     21.883M (± 9.4%) i/s -    108.378M in   5.004350s
          Slow Blank      1.060M (± 4.7%) i/s -      5.288M in   5.001365s
      New Slow Blank     18.883M (± 6.9%) i/s -     94.065M in   5.008899s

Comparison:
  Fast ActiveSupport: 21882711.5 i/s
          Fast Blank: 20478961.5 i/s - same-ish: difference falls within error
      New Slow Blank: 18883442.2 i/s - same-ish: difference falls within error
          Slow Blank:  1059692.6 i/s - 20.65x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s super fast. Rails&rsquo; version is 20x slower on my machine.</p>
<p>Now, <strong>Crystal version over OS X</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   174.349k i/100ms
  Fast ActiveSupport   174.035k i/100ms
          Slow Blank    64.684k i/100ms
      New Slow Blank   215.164k i/100ms
Calculating _______________________________________
          Fast Blank      8.647M (± 1.6%) i/s -     43.239M in   5.001530s
  Fast ActiveSupport      8.580M (± 1.3%) i/s -     42.987M in   5.010759s
          Slow Blank      1.047M (± 3.7%) i/s -      5.239M in   5.008907s
      New Slow Blank     19.090M (± 9.3%) i/s -     94.672M in   5.009057s

Comparison:
      New Slow Blank: 19090034.8 i/s
          Fast Blank:  8647459.7 i/s - 2.21x slower
  Fast ActiveSupport:  8580487.9 i/s - 2.22x slower
          Slow Blank:  1047465.3 i/s - 18.22x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As I explained before, even checking empty strings, the Crystal version is slower than the Ruby check for <code>String#empty?</code> (New Slow Blank) because I have the string copying routine of the Wrapper mappings. This adds overhead that is perceptible over many iterations. It&rsquo;s still 18x faster than Rails, but it loses to C-Ruby.</p>
<p>Finally, <strong>Crystal version over Ubuntu</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   255.883k i/100ms
  Fast ActiveSupport   260.915k i/100ms
          Slow Blank   105.424k i/100ms
      New Slow Blank   284.670k i/100ms
Calculating _______________________________________
          Fast Blank      8.895M (± 9.8%) i/s -     44.268M in   5.037761s
  Fast ActiveSupport      8.647M (± 8.2%) i/s -     43.051M in   5.020125s
          Slow Blank      1.736M (± 3.9%) i/s -      8.750M in   5.048253s
      New Slow Blank     22.170M (± 6.2%) i/s -    110.452M in   5.004909s

Comparison:
      New Slow Blank: 22170031.0 i/s
          Fast Blank:  8895113.3 i/s - 2.49x slower
  Fast ActiveSupport:  8646940.8 i/s - 2.56x slower
          Slow Blank:  1736071.0 i/s - 12.77x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice that it&rsquo;s around the same ballpark, but the Rails version on Ubuntu runs almost twice as fast compared to its counterpart in OS X, which makes the comparison against the Crystal library go down from 18x to 12x.</p>
<p>The benchmark keeps comparing against strings of larger and larger sizes, from 6, to 14, to 24, up to 136 characters in length.</p>
<p>Let&rsquo;s get just the last test case of 136 characters. First with <strong>C version on OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   177.521k i/100ms
  Fast ActiveSupport   193.559k i/100ms
          Slow Blank    89.378k i/100ms
      New Slow Blank    60.639k i/100ms
Calculating _______________________________________
          Fast Blank     10.727M (± 8.7%) i/s -     53.256M in   5.006538s
  Fast ActiveSupport     11.600M (± 8.3%) i/s -     57.681M in   5.009692s
          Slow Blank      1.872M (± 5.7%) i/s -      9.385M in   5.029243s
      New Slow Blank      1.017M (± 5.3%) i/s -      5.094M in   5.022994s

Comparison:
  Fast ActiveSupport: 11600112.2 i/s
          Fast Blank: 10726792.8 i/s - same-ish: difference falls within error
          Slow Blank:  1872262.5 i/s - 6.20x slower
      New Slow Blank:  1016926.7 i/s - 11.41x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The C-version is consistently much faster in all test cases and in the 136 characters it&rsquo;s still 11x faster than Rails in pure Ruby.</p>
<p>Now the <strong>Crystal version over OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   127.749k i/100ms
  Fast ActiveSupport   126.538k i/100ms
          Slow Blank    94.390k i/100ms
      New Slow Blank    60.594k i/100ms
Calculating _______________________________________
          Fast Blank      3.283M (± 1.8%) i/s -     16.480M in   5.021364s
  Fast ActiveSupport      3.235M (± 1.3%) i/s -     16.197M in   5.008315s
          Slow Blank      1.888M (± 4.4%) i/s -      9.439M in   5.009458s
      New Slow Blank    967.950k (± 4.7%) i/s -      4.848M in   5.018946s

Comparison:
          Fast Blank:  3283025.1 i/s
  Fast ActiveSupport:  3234586.5 i/s - same-ish: difference falls within error
          Slow Blank:  1887800.5 i/s - 1.74x slower
      New Slow Blank:   967950.2 i/s - 3.39x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s also faster, but just by 2 to 3 times compared to pure Ruby, a far cry from 11x. But my hypothesis is that the mapping and copying of so many strings over adds a large overhead that the C version does not have.</p>
<p>And the <strong>Crystal version over Ubuntu</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   186.810k i/100ms
  Fast ActiveSupport   187.306k i/100ms
          Slow Blank   143.439k i/100ms
      New Slow Blank    98.308k i/100ms
Calculating _______________________________________
          Fast Blank      3.517M (± 3.9%) i/s -     17.560M in   5.000791s
  Fast ActiveSupport      3.485M (± 3.8%) i/s -     17.419M in   5.006427s
          Slow Blank      2.755M (± 4.2%) i/s -     13.770M in   5.008490s
      New Slow Blank      1.551M (± 4.3%) i/s -      7.766M in   5.017853s

Comparison:
          Fast Blank:  3516960.7 i/s
  Fast ActiveSupport:  3484575.5 i/s - same-ish: difference falls within error
          Slow Blank:  2754669.0 i/s - 1.28x slower
      New Slow Blank:  1550815.2 i/s - 2.27x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Again, the Ubuntu versions of both the Crystal library and the Ruby binary run faster and the comparison shows no more than twice as much faster. And pure Ruby&rsquo;s <code>String#empty?</code> is in the same ballpark as Crystal&rsquo;s version.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The most obvious conclusion is that I probably made a mistake in choosing Fast Blank as my first proof of concept. The algorithm is too trivial and a simple check for <code>String#empty?</code> in pure Ruby is orders of magnitude faster than the added overhead of mapping and string copying to Crystal.</p>
<p>Also, any use case where you have a huge amount of small bits of data being transferred from C-Ruby to Crystal or any FFI-based extension will have the overhead of data copying, which a pure C-version will not have. Which is why Fast Blank is better done in C.</p>
<p>Any other use case where you have smaller amounts of data, or data that can be transferred in bulk (less calls from C-Ruby to the extension, with arguments with a larger size, and with more costly processing) is a better candidate to benefit from extensions.</p>
<p>Again, not everything gets automatically faster, we always have to figure out the use case scenarios first. But because it&rsquo;s so much easier to write in Crystal and benchmark, we can make faster proofs of concept and scrap the idea if the measurements prove that we won&rsquo;t benefit as much.</p>
<p>The Crystal documentation recently received a <a href="https://crystal-lang.org/docs/guides/performance.html"target="_blank" rel="noopener">&ldquo;Performance Guide&rdquo;</a>. It&rsquo;s very useful for you to avoid common pitfalls that harm overall performance. Even though LLVM is quite competent in heavy optimization, it can&rsquo;t do everything. So read it through to improve your general Crystal skills.</p>
<p>That being said, I still believe that this exercise was well worth it. I will probably do some more. I&rsquo;d really want to thank Ary (Crystal creator) and Paul Hoffer for the patience in helping me out through many of the quirks I found along the way.</p>
<p>While I was finishing this post, <a href="https://github.com/SamSaffron/fast_blank/pull/20#issuecomment-230875300"target="_blank" rel="noopener">Ary pointed out</a> that I could probably ditch Strings altogether and work directly with an array of bytes, which is a good idea and I will probably try that. I think I made it clear by now that the whole String copying adds a very perceptible overhead as we saw in the benchmarks above. Let me know if someone is interested in contributing as well. With a few more tweaks I believe we can have a Crystal version that can at least compete against the C version while also being more readable and maintainable for most Rubyists, which is my goal.</p>
<p>I hope the code I published here will serve as boilerplate examples for more Crystal-based Ruby extensions in the future!</p>
]]></content:encoded><category>crystal</category><category>performance</category><category>algorithms-and-data-structures</category></item><item><title>[Manga-Downloadr] Improving the Crystal/Ruby from bursts to pool stream</title><link>https://akitaonrails.github.io/en/2016/06/07/manga-downloadr-improving-the-crystal-ruby-from-bursts-to-pool-stream/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/06/07/manga-downloadr-improving-the-crystal-ruby-from-bursts-to-pool-stream/</guid><pubDate>Tue, 07 Jun 2016 17:13:00 -0300</pubDate><description>&lt;p&gt;Yesterday &lt;a href="http://www.akitaonrails.com/en/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener"&gt;I posted&lt;/a&gt; how I built a new implementation of the Manga Reader Downloader in Crystal, ported to Ruby, tested on JRuby and compared against Elixir. Just to recap, these were the results to fetch chapters, pages and image links of a sample manga:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MRI Ruby 2.3.1: 57 seconds&lt;/li&gt;
&lt;li&gt;JRuby 9.1.1.0: 45 seconds&lt;/li&gt;
&lt;li&gt;Crystal 0.17.0: 59 seconds&lt;/li&gt;
&lt;li&gt;Elixir: 14 seconds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also said how it was an unfair comparison as the Elixir version uses a different - and obviously more efficient - algorithm.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Yesterday <a href="http://www.akitaonrails.com/en/2016/06/06/manga-downloadr-portando-de-crystal-para-ruby-e-um-pouco-de-jruby"target="_blank" rel="noopener">I posted</a> how I built a new implementation of the Manga Reader Downloader in Crystal, ported to Ruby, tested on JRuby and compared against Elixir. Just to recap, these were the results to fetch chapters, pages and image links of a sample manga:</p>
<ul>
<li>MRI Ruby 2.3.1: 57 seconds</li>
<li>JRuby 9.1.1.0: 45 seconds</li>
<li>Crystal 0.17.0: 59 seconds</li>
<li>Elixir: 14 seconds</li>
</ul>
<p>I also said how it was an unfair comparison as the Elixir version uses a different - and obviously more efficient - algorithm.</p>
<p>It was the &ldquo;first-version-that-worked&rdquo; so I decided to go ahead and improve the implementations. In the Ruby/JRuby version I added the <a href="https://github.com/meh/ruby-thread"target="_blank" rel="noopener">thread</a> gem to have a good enough implementation of a Thread pool that works for Ruby and JRuby. I probably should have used Concurrent-Ruby but I was having some trouble making FixedThreadPool work.</p>
<p>Anyway, now all versions will have a constant pool of requests running and we can make a better comparison.</p>
<p>Another thing that may have skewed the results against Crystal is that it seems to have a <a href="https://github.com/crystal-lang/crystal/issues/2660"target="_blank" rel="noopener">faulty DNS resolver</a> implementation, so for now I just added the Manga Reader IP address directly to my <code>/etc/hosts</code> file to avoid <code>getaddrinfo: Name or service not known</code> exceptions.</p>
<p>To begin, let&rsquo;s test the same Elixir implementation, and as expected the result is still the same:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>11,49s user 0,82s system 77% cpu 15,827 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, the MRI Ruby with the &ldquo;batch burst&rdquo; algorithm was taking 57 seconds and this new implementation using a ThreadPool runs much better:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>12,67s user 0,92s system 50% cpu 27,149 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In Crystal, it&rsquo;s a bit more complicated as there is no way to implement the equivalent of a &ldquo;Fiber Pool&rdquo;. What we have to do is run an infinite loop until the last process signals a loop break. Within the loop we create a maximum number of fibers, wait for each one to signal that it finished through an individual channel and loop again to create a new fiber, and so on. Compared to yesterday&rsquo;s 59 seconds, this is much better:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5,29s user 0,33s system 26% cpu 21,166 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The JRuby version is not so fast though. Still better than yesterday&rsquo;s 45 seconds but now it&rsquo;s losing even to MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>49,24s user 1,41s system 146% cpu 34,602 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I tried to use the <code>--dev</code> flag for <a href="https://github.com/jruby/jruby/wiki/Improving-startup-time"target="_blank" rel="noopener">faster start up time</a> and it does improve a bit, getting it closer to MRI:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>22,26s user 0,99s system 76% cpu 30,320 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Not sure if it can be improved more though, any tips are welcome - don&rsquo;t forget to comment below.</p>
<p>So, Elixir is still at least twice as fast as Crystal at this point. But this also demonstrates how a different algorithm does make a huge difference where it matters. I can probably tweak it a bit more but this should suffice for now.</p>
<h3>Changing the Ruby implementation to use ThreadPool<span class="hx:absolute hx:-mt-20" id="changing-the-ruby-implementation-to-use-threadpool"></span>
    <a href="#changing-the-ruby-implementation-to-use-threadpool" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;thread/pool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">MangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="k">class</span> <span class="nc">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span>    <span class="o">=</span> <span class="no">Thread</span><span class="o">.</span><span class="n">pool</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex</span>   <span class="o">=</span> <span class="no">Mutex</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">collection</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">process</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span>  <span class="o">=</span> <span class="vi">@turn_on_engine</span> <span class="p">?</span> <span class="vi">@engine_klass</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">)</span> <span class="p">:</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">          <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span><span class="o">&amp;.</span><span class="n">flatten</span>
</span></span><span class="line"><span class="cl">          <span class="n">mutex</span><span class="o">.</span><span class="n">synchronize</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span> <span class="o">+=</span> <span class="p">(</span> <span class="n">reply</span> <span class="o">||</span> <span class="o">[]</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">pool</span><span class="o">.</span><span class="n">shutdown</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The idea is that we will have a fixed number of spawned native threads (in this case, determined by <code>@config.download_batch_size</code>). As one thread finishes it will pop a new link from the <code>collection</code> Array, essentially working as a &ldquo;queue&rdquo; to be depleted.</p>
<p>The results are accumulated in the <code>results</code> Array. Because many threads may want to modify it at once we have to synchronize access through a Mutex.</p>
<p>This way we always have a fixed amount of workers performing requests constantly instead of slicing the <code>collection</code> Array and doing bursts as in the previous version.</p>
<h3>Changing the Crystal implementation to simulate a Fibers Pool<span class="hx:absolute hx:-mt-20" id="changing-the-crystal-implementation-to-simulate-a-fibers-pool"></span>
    <a href="#changing-the-crystal-implementation-to-simulate-a-fibers-pool" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The Crystal version became a bit more complicated as I didn&rsquo;t find a pool library to pull in. I found a rough implementation of a pool in <a href="http://stackoverflow.com/a/30854065/1529907"target="_blank" rel="noopener">this stackoverflow post</a> and I was able to implement an improved version into a new shard so you can take advantage of it in your projects. Check out the source code at <a href="https://github.com/akitaonrails/fiberpool"target="_blank" rel="noopener">akitaonrails/fiberpool</a>. This is how I added it to my project:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">fiberpool</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">github</span><span class="p">:</span><span class="w"> </span><span class="l">akitaonrails/fiberpool</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And this is how I used it. Notice that the logic itself is very close to the MRI Ruby version, but using a &ldquo;Fiber Pool&rdquo; instead of a Thread Pool.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;fiberpool&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">CrMangaDownloadr</span>
</span></span><span class="line"><span class="cl">  <span class="n">struct</span> <span class="no">Concurrency</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="vi">@config</span> <span class="p">:</span> <span class="no">Config</span><span class="p">,</span> <span class="vi">@turn_on_engine</span> <span class="o">=</span> <span class="kp">true</span><span class="p">);</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">fetch</span><span class="p">(</span><span class="n">collection</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">A</span><span class="p">)</span><span class="sc">?,</span> <span class="n">engine_class</span> <span class="p">:</span> <span class="n">E</span><span class="o">.</span><span class="n">class</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">block</span> <span class="p">:</span> <span class="n">A</span><span class="p">,</span> <span class="n">E?</span> <span class="o">-&gt;</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span><span class="sc">?)</span> <span class="p">:</span> <span class="nb">Array</span><span class="p">(</span><span class="n">B</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span> <span class="o">=</span> <span class="o">[]</span> <span class="n">of</span> <span class="n">B</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">collection</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span> <span class="o">=</span> <span class="no">Fiberpool</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">download_batch_size</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">pool</span><span class="o">.</span><span class="n">run</span> <span class="k">do</span> <span class="o">|</span><span class="n">item</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">engine</span> <span class="o">=</span> <span class="k">if</span> <span class="vi">@turn_on_engine</span>
</span></span><span class="line"><span class="cl">                     <span class="n">engine_class</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="vi">@config</span><span class="o">.</span><span class="n">domain</span><span class="p">,</span> <span class="vi">@config</span><span class="o">.</span><span class="n">cache_http</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                   <span class="k">end</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="n">reply</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">item</span><span class="p">,</span> <span class="n">engine</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">results</span><span class="o">.</span><span class="n">concat</span><span class="p">(</span><span class="n">reply</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">          <span class="k">end</span>
</span></span><span class="line"><span class="cl">        <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">      <span class="n">results</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But again, this is very I/O intensive and both the Ruby and Crystal versions take advantage of the fact that they can do more work while waiting for one HTTP request to finish.</p>
<p><a name="reproducing-tests"> </a></p>
<h3>Reproducing the Tests<span class="hx:absolute hx:-mt-20" id="reproducing-the-tests"></span>
    <a href="#reproducing-the-tests" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>I implemented a &ldquo;Test Mode&rdquo; in all 3 implementations. You can clone from my repositories:</p>
<ul>
<li><a href="https://github.com/akitaonrails/cr_manga_downloadr"target="_blank" rel="noopener">Crystal version</a></li>
<li><a href="https://github.com/akitaonrails/manga-downloadr"target="_blank" rel="noopener">Ruby/JRuby version</a></li>
<li><a href="https://github.com/akitaonrails/ex_manga_downloadr"target="_blank" rel="noopener">Elixir version</a></li>
</ul>
<p>And you can run the test mode like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test

# MRI:
time bin/manga-downloadr --test

# JRuby (you have to edit Gemfile to uncomment the JRuby engine):
time jruby --dev -S bin/manga-downloadr --test

# Elixir:
time ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This will run only the fetching of chapters, pages and image links, skipping the actual downloading of the images, optimization through mogrify and PDF compilation. Those skipped parts take too long and don&rsquo;t say anything about the tested languages.</p>
<p>And if you want to test just the CPU intensive parts and avoid all networking interference altogether, you can turn on HTTP Cache mode and run the tests twice so the first run will cache everything first, like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># crystal:
time ./cr_manga_downloadr --test --cache

# MRI:
time bin/manga-downloadr --test --cache

# JRuby (you have to edit Gemfile to uncomment the JRuby engine):
time jruby --dev -S bin/manga-downloadr --test --cache

# Elixir:
time CACHE_HTTP=true ./ex_manga_downloadr --test</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>So, <strong>with all requests already cached</strong> these are the results:</p>
<p>Elixir:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>7,13s user 0,24s system 331% cpu 2,227 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>MRI Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>5.590000   0.180000   5.770000 (  5.678714)
5,87s user 0,21s system 101% cpu 5,996 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>JRuby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>10.580000   0.180000  10.760000 (  3.184472)
14,54s user 0,44s system 262% cpu 5,716 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>1.610000   0.050000   1.660000 (  1.344123)
1,62s user 0,06s system 124% cpu 1,350 total</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The Ruby/JRuby/Crystal versions have internal benchmarks to take away startup time (this is why they have 2 lines of times).</p>
<p>So, Elixir is very fast. It takes roughly 2 seconds to parse all the 1,900 HTML files to find the links.</p>
<p>Ruby is the slowest, obviously. It takes almost 6 seconds.</p>
<p>The JRuby version also takes almost 6 seconds but internally it processes in 3 seconds, the rest is startup time and warming up of the JVM underneath.</p>
<p>And Crystal is the fastest, as you would expect because it&rsquo;s a super optimized binary doing CPU bound operations, clocking in at a bit more than 1 second.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Even though the algorithms are roughly similar, Elixir is still winning by a very large margin in the total process (with the external HTTP requests).</p>
<p>There is more than just interpreter/compiler speeds, there is more than single-thread, multi-thread, fibers infrastructure.</p>
<p>We also have the maturity of the respective standard libraries (including TCP stack, HTTP client libraries, String/Array/Regex operations, etc) and 3rd party libraries (libXML, Nokogiri, etc). So there is a lot that can interfere with the tests. I&rsquo;d guess that Crystal&rsquo;s standard library, especially the networking parts, are not battle-tested enough at this point (pre 1.0!).</p>
<p>So, the summary with the new results is this:</p>
<ul>
<li>MRI Ruby 2.3.1: 27 seconds</li>
<li>JRuby 9.1.1.0: 30 seconds</li>
<li>Crystal 0.17.0: 21 seconds</li>
<li>Elixir: 15 seconds</li>
</ul>
<p>Let me know if you have ideas to make them even faster!</p>
]]></content:encoded><category>anime-and-manga</category><category>performance</category><category>distributed-systems</category></item><item><title>Small Experiment with Rails over JRuby 9.x on Heroku</title><link>https://akitaonrails.github.io/en/2016/06/03/small-experiment-with-rails-over-jruby-9-x-on-heroku/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/06/03/small-experiment-with-rails-over-jruby-9-x-on-heroku/</guid><pubDate>Fri, 03 Jun 2016 18:12:00 -0300</pubDate><description>&lt;p&gt;After playing around with languages different than Ruby (such as &lt;a href="https://akitaonrails.github.io/elixir"&gt;Elixir&lt;/a&gt; or &lt;a href="https://akitaonrails.github.io/crystal"&gt;Crystal&lt;/a&gt;), it was time to go back to Ruby and see how JRuby is performing nowadays.&lt;/p&gt;
&lt;p&gt;JRuby always pursued the goal of being a competent MRI replacement. If you have not followed what&amp;rsquo;s going on, JRuby changed version schemes when they switched from the 1.7 series to the 9.x series. You should read this blog post that explains it.&lt;/p&gt;
&lt;p&gt;In summary, if you want MRI 2.2 compatibility you must use JRuby 9.0.x and if you want MRI 2.3 compatibility you must use the JRuby 9.1.x series. The most current release being 9.1.2.0. Anything prior to that you can refer to this &lt;a href="https://devcenter.heroku.com/articles/ruby-support#ruby-versions"target="_blank" rel="noopener"&gt;table of versions&lt;/a&gt; from Heroku&amp;rsquo;s documentation.&lt;/p&gt;</description><content:encoded><![CDATA[<p>After playing around with languages different than Ruby (such as <a href="/elixir">Elixir</a> or <a href="/crystal">Crystal</a>), it was time to go back to Ruby and see how JRuby is performing nowadays.</p>
<p>JRuby always pursued the goal of being a competent MRI replacement. If you have not followed what&rsquo;s going on, JRuby changed version schemes when they switched from the 1.7 series to the 9.x series. You should read this blog post that explains it.</p>
<p>In summary, if you want MRI 2.2 compatibility you must use JRuby 9.0.x and if you want MRI 2.3 compatibility you must use the JRuby 9.1.x series. The most current release being 9.1.2.0. Anything prior to that you can refer to this <a href="https://devcenter.heroku.com/articles/ruby-support#ruby-versions"target="_blank" rel="noopener">table of versions</a> from Heroku&rsquo;s documentation.</p>
<p>There are important recommendations as well:</p>
<ul>
<li>
<p>Ideally you should use Rails 4.2. Try to be at least above 4.0, and you can turn on <code>config.threadsafe!</code> by default in the &ldquo;config/environments/production.rb&rdquo; file. To understand this subject, refer to <a href="http://tenderlovemaking.com/2012/06/18/removing-config-threadsafe.html"target="_blank" rel="noopener">Tenderlove&rsquo;s excellent explanation</a>.</p>
</li>
<li>
<p>If you&rsquo;re deploying to Heroku, don&rsquo;t bother trying the free or 1X dyno, which only gives you 512Mb of RAM. While it is enough for most small Rails applications (even with 2 or 3 Puma concurrent workers), I found that even the smallest apps can easily go above that. So you must consider at least the 2X dyno. In any server configuration, always consider more than 1Gb of RAM.</p>
</li>
</ul>
<h3>Gems<span class="hx:absolute hx:-mt-20" id="gems"></span>
    <a href="#gems" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>There are several gems with C extensions that just won&rsquo;t work. Some of them have drop-in replacements, some don&rsquo;t. You should refer to <a href="https://github.com/jruby/jruby/wiki/C-Extension-Alternatives"target="_blank" rel="noopener">their Wiki</a> for a list of cases.</p>
<p>In my small sample application - which is nothing more than a content website backed by a Postgresql database, ActiveAdmin to manage content, RMagick + Paperclip (yes, it&rsquo;s an old app) to handle image upload and resizing, there was not a lot to change. The important bits of my &ldquo;Gemfile&rdquo; end up looking like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">source</span> <span class="s1">&#39;https://rubygems.org&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ruby</span> <span class="s1">&#39;2.3.0&#39;</span><span class="p">,</span> <span class="ss">:engine</span> <span class="o">=&gt;</span> <span class="s1">&#39;jruby&#39;</span><span class="p">,</span> <span class="ss">:engine_version</span> <span class="o">=&gt;</span> <span class="s1">&#39;9.1.1.0&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ruby &#39;2.3.1&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rails&#39;</span><span class="p">,</span> <span class="s1">&#39;~&gt; 4.2.6&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;devise&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;haml&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;puma&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rack-attack&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rack-timeout&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rakismet&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Database</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;pg&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:ruby</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;activerecord-jdbcpostgresql-adapter&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:jruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Cache</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;dalli&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s2">&#34;actionpack-action_caching&#34;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s2">&#34;rails/actionpack-action_caching&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Admin</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;activeadmin&#39;</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s1">&#39;activeadmin&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;active_skin&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Assets</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;therubyracer&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:ruby</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;therubyrhino&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:jruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;asset_sync&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;jquery-ui-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sass-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;uglifier&#39;</span><span class="p">,</span>     <span class="s1">&#39;&gt;= 1.3.0&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;coffee-rails&#39;</span><span class="p">,</span> <span class="s1">&#39;~&gt; 4.0.0&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;compass-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;sprockets&#39;</span><span class="p">,</span> <span class="s1">&#39;~&gt;2.11.0&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Image Processing</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;paperclip&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;fog&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rmagick&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:ruby</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rmagick4j&#39;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:jruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:test</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;shoulda-matchers&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:test</span><span class="p">,</span> <span class="ss">:development</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s2">&#34;sqlite3&#34;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:ruby</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s2">&#34;activerecord-jdbcsqlite3-adapter&#34;</span><span class="p">,</span> <span class="ss">platforms</span><span class="p">:</span> <span class="ss">:jruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Pretty printed test output</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;turn&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;jasmine&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;pry-rails&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rspec&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rspec-rails&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;capybara&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;poltergeist&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;database_cleaner&#39;</span><span class="p">,</span> <span class="s1">&#39;&lt; 1.1.0&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;letter_opener&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;dotenv-rails&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">group</span> <span class="ss">:production</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rails_12factor&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;rack-cache&#39;</span><span class="p">,</span> <span class="nb">require</span><span class="p">:</span> <span class="s1">&#39;rack/cache&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;memcachier&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="n">gem</span> <span class="s1">&#39;newrelic_rpm&#39;</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice how I paired gems for the <code>:ruby</code> and <code>:jruby</code> platforms. After doing this change and <code>bundle install</code> everything, I ran my Rspec suite and - fortunately - they all passed on the first run without any further changes! Your mileage will vary depending on the complexity of your application, so have your tests ready.</p>
<h3>Puma<span class="hx:absolute hx:-mt-20" id="puma"></span>
    <a href="#puma" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In the case of Puma, the configuration is a bit trickier, mine looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">web_concurrency</span> <span class="o">=</span> <span class="nb">Integer</span><span class="p">(</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WEB_CONCURRENCY&#39;</span><span class="o">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">web_concurrency</span> <span class="o">&gt;</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">  <span class="n">workers</span> <span class="n">web_concurrency</span>
</span></span><span class="line"><span class="cl">  <span class="n">preload_app!</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">threads_count</span> <span class="o">=</span> <span class="nb">Integer</span><span class="p">(</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;RAILS_MAX_THREADS&#39;</span><span class="o">]</span> <span class="o">||</span> <span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">threads</span> <span class="n">threads_count</span><span class="p">,</span> <span class="n">threads_count</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">rackup</span>      <span class="no">DefaultRackup</span>
</span></span><span class="line"><span class="cl"><span class="n">port</span>        <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;PORT&#39;</span><span class="o">]</span>     <span class="o">||</span> <span class="mi">3000</span>
</span></span><span class="line"><span class="cl"><span class="n">environment</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;RACK_ENV&#39;</span><span class="o">]</span> <span class="o">||</span> <span class="s1">&#39;development&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">on_worker_boot</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Worker specific setup for Rails 4.1+</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot</span>
</span></span><span class="line"><span class="cl">  <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span><span class="o">.</span><span class="n">establish_connection</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>It&rsquo;s a bit different from what you might find in other documentation. The important part is to turn off workers and preload_app when loading over JRuby. Otherwise it will complain and crash. On my original MRI deploy I am using a small 1X dyno and I can leave <code>WEB_CONCURRENCY=2</code> and <code>RAILS_MAX_THREADS=5</code> but on the JRuby deploy I set it to <code>WEB_CONCURRENCY=1</code> (to turn off workers) and <code>RAILS_MAX_THREADS=16</code> (because I am assuming JRuby can handle more multithreading than MRI).</p>
<p>Another important bit: most people still assume that MRI can&rsquo;t take advantage of native parallel threads at all because of the dreaded GIL (Global Interpreter Lock), but that&rsquo;s only half true. MRI Ruby can parallelize threads on I/O waits. So, if a part of your app is waiting for the database to process and return rows, for example, another thread can take over and do something else, in parallel. There is some concurrency available, even if limited, so setting a small amount of threads for Puma might help a bit.</p>
<p>Do not forget to set the Pool size to at least the same number of <code>RAILS_MAX_THREADS</code>. You can either use the <code>config/database.yml</code> for Rails 4.1+ or add an initializer. Follow <a href="https://devcenter.heroku.com/articles/concurrency-and-database-connections#threaded-servers"target="_blank" rel="noopener">Heroku&rsquo;s documentation</a> on how to do so.</p>
<h3>Initial Benchmarks<span class="hx:absolute hx:-mt-20" id="initial-benchmarks"></span>
    <a href="#initial-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>So, I was able to successfully deploy a secondary JRuby version of my original MRI-based Rails app.</p>
<p>The original app is still in a Hobby Dyno, sized at 512Mb of RAM. The secondary app needed a Standard 1X Dyno, sized at 1Gb of RAM.</p>
<p>The app itself is super simple and as I&rsquo;m using caching - <a href="http://www.akitaonrails.com/2015/05/20/dynamic-site-as-fast-as-a-static-generated-one-with-raptor"target="_blank" rel="noopener">as you always should!</a> -, the response time is very low, on the tens of milliseconds.</p>
<p>I tried to use the <a href="https://github.com/rakyll/boom"target="_blank" rel="noopener">Boom</a> tool to benchmark the requests. I did a lot of warming up on the JRuby version, running the benchmarks multiple times and even using Loader.io for added pressure.</p>
<p>I am running this test:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>$ boom -n 200 -c 50 http://foo-my-site/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The MRI version performs like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Summary:
  Total:    16.4254 secs
  Slowest:  9.0785 secs
  Fastest:  0.8362 secs
  Average:  2.6551 secs
  Requests/sec: 12.1763
  Total data:   28837306 bytes
  Size/request: 144186 bytes

Status code distribution:
  [200] 200 responses

Response time histogram:
  0.836 [1] |
  1.660 [57]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  2.485 [57]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  3.309 [33]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  4.133 [22]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  4.957 [16]    |∎∎∎∎∎∎∎∎∎∎∎
  5.782 [6] |∎∎∎∎
  6.606 [3] |∎∎
  7.430 [1] |
  8.254 [3] |∎∎
  9.079 [1] |

Latency distribution:
  10% in 1.2391 secs
  25% in 1.5910 secs
  50% in 2.1974 secs
  75% in 3.4327 secs
  90% in 4.5580 secs
  95% in 5.6727 secs
  99% in 8.1567 secs</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And the JRuby version performs like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Summary:
  Total:    15.5784 secs
  Slowest:  7.4106 secs
  Fastest:  0.5770 secs
  Average:  2.3224 secs
  Requests/sec: 12.8383
  Total data:   28848475 bytes
  Size/request: 144242 bytes

Status code distribution:
  [200] 200 responses

Response time histogram:
  0.577 [1] |
  1.260 [23]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  1.944 [62]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  2.627 [51]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  3.310 [24]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  3.994 [26]    |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
  4.677 [8] |∎∎∎∎∎
  5.361 [1] |
  6.044 [0] |
  6.727 [1] |
  7.411 [3] |∎

Latency distribution:
  10% in 1.1599 secs
  25% in 1.5154 secs
  50% in 2.0781 secs
  75% in 2.8909 secs
  90% in 3.7409 secs
  95% in 4.2556 secs
  99% in 6.7685 secs</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In general, I&rsquo;d say that they are around the same. As this is not a particularly CPU-intensive processing, and most of the time is spent going through the Rails stack and hitting Memcachier to pull back the same content all the time, maybe it&rsquo;s only fair to expect similar results.</p>
<p>On the other hand, I&rsquo;m not sure I&rsquo;m using the tool in the best way possible. The log says something like this for every request:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>source=rack-timeout id=c8ad5f0c-b5c1-47ec-b88b-3fc597ab01dc wait=29ms timeout=20000ms state=ready
Started GET &#34;/&#34; for XXX.35.10.XXX at 2016-06-03 18:54:34 &#43;0000
Processing by HomeController#home as HTML
Read fragment views/radiant-XXXX-XXXXX.herokuapp.com/en (6.0ms)
Completed 200 OK in 8ms (ActiveRecord: 0.0ms)
source=rack-timeout id=c8ad5f0c-b5c1-47ec-b88b-3fc597ab01dc wait=29ms timeout=20000ms service=19ms state=completed
source=rack-timeout id=a5389dc4-9a1a-46b7-a1e5-53f334ca0941 wait=35ms timeout=20000ms state=ready

Started GET &#34;/&#34; for XXX.35.10.XXX at 2016-06-03 18:54:36 &#43;0000
Processing by HomeController#home as HTML
Read fragment views/radiant-XXXX-XXXXX.herokuapp.com/en (6.0ms)
Completed 200 OK in 9ms (ActiveRecord: 0.0ms)
source=rack-timeout id=a5389dc4-9a1a-46b7-a1e5-53f334ca0941 wait=35ms timeout=20000ms service=21ms state=completed
at=info method=GET path=&#34;/&#34; host=radiant-XXXX-XXXXX.herokuapp.com request_id=a5389dc4-9a1a-46b7-a1e5-53f334ca0941 fwd=&#34;XXX.35.10.XXX&#34; dyno=web.1 connect=1ms service=38ms status=200 bytes=144608</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The times reported by the Boom tool are much larger (2 seconds?) than the processing times in the logs (10ms?). Even considering some overhead for the router and so on, it&rsquo;s still a big difference. I wonder if requests are being queued for too long because the app isn&rsquo;t able to respond to more of the concurrent requests.</p>
<p>The amount of requests divided by the number of concurrent connections will bring the overall performance and throughput down if you increase it too much, I wasn&rsquo;t able to go much above 14 rpm with this configuration, though.</p>
<p>If you have more experience with http benchmarking and you can spot something wrong I am doing here, please let me know in the comments section below.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>JRuby continues to evolve, and you might benefit if you already have a large set of Dynos or large servers around. I wouldn&rsquo;t recommend it for small to medium applications.</p>
<p>I&rsquo;ve seen many orders of magnitude improvements in specific use cases (I believe it was a very high traffic API endpoint). This particular case I tested is probably not its sweet spot and changing from MRI to JRuby didn&rsquo;t give me too much advantage, so in this case I would recommend sticking to MRI.</p>
<p>Startup time is still an issue. There is an entry in <a href="https://github.com/jruby/jruby/wiki/Improving-startup-time"target="_blank" rel="noopener">their Wiki</a> giving some recommendations, but even in the Heroku deploy I ended up having R10 errors (Boot Timeout) every once in a while for this small app.</p>
<p>I didn&rsquo;t try increasing the dynos to the <a href="https://blog.heroku.com/archives/2015/8/20/introducing-improved-performance-dynos"target="_blank" rel="noopener">Performance tier</a> introduced last year. I would bet that JRuby would be better at those and more able to leverage the extra power of having from 2.5GB up to 14GB if you have really big traffic (on the order of thousands or tens of thousands of requests per minute). With MRI the recommendation would be using small-ish dynos (2X or at most Performance-M dynos) and scale horizontally. With JRuby you could have fewer dynos with larger sizes (Performance-L, for example). Again, depends on your case.</p>
<p>Don&rsquo;t take the benchmarks above as &ldquo;facts&rdquo; to generalize everywhere, they are just there to give you a notion of a specific use case of mine. Your mileage will vary, so you must test it out yourself.</p>
<p>Another use case (that I did not test) is, instead of just &ldquo;porting&rdquo; an MRI app to JRuby, leveraging JRuby&rsquo;s unique strengths as <a href="https://blog.heroku.com/archives/2016/5/24/reactive_ruby_building_real_time_apps_with_jruby_and_ratpack"target="_blank" rel="noopener">this post</a> from Heroku explains, in the case of using JRuby with Ratpack, for example.</p>
<p>All in all, JRuby is still a great project to experiment with. MRI itself has come a long way as well and 2.3.1 is quite decent. Most of the time it&rsquo;s down to your overall architecture choices, and the language is just one piece of the puzzle. If you didn&rsquo;t try it yet, you definitely should. It &ldquo;just works&rdquo;.</p>
]]></content:encoded><category>rails</category><category>performance</category><category>ruby</category></item><item><title>Improving your Microservices Integration Performance with Memcache and ETAGs</title><link>https://akitaonrails.github.io/en/2016/03/23/improving-your-microservices-integration-performance-with-memcache-and-etags/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2016/03/23/improving-your-microservices-integration-performance-with-memcache-and-etags/</guid><pubDate>Wed, 23 Mar 2016 14:46:00 -0300</pubDate><description>&lt;p&gt;Everybody is into microservices. There is no way around it. In the Rails world we are well equipped to satisfy any trending Javascript Framework&amp;rsquo;s craving for API consumption.&lt;/p&gt;
&lt;p&gt;In summary, most people are just exposing their contents through simple JSON API endpoints and consuming them from other microservices through simple HTTP GETs. The more microservices they add to the chain, the longer the last endpoint takes to return. There are many techniques to improve this situation, but I want to show just a simple one that can solve many common situations without too much hassle.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Everybody is into microservices. There is no way around it. In the Rails world we are well equipped to satisfy any trending Javascript Framework&rsquo;s craving for API consumption.</p>
<p>In summary, most people are just exposing their contents through simple JSON API endpoints and consuming them from other microservices through simple HTTP GETs. The more microservices they add to the chain, the longer the last endpoint takes to return. There are many techniques to improve this situation, but I want to show just a simple one that can solve many common situations without too much hassle.</p>
<p>First of all, if you&rsquo;re dealing with caching, never try to expire cache entries. The most important thing to learn about caching is how to generate proper cache keys. Do it right and most problems with caching are gone.</p>
<p>Second, if you&rsquo;re using HTTP, try to use everything you can from it, instead of reinventing the wheel.</p>
<p>The &ldquo;TL;DR&rdquo; version is: make your APIs return proper ETAGs and handle &ldquo;If-None-Match&rdquo; headers properly, return the correct 304 status code instead of full blown 200 with full content body in the responses every time. And in the consumer end, cache the ETAG with the corresponding response body and use it from cache when you receive 304s. You will save at least expensive rendering time in the consumed end and slow bandwidth from the consumer end. In the end you should be able to at least be 100% faster, or more, with just a few tweaks.</p>
<h3>The Example Applications<span class="hx:absolute hx:-mt-20" id="the-example-applications"></span>
    <a href="#the-example-applications" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In a very very contrived example we could have a Rails API controller like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1">## 1st application</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="o">.</span><span class="n">page</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="o">[</span><span class="ss">num_pages</span><span class="p">:</span> <span class="vi">@products</span><span class="o">.</span><span class="n">num_pages</span><span class="p">,</span> <span class="ss">products</span><span class="p">:</span> <span class="vi">@products</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>For the purposes of this contrived post example, we load it up on localhost port <strong>3001</strong>. Now, whenever we call &ldquo;http://localhost:3001/api/products?page=1&rdquo; the API server dumps something like this in the log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 13:29:34 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
   (0.3ms)  SELECT COUNT(*) FROM &#34;products&#34;
  Product Load (0.9ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
Completed 200 OK in 26ms (Views: 23.0ms | ActiveRecord: 1.2ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In summary, it&rsquo;s taking around <strong>26ms</strong> to send back a JSON with the first page of products of this application. Not too bad.</p>
<p>Then we can create another Rails API application that consumes this first one. Something also very contrived and stupid like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2nd application</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># never, ever, hard code hostnames like this, use dotenv-rails or secrets.yml</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">))</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>We load this other app in localhost port <strong>3000</strong> and when we call &ldquo;http://localhost:3000/api/products?page=1&rdquo; the server dumps the following log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 13:31:59 -0300
Processing by Api::ProductsController#index as HTML
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 51ms (Views: 7.1ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, this second application is taking twice the time compared to the first one. We can assume that part of those 51ms are the 26ms of the first application.</p>
<p>The more APIs we add on top of each other, the more time the entire flow will take. 26ms for the first, another 25ms for the second, and so on.</p>
<p>There are many things we could do. But I&rsquo;d argue that we should start simple: by actually using a bit more of the HTTP protocol.</p>
<h3>Sending proper ETAGs and handling &ldquo;If-None-Match&rdquo;<span class="hx:absolute hx:-mt-20" id="sending-proper-etags-and-handling-if-none-match"></span>
    <a href="#sending-proper-etags-and-handling-if-none-match" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In a nutshell, we can tag any HTTP response with an ETAG, an identifier for the content of the response. If the ETAG is the same, we can assume the content hasn&rsquo;t changed. Web browsers receive the ETAGs and send them back if we choose to refresh the content as a &ldquo;If-None-Match&rdquo; header. When a web server receives this header it compares against the ETAG of the response and doesn&rsquo;t send back any content, just a &ldquo;304 Not Modified&rdquo; HTTP header, which is much, much lighter and faster to transport back.</p>
<p>An ETAG can be as complicated as an entire SHA256 hexdigest of the entire response content or as simple as just the &ldquo;updated_at&rdquo; timestamp if this indicates that the record has changed (in a &ldquo;show&rdquo; controller action, for example). It must be a digest that represents the content and it must change if the content changes.</p>
<p>Rails has had support for ETAGs for a long time in the <a href="http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html"target="_blank" rel="noopener">ActionController::ConditionalGet</a> API.</p>
<p>In our contrived example, the 1st application on port 3001 fetches a page of ActiveRecord objects and sends back an array represented in JSON format. If we choose to digest the final content we would have to let ActionView do its job, but it is by far the most expensive operation so we want to avoid it.</p>
<p>One thing that we could do is just check the &ldquo;updated_at&rdquo; fields of all the records and see if they changed. If any one of them changed, we would need to re-render everything and send a new ETAG and a new response body. So the code could be like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="n">layout</span> <span class="kp">false</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="o">.</span><span class="n">page</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="n">freshness</span> <span class="vi">@products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="o">[</span><span class="ss">num_pages</span><span class="p">:</span> <span class="vi">@products</span><span class="o">.</span><span class="n">num_pages</span><span class="p">,</span> <span class="ss">products</span><span class="p">:</span> <span class="vi">@products</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">freshness</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">dates</span> <span class="o">=</span> <span class="n">collection</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:updated_at</span><span class="p">)</span><span class="o">.</span><span class="n">sort</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">dates</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:to_i</span><span class="p">)</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">&amp;</span><span class="ss">:+</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">etag</span><span class="p">:</span> <span class="no">Digest</span><span class="o">::</span><span class="no">MD5</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="n">etag</span><span class="o">.</span><span class="n">to_s</span><span class="p">),</span> <span class="ss">last_modified</span><span class="p">:</span> <span class="n">dates</span><span class="o">.</span><span class="n">last</span><span class="p">,</span> <span class="kp">public</span><span class="p">:</span> <span class="kp">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, when we try to &ldquo;curl -I http://localhost:3001/api/products?page=1&rdquo; we will see the following headers:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>HTTP/1.1 200 OK 
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Etag: &#34;ccf372c24cd259d0943fa3dc99830b10&#34;
Last-Modified: Wed, 23 Mar 2016 16:25:53 GMT
Content-Type: application/json; charset=utf-8
Cache-Control: public
X-Request-Id: 601f22bc-72a9-4960-97cb-c30a0b56dbf4
X-Runtime: 0.053529
Server: WEBrick/1.3.1 (Ruby/2.3.0/2015-12-25)
Date: Wed, 23 Mar 2016 17:00:13 GMT
Content-Length: 0
Connection: Keep-Alive</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Great! We have an ETAG that uniquely represents this page of products. Now we can go one step further and add the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Gemfile</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;dalli&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">gem</span> <span class="s1">&#39;rack-cache&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># config/environments/[development|production].rb</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">cache_store</span> <span class="o">=</span> <span class="ss">:dalli_store</span>
</span></span><span class="line"><span class="cl"><span class="n">client</span> <span class="o">=</span> <span class="no">Dalli</span><span class="o">::</span><span class="no">Client</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">action_dispatch</span><span class="o">.</span><span class="n">rack_cache</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:metastore</span>    <span class="o">=&gt;</span> <span class="n">client</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:entitystore</span>  <span class="o">=&gt;</span> <span class="n">client</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">config</span><span class="o">.</span><span class="n">static_cache_control</span> <span class="o">=</span> <span class="s2">&#34;public, max-age=2592000&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This configuration is assuming that we have Memcached installed and running in the same localhost machine (our development environment), but in production you can follow this <a href="https://devcenter.heroku.com/articles/rack-cache-memcached-rails31"target="_blank" rel="noopener">good documentation from Heroku</a>.</p>
<p>Now, our 1st application has an internal HTTP cache, with the same role as something more advanced such as Varnish in front of it. It will cache all HTTP 200 responses from the application, together with the ETAGs. Whenever a new call comes for the same URI, it will check the cache first, and if the application sends back the same ETAG, it will send the content back from the cache.</p>
<p>So if we call the above &ldquo;curl&rdquo; command multiple times, we will see this from the Rails server log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:05:16 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Product Load (0.8ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
   (1.0ms)  SELECT COUNT(*) FROM &#34;products&#34;
Completed 200 OK in 31ms (Views: 16.3ms | ActiveRecord: 1.8ms)
cache: [HEAD /api/products?page=1] miss, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notice the last line: it says that it tried to find the returning ETAG in the cache and it &ldquo;missed&rdquo;, so it &ldquo;stored&rdquo; the new content. Now, if we run the same &ldquo;curl&rdquo; command again, we will see this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:05:59 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Product Load (0.5ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
Completed 304 Not Modified in 12ms (ActiveRecord: 0.5ms)
cache: [HEAD /api/products?page=1] stale, valid, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>The simple curl command is not sending the &ldquo;If-None-Match&rdquo; header, so it expects to receive the full response body. But because we have Rack Cache it is adding the &ldquo;If-None-Match&rdquo; ETAG digest from the cache to the request before hitting the Rails app. The Rails app now compares the received &ldquo;If-None-Match&rdquo; digest through the &ldquo;stale?&rdquo; methods with the ETAG it just computed and because they match, it just sends an empty body response with the status code of 304. Rack Cache receives the 304 and fetches the cached JSON from Memcached and changes the HTTP response from the 304 to a normal 200 with the full body, which is what Curl can receive.</p>
<p>Because we are skipping the expensive ActionView rendering, the response time went from the previous 26ms to around 12ms: we are now <strong>twice</strong> as fast!</p>
<h3>Consuming APIs with ETAGs<span class="hx:absolute hx:-mt-20" id="consuming-apis-with-etags"></span>
    <a href="#consuming-apis-with-etags" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>But we can go one step further. If we change nothing about the 2nd application, it will keep receiving just HTTP 200 with full body responses from Rack Cache of the 1st application. Let&rsquo;s see the code again</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2nd application</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># never, ever, hard code hostnames like this, use dotenv-rails or secrets.yml</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">get_response</span><span class="p">(</span><span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">))</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>We can do better. How about the following:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># 2nd application - upgrade!</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">ProductsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">    <span class="n">page</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="ss">:page</span><span class="o">]</span> <span class="o">||</span> <span class="s2">&#34;1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;http://localhost:3001/api/products?page=?&#34;</span> <span class="o">%</span> <span class="n">page</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1 - fetch the ETAG for the URL</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 2 - fetch from external API or fetch from internal cache</span>
</span></span><span class="line"><span class="cl">    <span class="n">json</span> <span class="o">=</span> <span class="n">fetch_with_etag</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="o">[</span><span class="s2">&#34;Content-Type&#34;</span><span class="o">]</span> <span class="o">=</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">plain</span><span class="p">:</span> <span class="n">json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="kp">private</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">fetch_with_etag</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">uri</span> <span class="o">=</span> <span class="no">URI</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">req</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Get</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 3 - add the important If-None-Match header</span>
</span></span><span class="line"><span class="cl">    <span class="n">req</span><span class="o">[</span><span class="s1">&#39;If-None-Match&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">etag</span> <span class="k">if</span> <span class="n">etag</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">res</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">uri</span><span class="o">.</span><span class="n">hostname</span><span class="p">,</span> <span class="n">uri</span><span class="o">.</span><span class="n">port</span><span class="p">)</span> <span class="p">{</span><span class="o">|</span><span class="n">http</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">      <span class="n">http</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">res</span><span class="o">[</span><span class="s1">&#39;ETAG&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">etag</span> <span class="o">=</span> <span class="n">etag</span><span class="o">[</span><span class="mi">1</span><span class="o">..-</span><span class="mi">2</span><span class="o">]</span> <span class="k">if</span> <span class="n">etag</span><span class="o">.</span><span class="n">present?</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">res</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPNotModified</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># 4 - if got a 304, then we already have the content in the internal cache</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elsif</span> <span class="n">res</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Net</span><span class="o">::</span><span class="no">HTTPSuccess</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="c1"># 5 - if we got a 200 it&#39;s new content to store in internal cache before returning</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">etag</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="no">Rails</span><span class="o">.</span><span class="n">cache</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">etag</span><span class="p">,</span> <span class="n">res</span><span class="o">.</span><span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">res</span><span class="o">.</span><span class="n">body</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;{}&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I know, feels overwhelming, but it&rsquo;s actually quite simple. Let&rsquo;s go over it step-by-step:</p>
<ol>
<li>
<p>First we see if we already have an ETAG associated with the URL we want to fetch (be aware of query parameters!)</p>
</li>
<li>
<p>Now we call the separated &ldquo;fetch_with_etag&rdquo; method</p>
</li>
<li>
<p>This is all boilerplate &ldquo;Net::HTTP&rdquo; setup, but the important piece is that we add the &ldquo;If-None-Match&rdquo; header if we found an ETAG for the URL in the cache.</p>
</li>
<li>
<p>After we make the external call we can have 2 responses. The first being the very very short, body-less, header-only, &ldquo;304 Not Modified&rdquo;. In this case, it means that we already have the full content in the internal cache and it is still valid, so we use it.</p>
</li>
<li>
<p>In case we receive the normal HTTP &ldquo;200&rdquo; status code, we either didn&rsquo;t send any ETAG or the one we sent was invalidated and a new ETAG and content body were returned, so we must update them in our internal cache before exiting.</p>
</li>
</ol>
<p>Now, the first time we call &ldquo;curl http://localhost:3000/api/products?page=1&rdquo; for the 2nd application endpoint we will see this log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:14:05 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 62ms (Views: 5.6ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Caches are cold, it is taking the same &ldquo;around 50ms&rdquo; like we had before, in this case, it&rsquo;s more like 62ms.</p>
<p>Just to recap, this call to the 2nd application made it call the 1st application API, which shows the following in its log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=?&#34; for 127.0.0.1 at 2016-03-23 14:14:05 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;?&#34;}
/&#34;32b82ebbd99854ea2ca0d49ff4a7c07c
  Product Load (0.9ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
   (1.2ms)  SELECT COUNT(*) FROM &#34;products&#34;
Completed 200 OK in 37ms (Views: 21.5ms | ActiveRecord: 2.2ms)
cache: [GET /api/products?page=?] miss, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cache miss, new content stored!</p>
<p>Now, we call &ldquo;curl&rdquo; against the same URL for the 2nd application again and we now see what we wanted in the log:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=1&#34; for 127.0.0.1 at 2016-03-23 14:14:10 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;1&#34;}
  Rendered text template (0.0ms)
Completed 200 OK in 24ms (Views: 0.3ms | ActiveRecord: 0.0ms)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Down from 62ms to 24ms!! And in the 1st application log we see:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Started GET &#34;/api/products?page=?&#34; for 127.0.0.1 at 2016-03-23 14:14:10 -0300
Processing by Api::ProductsController#index as */*
  Parameters: {&#34;page&#34;=&gt;&#34;?&#34;}
&#34;ccf372c24cd259d0943fa3dc99830b10&#34;, ccf372c24cd259d0943fa3dc99830b10
  Product Load (1.2ms)  SELECT  &#34;products&#34;.* FROM &#34;products&#34; LIMIT 100 OFFSET 0
  Couldn&#39;t find template for digesting: products/index
Completed 304 Not Modified in 12ms (ActiveRecord: 1.2ms)
cache: [GET /api/products?page=?] stale, valid, store</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A cache hit! Content is stale and valid, so return just 304, the 2nd application acknowledges and fetches the still valid content from its own cache and returns to Curl.</p>
<h3>Conclusion<span class="hx:absolute hx:-mt-20" id="conclusion"></span>
    <a href="#conclusion" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>If you remove ETAGs from the 1st application, the 2nd one will not break and vice-versa, because it&rsquo;s optional. If &ldquo;ETAG&rdquo; and &ldquo;If-None-Match&rdquo; headers are present in received HTTP response, we can use them, otherwise they will work as before.</p>
<p>If the 2nd application is itself another API you should also add ETAGs for it, and so on. In this example we didn&rsquo;t, just because I wanted to simplify the scenario. But instead of being just a simple one-to-one proxy, it could be one of those &ldquo;porcelain&rdquo; APIs that fetch data from several other smaller microservices, compile them down into a single structure and return it. You should create ETAGs that could be the returning ETAGs from all the other microservices digested together in a single ETAG, for example. Because you&rsquo;re just receiving headers and fetching their content from an internal cache, it&rsquo;s quite cheap. Something like this pseudo-code:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">index</span>
</span></span><span class="line"><span class="cl">  <span class="n">url1</span> <span class="o">=</span> <span class="s2">&#34;http://somehost1.foo/some_endpoint/1&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">url2</span> <span class="o">=</span> <span class="s2">&#34;http://somehost1.foo/some_endpoint/2&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag1</span> <span class="o">=</span> <span class="n">etag_from</span><span class="p">(</span><span class="n">url1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag2</span> <span class="o">=</span> <span class="n">etag_from</span><span class="p">(</span><span class="n">url2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">etag</span> <span class="o">=</span> <span class="no">Digest</span><span class="o">::</span><span class="no">MD5</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="n">etag1</span> <span class="o">+</span> <span class="n">etag2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="ss">etag</span><span class="p">:</span> <span class="n">etag</span><span class="p">,</span> <span class="kp">public</span><span class="p">:</span> <span class="kp">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">body1</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span> <span class="n">fetch_from</span><span class="p">(</span><span class="n">url1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">body2</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span> <span class="n">fetch_from</span><span class="p">(</span><span class="n">url2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">do_some_processing</span><span class="p">(</span><span class="n">body1</span><span class="p">,</span> <span class="n">body2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="n">result</span><span class="o">.</span><span class="n">to_json</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Another thing: you can add any vanilla HTTP Cache between your microservices, to add authorization, security, or just plain extra caching, it&rsquo;s just HTTP with proper headers. But the more you exchange &ldquo;304&rdquo; between your services, the less processing and the less bandwidth you&rsquo;re spending. It should be noticeably efficient in most cases. But again, it&rsquo;s not always cheap or trivial to generate the cache keys/ETAGs to begin with, so this is the point to take more care.</p>
<p>And if you&rsquo;re creating heavy Javascript apps that also consume those APIs, I &ldquo;believe&rdquo; the Ajax calls properly cache HTTP content and send back the correct &ldquo;If-None-Match&rdquo; and in case they receive 304s, your application should get the normal &ldquo;success&rdquo; triggers. I didn&rsquo;t test this when I was writing this post but I think this is the case indeed. So you should automatically get better performance in your front-end application for free if you add proper ETAGs in your APIs.</p>
<p>This is particularly useful for APIs that return data that doesn&rsquo;t change too often. If it changes every second, or every minute, you should not see too much gains. But if it&rsquo;s something like this example: products lists that only change once every day or every week, or ZIP code lists, or Previous Orders in an e-commerce. Any data that changes infrequently is a good candidate. And the larger the dataset, the larger the benefits you will see (if it&rsquo;s a megabyte long listing, for example). As usual, this is also no Silver Bullet, but in this case it is not so much work to add ETAGs and there are near-zero side-effects, so why not?</p>
<p>ETAG is just one of many other HTTP features you should be using, CORS is another one (Research <a href="https://github.com/cyu/rack-cors"target="_blank" rel="noopener">Rack Cors</a>).</p>
<p>If you&rsquo;re from Brazil, you should watch Nando Vieira&rsquo;s entire course on the broad subject of <a href="http://howtocode.com.br/cursos/rails-caching"target="_blank" rel="noopener">Rails Caching</a>.</p>
<p>To be honest, I&rsquo;m not sure how effective this technique can actually be in all kinds of scenarios so I am very interested in hearing your feedback in case you use something like this in your applications.</p>
]]></content:encoded><category>performance</category><category>rails</category><category>distributed-systems</category></item><item><title>The Obligatory "Flame War" Phoenix vs Node.js</title><link>https://akitaonrails.github.io/en/2015/12/03/the-obligatory-flame-war-phoenix-vs-node-js/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2015/12/03/the-obligatory-flame-war-phoenix-vs-node-js/</guid><pubDate>Thu, 03 Dec 2015 12:01:00 -0200</pubDate><description>&lt;p&gt;I&amp;rsquo;ll warn you upfront: this will be a very unfair post. Not only am I biased for disliking Javascript and Node.js, but at this moment, I am very excited and fascinated by Elixir and Erlang.&lt;/p&gt;
&lt;p&gt;Comparisons are always unfair. There is no such thing as a &amp;ldquo;fair&amp;rdquo; synthetic benchmark comparison, the author is &lt;strong&gt;always&lt;/strong&gt; biased towards some targeted result. It&amp;rsquo;s the old pseudo-science case of having a conclusion and picking and choosing data that backs those conclusions. There are just too many different variables. People think it&amp;rsquo;s fair when you run it in the same machine against 2 &amp;ldquo;similar&amp;rdquo; kinds of applications, but it is not. Please don&amp;rsquo;t trust me on this as well, do your own tests.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ll warn you upfront: this will be a very unfair post. Not only am I biased for disliking Javascript and Node.js, but at this moment, I am very excited and fascinated by Elixir and Erlang.</p>
<p>Comparisons are always unfair. There is no such thing as a &ldquo;fair&rdquo; synthetic benchmark comparison, the author is <strong>always</strong> biased towards some targeted result. It&rsquo;s the old pseudo-science case of having a conclusion and picking and choosing data that backs those conclusions. There are just too many different variables. People think it&rsquo;s fair when you run it in the same machine against 2 &ldquo;similar&rdquo; kinds of applications, but it is not. Please don&rsquo;t trust me on this as well, do your own tests.</p>
<p>All that being said, let&rsquo;s have some fun, shall we?</p>
<h3>The Obligatory &ldquo;Hello World&rdquo; warm-up<span class="hx:absolute hx:-mt-20" id="the-obligatory-hello-world-warm-up"></span>
    <a href="#the-obligatory-hello-world-warm-up" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>For this very short post, I just created a Node.js + Express &ldquo;hello world,&rdquo; I will point to its root endpoint, Express, rendering a super simple HTML template with one title and one paragraph.</p>
<p>For Elixir, I just bootstrapped a bare-bone Phoenix project and added one extra endpoint called &ldquo;/teste&rdquo; in the Router. This endpoint will call the PageController, then the &ldquo;teste&rdquo; function, and render an EEX template with the same title and paragraph as in the Express example.</p>
<p>Simple enough. Phoenix does more than Express, but this is not supposed to be a fair trial anyway. I chose <a href="https://www.joedog.org/siege-home/"target="_blank" rel="noopener">Siege</a> as the testing tool for no particular reason. You can pick the testing tool you like the most. I am running this over my 2013 Macbook Pro with 8 cores and 16GB of RAM, so this benchmark will never max out my machine.</p>
<p>The first test is a simple run of 200 concurrent connections (the number of CPUs I have) firing just 8 requests each for 1,600. First, the Node.js + Express results:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+8+x+200.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+8+x+200.mp4)
</source></video>
<p>The first run already broke a few connections, but the 2nd run picked up and finished all 1,600 requests. And this is the Phoenix results:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+8+x+200.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+8+x+200.mp4)
</source></video>
<p>As you can see, Node.js has the upper hand in terms of the total time spent. One single Node.js process can only run one single real OS thread. Therefore it had to use just a single CPU core (although I had 7 other cores to spare). On the other hand, Elixir can reach all 8 cores of my machine, each running a Scheduler in a single real OS thread. So, if this test was CPU bound, it should have run 8 times faster than the Node.js version. As the test is largely a case of I/O bound operations, the clever async construction of Node.js does the job just fine.</p>
<p>This is not an impressive test by any stretch of the imagination. But we&rsquo;re just warming up.</p>
<p>Oh, and by the way, notice how Phoenix logs show the request processing times in MICROseconds instead of milliseconds!</p>
<h3>The Real Fun<span class="hx:absolute hx:-mt-20" id="the-real-fun"></span>
    <a href="#the-real-fun" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Now comes the real fun. In this second run, I added a blocking &ldquo;sleep&rdquo; call to both projects, so each request will sleep for one full second, and this is not absurd. Many programmers will use poor code that blocks for that time, process too much data from the database, render templates that are too complex, and so on. Never trust a programmer to follow best practices all the time.</p>
<p>Then, I fire up Siege with ten concurrent connections and just one request each, for starters.</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+1+x+10+Sleep.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Node+1+x+10+Sleep.mp4)
</source></video>
<p>This is why in my previous article &ldquo;Why Elixir?&rdquo; (<a href="http://www.akitaonrails.com/en/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal%29"target="_blank" rel="noopener">http://www.akitaonrails.com/en/2015/12/01/o-obrigatorio-por-que-elixir-visao-pessoal)</a>, I repeated many times how <strong>&ldquo;rudimentary&rdquo;</strong> a Reactor pattern-based solution is. It is super easy to block a single-threaded event loop.</p>
<p>If you didn&rsquo;t know that already, how does Node.js work? In summary, it is a simple infinite loop. When a Javascript function runs, it blocks that event loop. The function has to explicitly yield control back to the loop for another function to have a chance to run. I/O calls take time and just sit back idly waiting for a response, so it can yield control back and wait for a callback to continue running, which is why you end up with the dreaded <em>&ldquo;callback pyramid hell&rdquo;</em>.</p>
<p>Now, with what I explained in all my previous articles, you may already know how Elixir + Phoenix will perform:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+2+x+400+-+Sleep.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/Phoenix+2+x+400+-+Sleep.mp4)
</source></video>
<p>As expected, this is a walk in the park for Phoenix. It doesn&rsquo;t have a rudimentary single-thread loop waiting for the running functions to yield control back willingly. The Schedulers can forcefully suspend running coroutines/processes if they think they are taking too much time (the 2,000 reductions count and priority configurations), so every running process has a fair share of resources to run.</p>
<p>Because of that, I can keep increasing the number of requests and concurrent connections, and it&rsquo;s still <strong>fast</strong>.</p>
<p>In Node.js, if a function takes 1 second to run, it blocks the loop. When it finally returns, the next 1-second function can run. That&rsquo;s why if I have ten requests taking 1 second each to run, the entire process will linearly take 10 seconds!</p>
<p>Which obviously does not scale! If you &ldquo;do it right,&rdquo; you can scale. But why bother?</p>
<h3>&ldquo;Node&rdquo; done right<span class="hx:absolute hx:-mt-20" id="node-done-right"></span>
    <a href="#node-done-right" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As a side note, I find it ironic that &ldquo;Node&rdquo; is called &ldquo;Node.&rdquo; I would assume that connecting multiple Nodes that communicate with each other should be easy. And as a matter of fact, <a href="http://www.sitepoint.com/how-to-create-a-node-js-cluster-for-speeding-up-your-apps/"target="_blank" rel="noopener">it is not</a>.</p>
<p>If I had spun up 5 Node processes, instead of 10 seconds, everything would take 2 seconds as five requests would block the 5 Node processes for 1 second, and when returned, the next five requests would block again. This is similar to what we need to do with Ruby or Python, which have the dreaded big Global Interpreter Locks (GIL) that, in reality, can only run one blocking computation at a time. (Ruby with Eventmachine and Python with Tornado or Twisted are similar to Node.js implementation of a reactor event loop).</p>
<p>Elixir can do much better in terms of actually coordinating different nodes, and it is the Erlang underpinnings that allow highly distributed systems such as <a href="https://www.ejabberd.im/"target="_blank" rel="noopener">ejabberd</a> or <a href="https://www.rabbitmq.com/"target="_blank" rel="noopener">RabbitMQ</a> to do their thing as efficiently as they can.</p>
<p>Check out how simple it is for one Elixir Node to notice the presence of other Elixir nodes and make them send and receive messages between each other:</p>
<video controls>
<source src="https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/7+-+Nodes.mp4">
Your browser does not support the video tag. [Direct Link](https://s3.us-east-2.amazonaws.com/blip.tv/Elixir/7+-+Nodes.mp4)
</source></video>
<p>Yep, it is this simple. We have been using Remote Procedure Calls (RPC) for decades; this is not new. Erlang has implemented this for years, and it is built-in and available for easy usage out-of-the-box.</p>
<p>On their websites, ejabberd calls itself a &ldquo;Robust, Scalable, and Extensible XMPP Server,&rdquo; and RabbitMQ calls itself &ldquo;Robust messaging for applications.&rdquo; Now we know they deserve the labels &ldquo;Robust&rdquo; and &ldquo;Scalable.&rdquo;</p>
<p>So, we are struggling to do things that have been polished and ready for years. Elixir is the key to unlocking all this Erlang goodness right now. Let&rsquo;s just use it and stop shrugging.</p>
]]></content:encoded><category>performance</category><category>elixir-and-erlang</category><category>javascript</category></item><item><title>Ex Manga Downloadr - Part 2: Poolboy to the rescue!</title><link>https://akitaonrails.github.io/en/2015/11/19/ex-manga-downloadr-part-2-poolboy-to-the-rescue/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2015/11/19/ex-manga-downloadr-part-2-poolboy-to-the-rescue/</guid><pubDate>Thu, 19 Nov 2015 14:35:00 -0200</pubDate><description>&lt;p&gt;If you read my &lt;a href="http://www.akitaonrails.com/en/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener"&gt;previous article&lt;/a&gt; I briefly described my exercise building a MangaReader downloader. If you didn&amp;rsquo;t read it yet, I recommend you do so before continuing.&lt;/p&gt;
&lt;p&gt;In the mid-section of the article I described how I was still puzzled about what would be the best way to handle several HTTP requests to an unstable external source where I can&amp;rsquo;t control timeout or other network problems.&lt;/p&gt;
&lt;p&gt;A big Manga such as Naruto or Bleach has dozens of chapters with dozens of pages each, accounting for thousands of necessary HTTP requests. Elixir/Erlang allow me to fire up as many parallel HTTP requests as I want. But doing that makes the HTTP requests time out very quickly (it&amp;rsquo;s like doing a distributed denial of service attack against MangaReader).&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you read my <a href="http://www.akitaonrails.com/en/2015/11/18/ex-manga-downloadr-um-exercicio-com-elixir"target="_blank" rel="noopener">previous article</a> I briefly described my exercise building a MangaReader downloader. If you didn&rsquo;t read it yet, I recommend you do so before continuing.</p>
<p>In the mid-section of the article I described how I was still puzzled about what would be the best way to handle several HTTP requests to an unstable external source where I can&rsquo;t control timeout or other network problems.</p>
<p>A big Manga such as Naruto or Bleach has dozens of chapters with dozens of pages each, accounting for thousands of necessary HTTP requests. Elixir/Erlang allow me to fire up as many parallel HTTP requests as I want. But doing that makes the HTTP requests time out very quickly (it&rsquo;s like doing a distributed denial of service attack against MangaReader).</p>
<p>By trial and error I found that firing up fewer than 100 HTTP requests at once allows me to finish. I capped it at 80 to be sure, but it really depends on your environment.</p>
<p>Then I had to manually chunk my list of pages into groups of 80 elements and process them in parallel, finally reducing the resulting lists back into a larger list to pass through to the next steps in the Workflow. The code gets convoluted like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">images_sources</span><span class="p">(</span><span class="n">pages_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="n">chunk</span><span class="p">(</span><span class="vi">@maximum_fetches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="n">pages_chunk</span><span class="p">,</span> <span class="n">acc</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="n">result</span> <span class="o">=</span> <span class="n">pages_chunk</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">        <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">acc</span> <span class="o">++</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now I was able to reimplement this aspect and the same code now looks like this:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">images_sources</span><span class="p">(</span><span class="n">pages_list</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">page_image</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Wow! Now this is a big improvement and it&rsquo;s way more obvious what it&rsquo;s doing.</p>
<p>Best of all: downloading a Manga the size of Akira (around 2,200 pages long) took less than <strong>50 seconds</strong>. And this is not because Elixir is super fast, it&rsquo;s because MangaReader can&rsquo;t keep up if I extend the Pool size. It&rsquo;s hitting at a constant rate of 50 concurrent connections!</p>
<p>This makes my 4-core machine, sitting on a 40 Mbps connection, use less than 30%-40% of CPU and no more than around 3.5 Mbps. If MangaReader could keep up we could easily fetch all pages 2 or 3 times faster without breaking a sweat.</p>
<p>It was fast with the previous strategy, but I guess it got at least twice as fast as a bonus now. But how did I accomplish that?</p>
<h3>Open Telecom Platform<span class="hx:absolute hx:-mt-20" id="open-telecom-platform"></span>
    <a href="#open-telecom-platform" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>In the previous article I also said that I didn&rsquo;t want to dive into &ldquo;OTP and GenServers&rdquo; just yet. But if you&rsquo;re a beginner like me you probably didn&rsquo;t understand what this means.</p>
<p>OTP is what makes Erlang (and consequently, Elixir) different from pretty much every other language platform but, maybe, Java.</p>
<p>Many new languages today let you do many tasks in parallel through convoluted Reactor patterns (Node.js, EventMachine/Ruby, Tornado/Twisted/Python, etc) or through (cleaner) green threads (Scala, Go).</p>
<p>But none of this matters. It&rsquo;s not difficult to launch millions of lightweight processes, but it is not trivial to actually <strong>CONTROL</strong> them all. It doesn&rsquo;t matter how fast you can exhaust your hardware if you can&rsquo;t control it. You end up with millions of dumb minions wreaking havoc without an adult to coordinate them.</p>
<p>Erlang solved this problem decades ago through the development of OTP, initially called Open Systems, within Ericsson in 1995. By itself OTP is a subject that can easily fill an entire fat book and you will still not be able to call yourself an expert.</p>
<p>Just so I don&rsquo;t get too boring here:</p>
<ul>
<li>Start with <a href="http://learnyousomeerlang.com/what-is-otp"target="_blank" rel="noopener">this very brief summary</a>;</li>
<li>Then read <a href="http://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html"target="_blank" rel="noopener">Elixir&rsquo;s tutorial on Supervisors</a> and the page on <a href="http://elixir-lang.org/getting-started/processes.html"target="_blank" rel="noopener">Processes</a> first if you haven&rsquo;t already;</li>
<li>Finally, you can go further with <a href="https://www.manning.com/books/the-little-elixir-and-otp-guidebook"target="_blank" rel="noopener">The Elixir &amp; OTP Guidebook</a> from Manning Publications.</li>
</ul>
<p>Now, below is my personal point of view. As I am a beginner myself, let me know in the comments section below if I got some concept wrong.</p>
<p>OTP is a collection of technologies and frameworks. The part that interests us the most is to understand that this is a sophisticated collection of patterns to achieve the Nirvana of highly reliable, highly scalable, distributed systems. You know? That thing every new platform promises you but fails to actually deliver.</p>
<p>For our very simple intents and purposes, let&rsquo;s pick up where I said before: it&rsquo;s trivial to fire up millions of small processes. We call them &ldquo;workers&rdquo;. OTP provides the means to control them: Supervisors. And then it also provides the concept of Supervisor Trees (Supervisors that supervise other Supervisors). This is the gist of it.</p>
<p>Supervisors are responsible for starting up the workers and also for recovering from exceptions coming from the workers (which is why in Erlang/Elixir we don&rsquo;t do ugly try/catch stuff: let the error be raised and caught by the Supervisor). Then we can configure the Supervisor to deal with a faulty worker by, for example, restarting it.</p>
<p>We already touched on this OTP stuff before. An Elixir <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/task.ex"target="_blank" rel="noopener">Task</a> is just a high-level abstraction. It internally starts its own <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/task/supervisor.ex"target="_blank" rel="noopener">supervisor</a> to watch over asynchronous tasks.</p>
<p>There are so many subjects and details that it&rsquo;s difficult to even get started. One concept that is important to know is about <strong>state</strong>. There is no global state! (Yay, no JavaScript globals nightmare.) Each function has its own state and that&rsquo;s it. There is no concept of an &ldquo;object&rdquo; that holds state and then methods that can modify that state.</p>
<p>But there is the concept of Erlang processes. Now, a process does have state, it&rsquo;s a lightweight piece of state that exists only at runtime. To execute a function in a separate, parallel process, you can just do:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="n">spawn</span> <span class="n">fn</span> <span class="o">-&gt;</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">2</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1">#PID&lt;0.43.0&gt;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Unlike an object, a process does not have a set of methods that access its inner &ldquo;this&rdquo; or &ldquo;self&rdquo; state. Instead each process has a <strong>mailbox</strong>. When you start (or &ldquo;spawn&rdquo; in Erlang lingo) a new process, it returns a pid (process ID). You can now send messages to the process through its pid. Each process has a mailbox and you can choose to respond to incoming messages and send responses back to the pid that sent the message. This is how you can send a message to the IEx console and receive the messages in its mailbox:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="nb">send</span> <span class="nb">self</span><span class="p">(),</span> <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">&#34;world&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="s2">&#34;world&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">iex</span><span class="o">&gt;</span> <span class="n">receive</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span>   <span class="p">{</span><span class="ss">:hello</span><span class="p">,</span> <span class="n">msg</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">msg</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span>   <span class="p">{</span><span class="ss">:world</span><span class="p">,</span> <span class="n">msg</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="s2">&#34;won&#39;t match&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...&gt;</span> <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;world&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In essence, it&rsquo;s almost like an &ldquo;object&rdquo; that holds state. Each process has its own garbage collector, so when it dies it&rsquo;s individually collected. And each process is isolated from other processes; they don&rsquo;t bleed state out, which makes them much easier to reason about.</p>
<p>The <a href="http://elixir-lang.org/getting-started/processes.html"target="_blank" rel="noopener">Getting Started page on Processes</a> from the Elixir website shows examples of what I just explained and I recommend you follow it thoroughly.</p>
<p>In summary, a process can hold internal state by blocking indefinitely while waiting for an incoming message in its mailbox and then recursing into itself! This is a mind-blowing concept at first.</p>
<p>But a simple process is just too damn weak. This is where you get <a href="http://elixir-lang.org/getting-started/mix-otp/genserver.html"target="_blank" rel="noopener">OTP&rsquo;s GenServer</a>, which is a much more accomplished process. OTP exposes Behaviours for you to implement in order to add your own code but it takes care of the dirty infrastructure stuff so you don&rsquo;t have to.</p>
<h3>Deferring the Heavy Workflow calls to a Worker<span class="hx:absolute hx:-mt-20" id="deferring-the-heavy-workflow-calls-to-a-worker"></span>
    <a href="#deferring-the-heavy-workflow-calls-to-a-worker" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>All that said, we know that in the Workflow we implemented before, we have trouble with the <tt>Page.image/1</tt> and <tt>Workflow.download_image/2</tt> functions. This is why we made them asynchronous processes and we wait for batches of 80 calls every time.</p>
<p>Now, let&rsquo;s start by moving this logic away to a GenServer Worker, for example, in the <tt>ex_manga_downloadr/pool_management/worker.ex</tt> file:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">GenServer</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">ExMangaDownloadr</span><span class="o">.</span><span class="n">MangaReader</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="vi">@timeout_ms</span> <span class="mi">1_000_000</span>
</span></span><span class="line"><span class="cl">  <span class="vi">@transaction_timeout_ms</span> <span class="mi">1_000_000</span> <span class="c1"># larger just to be safe</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">GenServer</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="kp">nil</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:page_image</span><span class="p">,</span> <span class="n">page_link</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="n">page_link</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">handle_call</span><span class="p">({</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="n">_from</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:reply</span><span class="p">,</span> <span class="no">Page</span><span class="o">.</span><span class="n">download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">),</span> <span class="n">state</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>I first moved the <tt>Workflow.download_image/2</tt> to <tt>Page.download_image/2</tt> just for consistency&rsquo;s sake. But this is a GenServer in a nutshell. We have some setup in the <tt>start_link/1</tt> function and then we have to implement <tt>handle_call/3</tt> functions to handle each kind of argument it might receive. We separate them through pattern matching the arguments.</p>
<p>As a convention, we can add public functions that are just prettier versions that call each <tt>handle_call/3</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">page_image</span><span class="p">(</span><span class="n">page_link</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_image</span><span class="p">,</span> <span class="n">page_link</span><span class="p">},</span> <span class="vi">@timeout_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span><span class="p">,</span> <span class="vi">@transaction_timeout_ms</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">page_download_image</span><span class="p">(</span><span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Task</span><span class="o">.</span><span class="n">async</span> <span class="n">fn</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">transaction</span> <span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">fn</span><span class="p">(</span><span class="n">server</span><span class="p">)</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="no">GenServer</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">server</span><span class="p">,</span> <span class="p">{</span><span class="ss">:page_download_image</span><span class="p">,</span> <span class="n">image_data</span><span class="p">,</span> <span class="n">directory</span><span class="p">},</span> <span class="vi">@timeout_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span><span class="p">,</span> <span class="vi">@transaction_timeout_ms</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But we are not just calling the previous <tt>handle_call/3</tt> functions. First there is the <tt>Task.async/1</tt> we were already using in the Workflow functions to make the parallel batches. But inside the Task calls there is this other strange thing: <tt>:poolboy</tt>.</p>
<h3>Controlling Pools of Processes through Poolboy<span class="hx:absolute hx:-mt-20" id="controlling-pools-of-processes-through-poolboy"></span>
    <a href="#controlling-pools-of-processes-through-poolboy" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>The entire OTP ordeal I wrote here was just an introduction so I could show off <a href="https://github.com/devinus/poolboy"target="_blank" rel="noopener">Poolboy</a>.</p>
<p>Repeating myself again: it&rsquo;s trivial to fire up millions of processes. OTP is how we control failures to those processes. But there is another problem: the computation within each process may be so heavy we can either bring down the machine or, in our case, do a Distributed Denial of Service (DDoS) against the poor MangaReader website.</p>
<p>My initial idea was to just do parallel requests in batches. But the logic is convoluted.</p>
<p>Instead, we can use a process pool! It queues up our requests for new processes. Whenever a process finishes, it is returned to the pool and a new computation can take over the available process. This is how pools work (you probably have an intuition of how it works from traditional database connection pools). Pools and queues are useful software constructs to deal with limited resources.</p>
<p>By doing this we can remove the chunking of the large list into batches and process every element of the large list in parallel at once, repeating again the initial version:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="n">chunk</span><span class="p">(</span><span class="vi">@maximum_fetches</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">,</span> <span class="n">fn</span> <span class="n">pages_chunk</span><span class="p">,</span> <span class="n">acc</span> <span class="o">-&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="n">result</span> <span class="o">=</span> <span class="n">pages_chunk</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">      <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">acc</span> <span class="o">++</span> <span class="n">result</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, removing the chunking and reducing logic:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">async</span><span class="p">(</span><span class="n">fn</span> <span class="o">-&gt;</span> <span class="no">Page</span><span class="o">.</span><span class="n">image</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">)</span> <span class="k">end</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="p">(</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@http_timeout</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>And finally, replacing the direct <tt>Task.async/1</tt> call with the GenServer worker we just implemented above:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">pages_list</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Worker</span><span class="o">.</span><span class="n">page_image</span><span class="o">/</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="o">&amp;</span><span class="no">Task</span><span class="o">.</span><span class="n">await</span><span class="p">(</span><span class="o">&amp;</span><span class="mi">1</span><span class="p">,</span> <span class="vi">@await_timeout_ms</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="o">|&gt;</span> <span class="no">Enum</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">fn</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="n">image</span><span class="p">}</span> <span class="o">-&gt;</span> <span class="n">image</span> <span class="k">end</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, Poolboy will require a Supervisor that monitors our Worker. Let&rsquo;s put it under <tt>ex_manga_downloadr/pool_management/supervisor.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Supervisor</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Supervisor</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start_link</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">__MODULE__</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="n">pool_options</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="nb">name</span><span class="p">:</span> <span class="p">{</span><span class="ss">:local</span><span class="p">,</span> <span class="ss">:worker_pool</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="ss">worker_module</span><span class="p">:</span> <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Worker</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">size</span><span class="p">:</span> <span class="mi">50</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="ss">max_overflow</span><span class="p">:</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">children</span> <span class="o">=</span> <span class="o">[</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:poolboy</span><span class="o">.</span><span class="n">child_spec</span><span class="p">(</span><span class="ss">:worker_pool</span><span class="p">,</span> <span class="n">pool_options</span><span class="p">,</span> <span class="o">[]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">supervise</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="ss">strategy</span><span class="p">:</span> <span class="ss">:one_for_one</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>More OTP goodness here. We had a rogue Worker, now we have a responsible Supervisor deferring responsibility to Poolboy. We start with a pool that can hold a maximum of 50 processes within (without overflowing). This number comes from trial and error again. And the Supervisor will use a strategy of <tt>:one_for_one</tt>, which means that if the Worker dies it restarts it.</p>
<p>Now, we must add Poolboy to the <tt>mix.exs</tt> as a dependency and run <tt>mix deps.get</tt> to fetch it:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defp</span> <span class="n">deps</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span><span class="ss">:poolboy</span><span class="p">,</span> <span class="ss">github</span><span class="p">:</span> <span class="s2">&#34;devinus/poolboy&#34;</span><span class="p">,</span> <span class="ss">tag</span><span class="p">:</span> <span class="s2">&#34;1.5.1&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>In the same <tt>mix.exs</tt> we make the main Application (surprise: which is <a href="https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/application.ex"target="_blank" rel="noopener">already a supervised OTP application</a>) start the PoolManagement.Supervisor for us:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">application</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="o">[</span><span class="ss">applications</span><span class="p">:</span> <span class="o">[</span><span class="ss">:logger</span><span class="p">,</span> <span class="ss">:httpotion</span><span class="p">,</span> <span class="ss">:porcelain</span><span class="o">]</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="ss">mod</span><span class="p">:</span> <span class="p">{</span><span class="no">PoolManagement</span><span class="p">,</span> <span class="o">[]</span><span class="p">}</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>But we also need to have this <tt>PoolManagement</tt> module for it to call. We may call it <tt>pool_management.ex</tt>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">defmodule</span> <span class="no">PoolManagement</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">  <span class="n">use</span> <span class="no">Application</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="no">PoolManagement</span><span class="o">.</span><span class="n">Supervisor</span><span class="o">.</span><span class="n">start_link</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Summary<span class="hx:absolute hx:-mt-20" id="summary"></span>
    <a href="#summary" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Let&rsquo;s summarize:</p>
<ol>
<li>the <tt>ExMangaDownloadr</tt> application will start up and fire up the <tt>PoolManagement</tt> application;</li>
<li>the <tt>PoolManagement</tt> application fires up the <tt>PoolManagement.Supervisor</tt>;</li>
<li>the <tt>PoolManagement.Supervisor</tt> fires up Poolboy and assigns <tt>PoolManagement.Worker</tt> to it, configuring its pool size to 50 and responding to the pool name <tt>:worker_pool</tt>;</li>
<li>now we start to fetch and parse through the Manga&rsquo;s chapters and pages until <tt>ExMangaDownloadr.Workflow.images_sources/1</tt> is called;</li>
<li>it will call the <tt>PoolManagement.Worker.page_image/1</tt> function which in turn fires up a <tt>Task.async/1</tt>, calling <tt>:poolboy.transaction(:worker_pool, fn -&gt; &hellip; end)</tt>;</li>
<li>if a process is available in Poolboy&rsquo;s pool it starts right away; otherwise it waits for a process to become available, up to the <tt>@transaction_timeout_ms</tt> timeout configuration.</li>
<li>the process maps the entire <tt>pages_list</tt>, creating one async Task for each page in the list. We end up with a ginormous list of Task pids, then we <tt>Task.await/2</tt> for them all to return.</li>
</ol>
<p>Now, this application is much more reliable and faster as it fires up a new HTTP connection as soon as the first one responds and it returns the process back to the pool. Instead of firing up 80 connections at a time, in batches, we start with 50 at the same time and then we fire up one at a time for each process returned to the pool.</p>
<p>Through trial and error I set the <tt>@http_timeout</tt> to wait at most 60 seconds. I also set the <tt>timeout_ms</tt>, which is the time to wait for the GenServer worker call handler to return, and <tt>transaction_timeout_ms</tt>, which is the time Poolboy waits for a new process in the pool, both to around 16 minutes (1,000,000 ms).</p>
<p>This is putting 25 years of Erlang experience in the Telecom industry to good use!</p>
<p>And to make it crystal clear: OTP is the thing that sets Erlang/Elixir apart from all the rest. It&rsquo;s not the same thing, but it&rsquo;s like if the standard were to write everything in Java as an EJB, ready to run inside a JEE Container. What comes to mind is: heavy.</p>
<p>In Erlang, an OTP application is lightweight, you can just build and use it ad hoc, without bureaucracy, without having to set up complicated servers. As in our case, it&rsquo;s a very simple command line tool, and within it, the entire power of a JEE Container! Think about it.</p>
]]></content:encoded><category>elixir-and-erlang</category><category>performance</category><category>distributed-systems</category></item><item><title>The New Kid on the Block for Ruby Servers: Raptor!</title><link>https://akitaonrails.github.io/en/2014/10/19/the-new-kid-on-the-block-for-ruby-servers-raptor/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2014/10/19/the-new-kid-on-the-block-for-ruby-servers-raptor/</guid><pubDate>Sun, 19 Oct 2014 15:00:00 -0200</pubDate><description>&lt;p&gt;&lt;em&gt;Update (25/11/2014):&lt;/em&gt; We can finally confirm that yes, the awesome Raptor project is the codename for the next release of Phusion Passenger itself! Read the &lt;a href="http://blog.phusion.nl/2014/11/25/introducing-phusion-passenger-5-beta-1-codename-raptor/"target="_blank" rel="noopener"&gt;announcement&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a seasoned Ruby web developer you&amp;rsquo;re probably familiar and comfortable using the usual suspects to deploy your applications. You&amp;rsquo;re either deploying something simple through good old Thin, or you&amp;rsquo;re orchestrating several Ruby processes through Unicorn workers, or you&amp;rsquo;re trying out JRuby and want better concurrency and thread management, and therefore you&amp;rsquo;re using either Puma or Torquebox.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>Update (25/11/2014):</em> We can finally confirm that yes, the awesome Raptor project is the codename for the next release of Phusion Passenger itself! Read the <a href="http://blog.phusion.nl/2014/11/25/introducing-phusion-passenger-5-beta-1-codename-raptor/"target="_blank" rel="noopener">announcement</a></p>
<p>If you&rsquo;re a seasoned Ruby web developer you&rsquo;re probably familiar and comfortable using the usual suspects to deploy your applications. You&rsquo;re either deploying something simple through good old Thin, or you&rsquo;re orchestrating several Ruby processes through Unicorn workers, or you&rsquo;re trying out JRuby and want better concurrency and thread management, and therefore you&rsquo;re using either Puma or Torquebox.</p>
<p>And even though it feels like we are already at the peak of what&rsquo;s possible with Ruby, we do want more. Ruby 2.1.3 was just released, 3.0 is in development. But until then it should be possible to squeeze some more performance out of your boxes.</p>
<p>In fact, a new contender with some new approaches just showed up. I have few details so far, but it&rsquo;s a brand new product - not derived from the others - from an unknown team. This product is named <a href="http://www.rubyraptor.org"target="_blank" rel="noopener">&ldquo;Raptor&rdquo;</a> and they claim that it can squeeze the extra juice out of our boxes.</p>
<p>And this is their claim:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/475/chart-1.png" alt="Raptor Chart"  loading="lazy" /></p>
<p>Fortunately, I had the chance to experiment with its beta release in a controlled Vagrant environment and check those claims! Bear in mind that those are synthetic benchmarks and real world throughput of real applications should give us different behavior. With that having been said, let&rsquo;s see how the main Ruby servers perform a very simple Rack app returning a simple HTTP 200 with &ldquo;Hello World&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code> ==&gt; Benchmark parameters:
     Application         : hello_app
     Operating system    : Ubuntu 14.04 LTS (x86_64)
     Virtual CPU cores   : 4
     MRI Ruby            : ruby 2.1.3p242 (2014-09-19 revision 45877) [x86_64-linux-gnu]
     JRuby               : jruby 1.7.13 (1.9.3p392) 2014-06-24 43f133c on OpenJDK 64-Bit Server VM 1.7.0_65-b32 [linux-amd64]

     Unicorn workers     : 8
     Puma workers (MRI)  : 8 (16 threads each)
     Puma workers (JRuby): 1 (32 threads each)
     Torquebox threads   : 32
     Raptor workers      : 8

     Concurrent clients  : 16

 ==&gt; Benchmark summary:
     Unicorn (MRI Ruby)         : 23015.36 req/sec
     Puma (MRI Ruby)            : 32538.62 req/sec
     Puma (JRuby)               : 399.14 req/sec
     Torquebox (JRuby)          : 29773.21 req/sec
     Raptor (MRI Ruby)          : 95309.35 req/sec
     Raptor (JRuby)             : 87523.65 req/sec</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Now, this sounds very impressive indeed! And what about a more complex example: a simple Rails 4.1.6 application rendering a default scaffold index page with a model fetching 20 rows from MySQL:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>==&gt; Benchmark summary:
    Unicorn (MRI Ruby)         : 326.49 req/sec
    Puma (MRI Ruby)            : 327.36 req/sec
    Puma (JRuby)               : 221.78 req/sec
    Torquebox (JRuby)          : 226.57 req/sec
    Raptor (MRI Ruby)          : 79617.78 req/sec
    Raptor (JRuby)             : 73948.59 req/sec</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Again, <strong>super</strong> impressive. My first impression when reading these numbers is that Raptor must have built-in response caching (which is great!). So I tweaked the example to make it much heavier than what&rsquo;s considered &ldquo;normal&rdquo; for a small cache, rendering a table of 100 rows from the database in the index page, and the results are still competitive:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>==&gt; Benchmark summary:
    Unicorn (MRI Ruby)         : 85.98 req/sec
    Puma (MRI Ruby)            : 89.93 req/sec
    Puma (JRuby)               : 79.42 req/sec
    Torquebox (JRuby)          : 77.98 req/sec
    Raptor (MRI Ruby)          : 82.63 req/sec
    Raptor (JRuby)             : 88.92 req/sec</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>This could mean that in the best case scenario, you could get almost <strong>4 times</strong> the throughput from your application, and in the worst case scenario, you&rsquo;re still getting similar throughputs. This means that Raptor is adaptable and reasonably &ldquo;smart&rdquo;. So overall it&rsquo;s a win-win situation!</p>
<p>The way they are able to achieve these superior numbers can be explained by the way they approached the implementation. This is the breakdown that they have released so far:</p>
<ul>
<li><strong>Protection against slow clients</strong>: Slow clients on the Internet can block your app, like people standing still in front of your door. Unicorn only has a single small door, so it <a href="http://unicorn.bogomips.org/PHILOSOPHY.html"target="_blank" rel="noopener">requires</a> you to attach a buffering reverse proxy in front of it, like Nginx. Puma has as many small doors as you configure threads, so you still need to attach Nginx in front to be safe. But Raptor has a door that&rsquo;s almost infinitely wide, and fully protects your app against slow clients without additional software.</li>
<li><strong>Optional integration with Nginx</strong>. Nginx is a great web server and provides useful features like compression, security, etc. But using Puma and Unicorn with Nginx requires you to configure Puma/Unicorn separately, to configure Nginx separately, to configure monitoring separately, and to manage each component separately. Doesn&rsquo;t it make sense to consolidate all these moving parts into a single package? Enter Raptor&rsquo;s optional Nginx integration mode: configuration, management and daemon monitoring all directly from Nginx.</li>
<li><strong>Multitenancy</strong>. If you run multiple apps on the same server, then managing all those Puma and Unicorn instances quickly becomes tedious. Raptor&rsquo;s Nginx integration mode allows you to manage all your apps from a single Nginx instance.</li>
<li><strong>Security</strong>. Improve your server&rsquo;s security by easily running multiple apps under multiple user accounts. This way, a vulnerability in one app won&rsquo;t affect all your other apps.</li>
<li><strong>JRuby support</strong>. Like Puma (and unlike Unicorn), Raptor has great JRuby support.</li>
</ul>
<p>So it&rsquo;s a different implementation and also brings features for the future, transparently handling slow clients without causing bottlenecks for your application and avoiding extra layers of protection. And everything in an easy-to-use package (according to their release: you will be able to replace your current server in the Gemfile and bundle install it!)</p>
<p>Raptor is about to be released, so stay tuned for more news and for when it&rsquo;s available for everybody to test their applications with. From what we have seen so far, this one looks like a winner!</p>
<p>Go to <a href="http://www.rubyraptor.org"target="_blank" rel="noopener">their website</a> to know more about it and if you&rsquo;re like me and feel like this is the real deal, <a href="https://www.thunderclap.it/projects/17748-raptor-fast-ruby-web-server"target="_blank" rel="noopener">Thunderclap it!</a></p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/473/raptor_square.png" alt="Raptor"  loading="lazy" /></p>
]]></content:encoded><category>performance</category><category>rails</category><category>ruby</category></item><item><title>Chatting with Joshua Peek</title><link>https://akitaonrails.github.io/en/2008/09/27/chatting-with-joshua-peek/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2008/09/27/chatting-with-joshua-peek/</guid><pubDate>Fri, 26 Sep 2008 02:32:00 -0300</pubDate><description>&lt;p&gt;&lt;a href="http://www.locaweb.com.br/railssummit"target="_blank" rel="noopener"&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2008/8/1/468x60.gif" alt="" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This time I had some time with &lt;a href="http://joshpeek.com/"target="_blank" rel="noopener"&gt;&lt;strong&gt;Joshua Peek&lt;/strong&gt;&lt;/a&gt;. He was recently &lt;a href="http://weblog.rubyonrails.org/2008/8/16/josh-peek-officially-joins-the-rails-core"target="_blank" rel="noopener"&gt;announced&lt;/a&gt; as a new member of the Rails Core Team.&lt;/p&gt;
&lt;p&gt;He got the honor because of his hard work for the upcoming Rails 2.2, solving the &lt;strong&gt;thread-safety&lt;/strong&gt; issue within Rails. Because of his Google Summer of Code project, guided by Michael Koziarski, they were able to make Ruby on Rails truly Thread-Safe, which could be a great boost for virtual machines with native-threading support, such as JRuby.&lt;/p&gt;</description><content:encoded><![CDATA[<p><a href="http://www.locaweb.com.br/railssummit"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/8/1/468x60.gif" alt=""  loading="lazy" /></a></p>
<p>This time I had some time with <a href="http://joshpeek.com/"target="_blank" rel="noopener"><strong>Joshua Peek</strong></a>. He was recently <a href="http://weblog.rubyonrails.org/2008/8/16/josh-peek-officially-joins-the-rails-core"target="_blank" rel="noopener">announced</a> as a new member of the Rails Core Team.</p>
<p>He got the honor because of his hard work for the upcoming Rails 2.2, solving the <strong>thread-safety</strong> issue within Rails. Because of his Google Summer of Code project, guided by Michael Koziarski, they were able to make Ruby on Rails truly Thread-Safe, which could be a great boost for virtual machines with native-threading support, such as JRuby.</p>
<p>The interesting bit: he is just 19 years old and 4 years ago he wasn’t even a programmer. He started on Ruby with <a href="http://pine.fm/LearnToProgram/"target="_blank" rel="noopener">Learn to Program</a> and he is now a Rails Core member. Let’s get to know him.</p>
<p><strong>AkitaOnRails:</strong> Ok, so let’s get started. First of all, it would be good to know more about yourself. Like how did you find Ruby, why you got hooked by it, and what are your “hobbies” in the tech area ￼</p>
<p><strong>Josh Peek:</strong> Like most people, I originally found Ruby through Rails. At the time, I was only doing simple web design like html, css, js. I had no programming experience. I think I bought some php book, but I could never get into it. So I heard about this “Rails” thing and looked into it. I learned quickly that I really needed to learn how to program and learn Ruby, so I got the <a href="http://pragprog.com/titles/ruby/programming-ruby"target="_blank" rel="noopener">Pickaxe</a> and <a href="http://pragprog.com/titles/rails3/agile-web-development-with-rails-third-edition"target="_blank" rel="noopener">Agile Web Development with Rails</a> book and went from there.</p>
<p><strong>AkitaOnRails:</strong> This is interesting, one of my goals of interviewing you is because I figured you are pretty young and there are lots of young programmers who feel that learning Ruby is difficult. And you say you never programmed before Ruby, so how was this learning curve for you?</p>
<p><strong>Josh Peek:</strong> Yeah, I’m currently <strong>19</strong> and I started programming Ruby about 3 years ago. Before I read the pickaxe, I found this little Ruby programming book by <a href="http://pine.fm/LearnToProgram/"target="_blank" rel="noopener">Chris Pine</a>. It was really helpful to get into programming. The hardest thing about programming for me, was wrapping my head around OOP. It’s really something you just need to work with to truly understand.</p>
<p><strong>AkitaOnRails:</strong> Oh, and I forgot to ask, where are you from? Do you study CS?</p>
<p><strong>Josh Peek:</strong> I’m currently living up in Chicago, IL where I attend college as well. Yes, my current major is Computer Science</p>
<p><strong>AkitaOnRails:</strong> When you started programming with Rails, what were the things that most annoyed you, if any?</p>
<p><strong>Josh Peek:</strong> Hmm, I can’t really think of anything that “annoyed” me. It was never a problem because you could either <a href="http://en.wikipedia.org/wiki/Monkey_patch"target="_blank" rel="noopener">monkey patch</a> the issue or fix it and get it out to the entire Rails community.</p>
<p><strong>AkitaOnRails:</strong> So you started working with Rails as a freelancer? Did you build any open source gems, plugins as well?</p>
<p><strong>Josh Peek:</strong> Yeah, I started as a freelancer. I technically still am. I used to publish a ton of Rails plugins. I think I went a bit overboard at first. I really don’t have any of those around anymore, but it would be fun to go through them and see how much my views on programming have changed since.</p>
<p>Right now, all my current plugins and code are at <a href="http://github.com/josh"target="_blank" rel="noopener">github.com/josh</a>. I tend to just post any personal project I’m working on, on Github even if it’s not done or I think it will be useful at all.</p>
<p><strong>AkitaOnRails:</strong> Ok, so the main dish Thread-safety! You said you were not particularly ‘annoyed’ by anything about Rails, but you decided to tackle on this challenge anyway, how did it all start?</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/9/26/TwextGoogleSOC2008.png" alt=""  loading="lazy" /></p>
<p><strong>Josh Peek:</strong> I did want to do a <a href="http://code.google.com/soc/2008/ruby/appinfo.html?csaid=AE462A3EF48107C3"target="_blank" rel="noopener">Google Summer of Code</a> project for a while now. However when it came to signing up I did things really last second. I asked around the core team and a few ideas came up, one of them was thread safety.</p>
<p>I wasn’t really even that interested in doing it, I actually wanted to work in <a href="http://github.com/rails/rails/tree/master/activemodel/README"target="_blank" rel="noopener">ActiveModel</a> as my project. However the thread safe project was the only one that was accepted, so I was stuck with it. Before the project started, I had zero experience with threads.</p>
<p>I didn’t even know what a mutex was for, however I welcomed it as a learning experience.</p>
<p><strong>AkitaOnRails:</strong> Maybe it would be interesting if you could give us an overview of how the Google Summer of Code works, I mean, what are their goals, how do you participate, how difficult it is. Some Brazilians are probably not familiar with it.</p>
<p><strong>Josh Peek:</strong> Google Summer of Code is a program setup by Google that sponsors students to work on open source projects. The student creates a project proposal for the organization they want to work with, Ruby Central in my case, and it gets approved by them.</p>
<p>You also work with a mentor from the organization. Michael Koziarski (Rails Core) originally suggested that I do the project and so we “arranged” for us to work together.</p>
<p>At the end of the program, you submit your code and if you have completed the project you get paid $5000. And I think $500 of that goes to the open source organization.</p>
<p><strong>AkitaOnRails:</strong> And you completed the project?</p>
<p><strong>Josh Peek:</strong> Yes.</p>
<p><strong>AkitaOnRails:</strong> Cool, and how long is the program?</p>
<p><strong>Josh Peek:</strong> Around 3 months I think.</p>
<p><strong>AkitaOnRails:</strong> Ok, back to the more technical stuff. There is something that bothers me a little bit. Of course it is a lot of work to make Rails thread-safe and this is something people were really looking forward to. But on the other hand, the MRI doesn’t use native threads, which essentially means that they are never truly concurrent, is that correct? Maybe other projects such as JRuby could benefit? Is there any real benefits for the MRI itself?</p>
<p><strong>Josh Peek:</strong> Correct. JRuby is the only true solution if you want real thread concurrency. Ruby 1.9 does support “real” threads. However most of the internal C code is not thread-safe so there is an interrupter around those methods, but I’m hoping that maybe Ruby 2.0 will be fully thread-safe.</p>
<p><strong>AkitaOnRails:</strong> You probably ran tests for both MRI and JRuby? Did you get improved performance/responsiveness because of the new thread-safe code? Or does it add up some new overhead instead?</p>
<p><strong>Josh Peek:</strong> Our tests showed some slight improves in MRI, however the app we were testing was a simple test app. We didn’t have a real app we could benchmark since nothing else was thread-safe at the time. JRuby did show some nice improvements, again it wasn’t a real app though.</p>
<p><strong>AkitaOnRails:</strong> Did you only aim for the thread-safe parts or did you also found and fixed other kinds of bottlenecks in the framework? You probably went through the entire code over and over and you already know it upside down, right? ￼</p>
<p><strong>Josh Peek:</strong> I did a lot of work on preloading caches on boot up. The easy way out would be to just put a mutex around those caches and call it done. However, I had some extra time so I rewrote a ton of ActionView code.</p>
<p>Preloading means that we are building the cache on boot so it doesn’t need to be modified (or synchronized) while it’s serving requests and this should also benefit non threaded apps as well.</p>
<p>And it’s a huge win for Passenger, if you are using the copy on write feature, since caches built during Rails boot will be shared across multiple process.</p>
<p><strong>AkitaOnRails:</strong> Awesome, the <a href="http://www.modrails.com/"target="_blank" rel="noopener">Phusion</a> guys will like to hear about that. And I know some people will be lost about <a href="http://en.wikipedia.org/wiki/Mutual_exclusion"target="_blank" rel="noopener">mutexes</a>, could you explain a little bit about it for the audience?</p>
<p><strong>Josh Peek:</strong> The big issue about running multiple threads is shared memory. If two threads try to access and modify the same object, it can cause some major problems. A “mutex” is a type of lock that “synchronizes” your threads so only one thread can access that piece of memory at a time.</p>
<p>However, this does have a performance impact because other threads have to stop while one is accessing the shared memory.</p>
<p><strong>AkitaOnRails:</strong> I think it is awesome work, specially for JRuby. Did you talk to Charles, Nick or someone from the JRuby Core? I remember that I interviewed Charles and he said that even though they were pushing harder on the optimization of the JRuby runtime, the performance of Rails apps were not improving that much, so the next step would be to try to figure out how to modify Rails itself to behave better. Have you heard anything about this?</p>
<p><strong>Josh Peek:</strong> Yes, we worked with Charles on some JRuby specific optimizations. He was a lot of help and he answered any of my questions relating to how JRuby synchronizes threads and what operations are atomic.</p>
<p><a href="http://blog.nicksieger.com/"target="_blank" rel="noopener">Nick Sieger</a> actually volunteered (before GSoC) to tackle the ActiveRecord <a href="http://ryandaigle.com/articles/2008/9/7/what-s-new-in-edge-rails-connection-pools"target="_blank" rel="noopener">connection pool</a>. Really a big thanks to Nick, I really didn’t have the experience to tackle that problem and he did a great job on it.</p>
<p><strong>AkitaOnRails:</strong> Ok, even though the Ruby VM can’t have true concurrent threads, they actually have to deal with concurrent resources, in this case database connections, so a pool was necessary. Was it too much trouble to implement? Was it something that you had to do from scratch or there were prior work on connection pools for Ruby?</p>
<p><strong>Josh Peek:</strong> Well, I think you better of asking Nick this question. But here is what I understand about it: The previous AR connection implementation did support threads, however it was easy to max out on the allowed number of db connections if a server got blasted.</p>
<p>The connection pool basically recycles old connections and prevents AR from establishing too many. You can actually set his max in your database.yml file with the “pool” option. I think the default is 3.</p>
<p><strong>AkitaOnRails:</strong> And how does the mentor role work? I mean, did <a href="http://www.koziarski.com/"target="_blank" rel="noopener">Koziarski</a> guide you on specific points he wanted you to take a look on?</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/9/26/506281043_419fd4e72d.jpg" alt=""  loading="lazy" /></p>
<p><strong>Josh Peek:</strong> Koz really help me stay on track. He did a lot of code auditing, and pointed out specific places I should look for problems in the large Rails code base.</p>
<p><strong>AkitaOnRails:</strong> From the outside it probably feels ‘easy’ to say <em>‘ok, we just added thread-safety into Rails’</em>, but this is not something like an external gem or plugin, you actually have to tackle the guts of the framework. How can you be sure you actually touched every part of the code that needed concurrency safety measures?</p>
<p><strong>Josh Peek:</strong> That’s really what is hard about making code “thread safe”: there is no real way to be sure. You can write a bunch of unit tests and say <em>‘look, I can prove it’s fine’</em>. However, the big issues are out of the way. From now on, we’ve decided to classify any further thread safe issues just like normal bugs.</p>
<p><strong>AkitaOnRails:</strong> Do you think it is ready for prime-time? Or are you still tweaking it?</p>
<p><strong>Josh Peek:</strong> I think it is ready for those people who have been waiting for a thread-safe Rails. People should start to audit and test their own apps for thread-safe issues and if something comes up, we’ll fix it in core. It’s really hard to test this stuff unless real people are testing real apps against it.</p>
<p><strong>AkitaOnRails:</strong> At least, <em>“Rails is not thread-safe”</em> is not a good enough reason anymore to not use Rails. This should shut up a few pundits. This Rails 2.2 is going to be interesting, debunking some of these criticisms. Another one is <em>“Rails doesn’t support i18n”</em>, thanks to <a href="http://www.artweb-design.de/2008/9/6/the-future-of-i18n-in-ruby-on-rails-railsconf-europe-2008"target="_blank" rel="noopener">Sven</a>. What else do you like the most about the next Rails release?</p>
<p><strong>Josh Peek:</strong> My next little project within Rails is switching over to <a href="http://rack.rubyforge.org/"target="_blank" rel="noopener">Rack</a>. Rails 2.2 will include a Rack processor which lets your use it with any Rack server. However we are not fully switching over to Rack, yet. In 2.2 rack is opt-in.</p>
<p>./script/server will still run the old CGI processor. I hope to see CGI deprecated in the next release so we can be Rack only for 3.0.</p>
<p><strong>AkitaOnRails:</strong> What are the benefits of going for Rack? It may not be so obvious for some people on why change code that is working already (the CGI bit).</p>
<p><strong>Josh Peek:</strong> There should be some performance benefits, however that wasn’t enough to convince. Right now we have a ton of CGI hacks in Rails. This should clean up a lot of that.</p>
<p>What I really love is the standardized interface to Rails. This means a more universal API for writing ActionController related plugins that will work on any compatible Rack framework. I hope this means more Rails plugins will just work with Merb and vice versa.</p>
<p><strong>AkitaOnRails:</strong> Having finished your GSoC project granted you the keys for the Rails official repo, is that correct? How does the Rails Core organize itself? Is each person responsible for some particular bit, or everybody else looks after everything?</p>
<p><strong>Josh Peek:</strong> It seems like everyone has their own little responsibilities depending on what they were just working on. Since I just rewrote a ton of ActionView, I pretty much assume any issues or tickets related to it. I don’t think anyone has a pre-determined area though. People change their interests and that’s fine.</p>
<p><strong>AkitaOnRails:</strong> But now can you ‘git push’ anything you want into Edge? And by the way, you mentioned you messed a lot with ActionView, but what exactly you changed there?</p>
<p><strong>Josh Peek:</strong> Yeah, I’ve had commit access a while before the GSoC project (just before we switched to git). ActionView had to be refactored to support the preloading approach I was talking about earlier. So ERB templates are pre-compiled and cached on boot instead of at runtime.</p>
<p><strong>AkitaOnRails:</strong> So, in 3 years you went from zero to Rails Core, solving the “thread-safety issue” and still having time to study and work on your freelance projects. How does it feel, looking back in retrospective? Did you ever thought you would contribute so much?</p>
<p><strong>Josh Peek:</strong> 4 years ago, I really never even saw myself programming. I definitely never thought I end up on the Core Team of Rails.</p>
<p><strong>AkitaOnRails:</strong> After so much you learned, what are the areas in computing that interests you the most? Web Development, any other area such as compilers, OS, etc? What do you intend to do next?</p>
<p><strong>Josh Peek:</strong> I still really love Web Development. I recently tried some iPhone programming, but I’m not sure how I feel about it yet.</p>
<p>I’d love to try some lower level stuff. I still haven’t had a chance to use the Ruby C API, and I hear it’s really nice. Lately I’ve been doing a lot of work on a javascript project that’s soon to be announced and open sourced.</p>
<p><strong>AkitaOnRails:</strong> Oh, and by the way, are you also a Mac user? :-) And I think this is it! Any other remarks about what we talked or something you would say to the Brazilian Ruby community?</p>
<p><strong>Josh Peek:</strong> Yeah, I’ve been using Macs for about 6 years now. I’m not one of those people who switched after they discovered Ruby :-)</p>
<p><strong>Josh Peek:</strong> And think thats pretty much it, … I was suckered into doing a talk at RubyConf this year, for the GSoC thing. So you’ll probably hear about about “thread safety” there. It’s probably the last time I’ll ever want to talk about Rails and threads again.</p>
<p>Thanks for the questions.</p>
]]></content:encoded><category>interviews</category><category>rails</category><category>performance</category></item><item><title>Chatting with Blaine Cook (Twitter)</title><link>https://akitaonrails.github.io/en/2008/06/17/chatting-with-blaine-cook-twitter/</link><guid isPermaLink="true">https://akitaonrails.github.io/en/2008/06/17/chatting-with-blaine-cook-twitter/</guid><pubDate>Tue, 17 Jun 2008 00:29:00 -0300</pubDate><description>&lt;p&gt;Ruby on Rails is &lt;strong&gt;big&lt;/strong&gt;. Twitter is &lt;strong&gt;big&lt;/strong&gt;. And because of that they became easy targets for the media and the frustrated pundits wanting a few more pageviews. &amp;ldquo;Blaine Cook&amp;rdquo; was one of Twitter&amp;rsquo;s developers, and he kindly agreed to participate in one of my interviews. And, of course, he will answer the question &lt;em&gt;&amp;ldquo;Does Rails Scale?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.flickr.com/photos/hyku/2299866775/"target="_blank" rel="noopener"&gt;&lt;img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2299866775_385f7af555.jpg" alt="" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AkitaOnRails:&lt;/strong&gt; So, let me first explain that my main goal when I interview high profile people like you is to bring good information for our growing Rails community in Brazil. It is always good to ask about your background, how did you get into Rails and what was your experience with it.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ruby on Rails is <strong>big</strong>. Twitter is <strong>big</strong>. And because of that they became easy targets for the media and the frustrated pundits wanting a few more pageviews. &ldquo;Blaine Cook&rdquo; was one of Twitter&rsquo;s developers, and he kindly agreed to participate in one of my interviews. And, of course, he will answer the question <em>&ldquo;Does Rails Scale?&rdquo;</em></p>
<p><a href="http://www.flickr.com/photos/hyku/2299866775/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2299866775_385f7af555.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>AkitaOnRails:</strong> So, let me first explain that my main goal when I interview high profile people like you is to bring good information for our growing Rails community in Brazil. It is always good to ask about your background, how did you get into Rails and what was your experience with it.</p>
<p><strong>Blaine Cook:</strong> I started with Rails on a small personal project back in December 2004, after Evan Henshaw-Plath (<a href="http://2008.xtech.org/public/schedule/speaker/283"target="_blank" rel="noopener">rabble</a>) got really excited about Ruby &amp; Rails.</p>
<p><a href="http://www.flickr.com/photos/foolswisdom/43021932/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/43021932_545efed570.jpg" alt=""  loading="lazy" /></a></p>
<p>Shortly afterwards, I started working on <a href="http://www.odeo.com"target="_blank" rel="noopener">Odeo</a>. I really like Rails, and I&rsquo;ve really enjoyed my experience with it. Building web-apps used to be a real pain (i.e., boring) because you ended up building the same pieces over and over again.</p>
<p>Rails fixed that. There were frameworks before, but Rails reframed the question in a humane way, and we see that in all the other frameworks that have been created afterwards.</p>
<p><strong>AkitaOnRails:</strong> So, what were you doing before Rails then? Enterprise Java? Microsoft stuff? Other open source projects?</p>
<p><strong>Blaine Cook:</strong> I mostly did PHP and Perl, in the context of open source projects and consulting jobs.</p>
<p><strong>AkitaOnRails:</strong> So, <strong>Odeo</strong> and <strong>Twitter</strong> are your biggest projects in Rails then? Before digging into Twitter, can you tell us how was your work for Odeo?</p>
<p><strong>Blaine Cook:</strong> Sure – Odeo was fine – the big problem there was with focus. Podcasting hadn&rsquo;t been proven (and the form that podcasting takes now is much different than what people envisioned in 2004 / 2005), so it was a struggle to gain traction for the product.</p>
<p>We had all sorts of cool things built, many of which are just becoming products now. Things like multi-track mixing on the web, video messaging (like <a href="http://www.seesmic.com/"target="_blank" rel="noopener">seesmic</a>), etc.</p>
<p><strong>AkitaOnRails:</strong> This is an interesting side-subject: podcasting. I do a <a href="http://podcast.rubyonrails.pro.br/"target="_blank" rel="noopener">Brazilian Rails podcast</a> with a friend and nowadays people are very acquainted with podcasts. What was the vision 3, 4 years ago about what podcasting would become?</p>
<p><strong>Blaine Cook:</strong> I think that&rsquo;s fairly similar, but it seems like most of the traction (i.e., listeners) for podcasting is established thru radio shows that are seeking a wider distribution.</p>
<p>I think there was a sentiment that podcasting would be much more popular amongst bloggers in general. Everyone was going to have a podcast.</p>
<p>But in reality it&rsquo;s a medium with far fewer publishers than blogging.</p>
<p><a href="http://www.flickr.com/photos/jolieani/2303816926/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2303816926_beafddbb5c.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>AkitaOnRails:</strong> You helped start Odeo, I mean were you one of the creators? Or were you hired to solve their problem? And why did you leave Odeo?</p>
<p><strong>Blaine Cook:</strong> No – I started at Odeo a few months after development had started. I never really left Odeo – the product was sold, and the company continued on as Obvious. Twitter was a side-project at Odeo.</p>
<p><strong>AkitaOnRails:</strong> Ok, and Twitter is big nowadays. Could you give us a glimpse of how the whole idea of <em>&ldquo;What are you doing&rdquo;</em> started? Did you think at that time that it would become such a success as it is now?</p>
<p><strong>Blaine Cook:</strong> Twitter was started as a side project at Odeo, but really it was an idea that <a href="http://www.stonetemple.com/articles/interview-jack-dorsey.shtml"target="_blank" rel="noopener">Jack</a> had been kicking around in various forms for several years.</p>
<p>He and <strong>Noah Glass</strong> (the founder of Odeo) created a small sub-company within Odeo to work on it, in spring 2006. Initially, I hated the idea, mostly because it was intended to be SMS-only.</p>
<p>It&rsquo;s hard to say looking back, but at the time I&rsquo;d say Twitter was a long shot; I think that its success had a lot to do with luck and openness.</p>
<p><strong>AkitaOnRails:</strong> So, maybe one of the biggest reasons for Twitter&rsquo;s success was also its Achilles&rsquo; heels? I mean the paramount API based access it has today?</p>
<p><strong>Blaine Cook:</strong> Yes, absolutely.</p>
<p><strong>AkitaOnRails:</strong> So, getting to the hairy stuff, it is quite obvious that <a href="http://www.techcrunch.com/2008/04/23/amateur-hour-over-at-twitter/"target="_blank" rel="noopener">Michael Arrington</a> dislikes Twitter, period. And especially he has something <a href="http://www.alleyinsider.com/2008/4/lead_architect_blaine_cook_out_at_twitter"target="_blank" rel="noopener">against you</a> personally, at least considering the tone of his articles on Techcrunch. Can you comment on that?</p>
<p><strong>Blaine Cook:</strong> Not really; I don&rsquo;t know why he has a grudge. It&rsquo;s mildly annoying, but in the end it makes Techcrunch look more like a gossip rag and less like a respectable news outlet, so it&rsquo;s his loss.</p>
<p><strong>AkitaOnRails:</strong> And not only that but it also seems that some tech related news media wants to correlate Twitter&rsquo;s instability with the fact that part of its front-end is powered by Ruby on Rails, hence the discussion <em>&ldquo;Does Rails scale?&rdquo;</em>. As a Railer this seems quite obvious that relating Rails and instability generates lots of pageviews. Do you think that&rsquo;s the case?</p>
<p><strong>Blaine Cook:</strong> I think that&rsquo;s at least part of it. Obviously page views are a huge draw.</p>
<p><strong>AkitaOnRails:</strong> Maybe it would be good for someone like you to explain to our audience the difference between <strong>&lsquo;performance&rsquo;</strong> and <strong>&lsquo;scalability&rsquo;</strong> as it seems that most people don&rsquo;t know the difference between the two.</p>
<p><strong>Blaine Cook:</strong> Performance and scalability are very different things. Performance is like the speed limit; it&rsquo;s a question of how quickly you can get from point A to point B. In this case, we&rsquo;re talking about how quickly you can get a page rendered and delivered to the user.</p>
<p>Scalability is a question of how many users you can handle. Highways intrinsically don&rsquo;t scale, for example, because when they&rsquo;re full, you get traffic jams. In web architectures, what we aim to provide are systems that will expand (usually by adding more hardware) to handle more traffic.</p>
<p>Obviously they&rsquo;re related – if you have a traffic jam, then the effective speed limit is lower than the theoretical limit. But increasing the speed limit won&rsquo;t make traffic jams any better.</p>
<p>There are a whole bunch of ways to make traffic less congested – adding more lanes to the highway, encouraging people to use public transit, or better yet encouraging people to work closer to home.</p>
<p>Likewise, there are many techniques for making web sites more scalable, and most of them don&rsquo;t involve making things much &ldquo;faster&rdquo;.</p>
<p><a href="http://www.flickr.com/photos/gavinbell/2476619970/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2476619970_af72edc5ef.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>AkitaOnRails:</strong> Great explanation. Twitter was involved in some not very good controversy since last year, and I think you were not involved in most of them, but maybe you could shed some light on those issues as some people don&rsquo;t follow every news story all the time. The first one being that interview where <a href="http://www.radicalbehavior.com/5-question-interview-with-twitter-developer-alex-payne/"target="_blank" rel="noopener">Alex Payne</a> complained about Rails not supporting multiple databases. <strong>David Hansson</strong> ranted about it. Then <a href="http://drnicwilliams.com/2007/04/12/magic-multi-connections-a-facility-in-rails-to-talk-to-more-than-one-database-at-a-time/"target="_blank" rel="noopener">Dr. Nic</a> came to the beginning of a solution. Can you explain this matter for our audience?</p>
<p><strong>Blaine Cook:</strong> Sure – it&rsquo;s true that Rails doesn&rsquo;t support multiple databases out of the box, but this is easily fixed. I think the interview with Alex didn&rsquo;t get into the details very well, and mis-represented our challenges.</p>
<p>If it was just a matter of supporting multiple database connections, that&rsquo;s something that&rsquo;s easily solved. The real problem was much more complex, and had to do with custom sharding for our particular dataset.</p>
<p><strong>Eran Hammer-Lahav</strong> has an excellent series (parts <a href="http://www.hueniverse.com/hueniverse/2008/03/on-scaling-a-mi.html"target="_blank" rel="noopener">1</a>, <a href="http://www.hueniverse.com/hueniverse/2008/03/scaling-a-micro.html"target="_blank" rel="noopener">2</a>, <a href="http://www.hueniverse.com/hueniverse/2008/04/scaling-a-micro.html"target="_blank" rel="noopener">3</a>) in which he describes some of the challenges associated with building microblogging sites to scale.</p>
<p>None of the things he describes in the article are hard to do with Rails. In fact, I think that David (Hansson) is wrong when he says that Rails isn&rsquo;t flexible – it&rsquo;s actually much more flexible than most other frameworks.</p>
<p><strong>AkitaOnRails:</strong> That&rsquo;s right. And then the other recent controversy is, of course, because of <a href="http://www.techcrunch.com/2008/05/31/hey-twitter-i-have-a-few-questions-too/"target="_blank" rel="noopener">TechCrunch</a> again, where they state that Twitter only has like half a dozen servers and that&rsquo;s why it falls down frequently. Can you roughly describe how is Twitter&rsquo;s hardware architecture?</p>
<p><strong>Blaine Cook:</strong> Twitter has a lot of servers, definitely more than half a dozen. I can&rsquo;t go into more detail about the hardware setup, but I can say that the back end is an asynchronous messaging-based system. <a href="http://rubyforge.org/projects/starling/"target="_blank" rel="noopener">Starling</a> (the queue server that I wrote that Twitter released in January) is the mechanism used to pass messages between processes.</p>
<p>The biggest problem is that Twitter deals with many, many often expensive API requests due to the frequent polling required to stay up to date with your friends&rsquo; most recent tweets. The big challenge was to come up with an inexpensive way to make those expensive API requests cheap.</p>
<p>We used memcache extensively, and had a whole bunch of caching in place well over a year ago, but it wasn&rsquo;t enough.</p>
<p><strong>AkitaOnRails:</strong> Last year Alex and Britt spoke at RailsConf I think about <a href="http://www.slideshare.net/eraz/scaling-rails-presentation/"target="_blank" rel="noopener">Scaling Rails</a> where it felt like Twitter&rsquo;s problem would end soon. It was mostly about caching and optimizations like that, and one of the main messages was <em>&ldquo;cache the hell out of it&rdquo;</em>. I saw you speaking this year about database sharding and how different it is to architect for 1k people and for 1 million people and that you can&rsquo;t predict what to do before that. Twitter still goes down sometimes, so what changed between last year and now about Scaling Rails?</p>
<p><a href="http://www.flickr.com/photos/x180/2329021397/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2329021397_3dde02123a.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>Blaine Cook:</strong> Yes – we knew for a long time that database sharding was important, but for various reasons weren&rsquo;t able to move forward with it.</p>
<p>The thing with sharding is that until someone comes out with something like what Google has (and I don&rsquo;t mean Bigtable), the sort of shape your application takes before sharding is very different than the shape afterwards. Sharding makes doing some things harder, and is generally expensive.</p>
<p>When people say &ldquo;sharding&rdquo; or &ldquo;denormalization&rdquo;, what they mean is <em>&ldquo;duplicating data in particular ways to make certain lookups faster.&rdquo;</em></p>
<p>That always involves additional cost. If you&rsquo;re building a new app that will either never see hundreds of thousands of users, or if you don&rsquo;t know if it will or not, it&rsquo;s generally not worth sinking lots of money into hardware and engineering time to build a &ldquo;scalable&rdquo; architecture.</p>
<p>The reality is that most developers know SQL and can build CGI applications using PHP or Rails or whatever, but relatively few have experience taking those straightforward, normalized data sets and breaking them up into much more complex sharded systems.</p>
<p><strong>AkitaOnRails:</strong> Of course, sometimes people feel like they will actually build the &rsquo;next&rsquo; Twitter. I <a href="/2008/6/5/railsconf-2008-brazil-rails-podcast-special-edition">interviewed Ezra</a>, from Engine Yard, at RailsConf and he seems to acknowledge that the friends-of-friends graph is a problem so complex that whoever figures it out will make a ton of money I mean, isn&rsquo;t it the case that traditional SQL designs don&rsquo;t work all the time?</p>
<p><strong>Blaine Cook:</strong> Of course – the question is, what else are you going to use? Google has an architecture that lets them spend infinite amounts of money to build denormalized lists of everything, and access those lists quickly. There are a number of projects – HBase, Hypertable, CouchDB, and many others – that aim to solve these problems, but none of them are ready for production use.</p>
<p>The reality is that 10 years ago, people were building web applications with tcsh, and now we have better tools.</p>
<p><strong>AkitaOnRails:</strong> Where is Twitter hosted? For some reason I saw Engine Yard pitching a lot about scalability issues throughout the whole RailsConf this year. Do you think this whole &ldquo;Rails doesn&rsquo;t scale&rdquo; meme is making Railers worried about future prospects? Even though it was not supposed to scare that much as we do have higher traffic websites such as Scribd and YellowPages in the top of the list – though the architectures are world apart, of course. What do you think about this issue?</p>
<p><strong>Blaine Cook:</strong> I&rsquo;m not sure, but I think Twitter actually does more traffic than YellowPages or Scribd; the API is a real killer.</p>
<p><strong>AkitaOnRails:</strong> Ah, I see, Alexa and others don&rsquo;t account for API request, right?</p>
<p><strong>Blaine Cook:</strong> Twitter is hosted at NTT / Verio. I think there was a blog post about it in February. Right, Alexa doesn&rsquo;t account for API / SMS / XMPP traffic at all.</p>
<p>As far as the perspective that Rails doesn&rsquo;t scale – I think it&rsquo;s an unfortunate side-effect of Twitter&rsquo;s issues, to be sure, and I think some people worry about it. To put things into perspective, though, Friendster was written in Java to start, and switched to PHP. Myspace was written in ColdFusion and transitioned to ASP.NET.</p>
<p>When people run into problems scaling sites they often think that the language is the problem, but I think it&rsquo;s rarely the case.</p>
<p>Ruby is slower than PHP or Java (depending on which framework you use) or, well, most languages, but it&rsquo;s getting faster. And &ldquo;slower&rdquo; really only translates to additional cost.</p>
<p><strong>AkitaOnRails:</strong> Which reminds me of the last controversy which was <a href="http://www.techcrunch.com/2008/05/01/twitter-said-to-be-abandoning-ruby-on-rails/"target="_blank" rel="noopener">Twitter switching off from Rails</a>. The case is that you already have a mixed environment, right? Most pundits think they &lsquo;know&rsquo; how to solve Twitter&rsquo;s problems, though they have absolutely no idea what they are talking about. Some blame Rails, others blame MySQL (<em>&ldquo;go to Oracle&rdquo;</em>, they say). Can you elaborate on that?</p>
<p><strong>Blaine Cook:</strong> Right – the majority of our development was in Ruby / Rails, but there was code in other langauges. Python, Java, and there were some experiments with Scala. Which ignores the fact that we ran significant portions of our infrastructure on non-Ruby code – Apache, Mongrel, MySQL, memcached, ejabberd, etc. are all great applications and critical to building scalable sites.</p>
<p>Oracle definitely has some of these things figured out, but it&rsquo;s much easier to hire for MySQL, and Oracle licensing and support fees are nothing to scoff at.</p>
<p>Building your architecture around a system that will only get cheaper with time, rather than more expensive, is always a good idea.</p>
<p><strong>AkitaOnRails:</strong> And I have to ask, can you disclose some meaningful numbers, or orders of magnitude, such as tweets per day, average downtime, or something like this, just so we can have an idea of the size of Twitter&rsquo;s problems? Coming from someone that has actually had to deal with such a huge situation – which most people probably will never have to face -, what would you say are Ruby/Rails&rsquo; strongest features for you?</p>
<p><strong>Blaine Cook:</strong> Sadly, no – there are plenty of estimates on the web, most notably compete, and Dave Winer&rsquo;s fan-out numbers.</p>
<p>I think Ruby&rsquo;s best feature is that it&rsquo;s <strong>fun to develop</strong> with. It never stops being fun, either. It&rsquo;s an expressive and powerful language. Rails just makes web development painless, compared to doing everything by hand. That&rsquo;s something that shouldn&rsquo;t be underestimated, or whose value forgotten.</p>
<p>On top of that, they&rsquo;re really flexible. We needed to build a denormalization tool, for example, and doing it in Ruby was just as easy / hard as any other language, except when it came to tying it in to the rest of the system – no search and replace, no subclassing or anything. Just turn it on, and it works.</p>
<p><a href="http://www.flickr.com/photos/niallkennedy/440202833/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/440202833_77152b8080.jpg" alt=""  loading="lazy" /></a></p>
<p><strong>AkitaOnRails:</strong> Finally can you tell us why you left Twitter? What are your new endeavors? Twitter survived WWDC last week which is saying a lot considering such huge events would usually bring it down in the past.</p>
<p><strong>Blaine Cook:</strong> I&rsquo;m moving to the UK, and Twitter is / has moved into a position where their primary focus is reliability. When I left, the hope was that their system was stabilized, and the roadmap was clear as far as putting them in a better position overall. At the time, they&rsquo;d had an extended period of uninterrupted uptime, and had survived with flying colours <a href="http://sxsw.com/"target="_blank" rel="noopener">SXSW</a>. Unfortunately, it didn&rsquo;t last, but it looks like things are better again.</p>
<p>I&rsquo;m not yet ready to reveal what&rsquo;s next, but I have a number of exciting things going on. I&rsquo;ll continue maintaining Starling and <a href="http://code.google.com/p/xmpp4r-simple/"target="_blank" rel="noopener">xmpp4r-simple</a>, and I&rsquo;m still active with the OAuth spec. working group.</p>
<p><strong>AkitaOnRails:</strong> In the end, Twitter became almost part of our culture already (at least in our tech world). People complain when it is down because now they depend on it so much. People like Michael Arrington, Robert Scoble, Leo Laporte are always complaining, saying they would move to Pownce or Jaiku, but they are still around. Do you have any guess on why Twitter users are so loyal?</p>
<p><strong>Blaine Cook:</strong> Nope! I think the really rich landscape of tools that you can use to interact with Twitter on your own terms is a really big part of it. A lot of the users are personally invested, and they want to see that investment pay out.</p>
<p><strong>AkitaOnRails:</strong> Ok, I think this covers everything. Any closing remarks for our young Brazilian Rails community?</p>
<p><strong>Blaine Cook:</strong> The web is remarkably young, and there&rsquo;s plenty of space for really amazing things to happen. I&rsquo;m always excited to see what people are working on, and what&rsquo;s possible with this whole Internet thing. For all of Arrington&rsquo;s petty and misinformed comments, I think sites like Techcrunch and <a href="http://www.mashable.com"target="_blank" rel="noopener">Mashable</a> and <a href="http://www.alleyinsider.com"target="_blank" rel="noopener">Silicon Alley Insider</a> are interesting because they present a never ending stream of amazing things that people are building on the web.</p>
<p>The financial focus is unfortunate, as there are plenty of apps that survive and provide income for their founders without ever needing to scale to galactic proportions.</p>
<p>The bottom line is that there&rsquo;s plenty to do, and don&rsquo;t worry about scaling or business model or anything – unless you&rsquo;re passionate about what you&rsquo;re doing, none of it matters.</p>
<p>If you are passionate about what you&rsquo;re doing, there&rsquo;s a really good chance you&rsquo;ll figure the details out.</p>
<p><strong>AkitaOnRails:</strong> Fantastic! Thank you very much for your kindness, I am sure my audience will enjoy it. And just to let you know that we, Brazilians, <strong>do</strong> tweet a lot! :-) Thanks!</p>
<p><strong>Blaine Cook:</strong> Excellent, glad to hear! No problem, thanks for being a great interviewer.</p>
<p><a href="http://www.flickr.com/photos/briandewitt/2300104137/"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/6/17/2300104137_96c83ac13d.jpg" alt=""  loading="lazy" /></a></p>
]]></content:encoded><category>interviews</category><category>performance</category><category>distributed-systems</category></item></channel></rss>