<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Peter Truchly</title>
    <description>The latest articles on Forem by Peter Truchly (@peter_truchly_4fce0874fd5).</description>
    <link>https://forem.com/peter_truchly_4fce0874fd5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1627133%2F754d0602-d0ad-4fa7-bc25-66495c379400.jpg</url>
      <title>Forem: Peter Truchly</title>
      <link>https://forem.com/peter_truchly_4fce0874fd5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/peter_truchly_4fce0874fd5"/>
    <language>en</language>
    <item>
      <title>Why Success Favors Action and How This Relates to AI</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Mon, 08 Sep 2025 17:49:19 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/why-success-favors-action-and-how-this-relates-to-ai-4e54</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/why-success-favors-action-and-how-this-relates-to-ai-4e54</guid>
      <description>&lt;p&gt;It's been over a year since I published my view on the state of AI/LLMs and how it could be significantly improved in my post: &lt;a href="https://dev.to/peter_truchly_4fce0874fd5/failure-is-not-an-option-for-ai-and-it-shouldnt-be-1hm2"&gt;"Failure Is Not An Option For AI"&lt;/a&gt;. While scrolling through the &lt;strong&gt;rStar2-Agent&lt;/strong&gt; technical report, I couldn't help but mumble to myself: "I told you so!".&lt;/p&gt;

&lt;p&gt;Consider how quickly the model improved during training, especially given that we are comparing a 14B parameter model to 671B R1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu56ks75ba29evr5unl7f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu56ks75ba29evr5unl7f.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This doesn't just show how vast the universe of optimization is; it demonstrates the fundamental principle:&lt;/p&gt;

&lt;p&gt;➡️ &lt;strong&gt;The cost of inaction often far exceeds the cost of a reversible mistake&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When comparing the traditional Chain-of-Thought (CoT) approach to this new "Chain of Action", the most striking difference is how early the environmental feedback provides value to the training process. To appreciate what was achieved, let's look at technical side. Using this approach effectively during training required a high-throughput execution environment. The one used for this project was capable of handling &lt;strong&gt;45,000 concurrent tool calls&lt;/strong&gt;, returning &lt;strong&gt;feedback in just 0.3s&lt;/strong&gt; on average.&lt;/p&gt;

&lt;p&gt;Compounding is a fundamental principle of investing. Becoming the richest person in the world is possible by investing small sums and compounding them well enough and long enough. But this principle works inefficiently when applied to chain of thought. &lt;strong&gt;Compounding subtle errors early in a reasoning process leads to a long, inefficient, and ultimately incorrect reasoning trajectory.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyone who sat through lengthy corporate meetings or a drawn-out group decision-making process understands this intuitively. The first practical attempt provides more knowledge than a month of theoretical planning. Feedback, even when negative, is crucial and must be delivered swiftly. I cannot stress this enough. &lt;/p&gt;

&lt;p&gt;The emergent ability of the model trained with this novel approach is particularly unique: &lt;strong&gt;The model learned to react productively to negative feedback.&lt;/strong&gt; Researchers observed the model using  specific "forking" and "reflection" tokens. It was effectively talking to itself - course-correcting, pausing to analyze an error, exploring alternative approaches.&lt;/p&gt;

&lt;p&gt;This suggests a universal success formula, for humans and AI alike:&lt;/p&gt;

&lt;p&gt;➡️ &lt;strong&gt;Form a hypothesis, take action, observe feedback, and repeat.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The best part of this story is that the rStar2-Agent codebase has been released under an MIT license on &lt;a href="https://github.com/microsoft/rStar" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;[1] &lt;a href="https://www.arxiv.org/pdf/2508.20722" rel="noopener noreferrer"&gt;rStar2-Agent: Agentic Reasoning Technical Report&lt;/a&gt;&lt;br&gt;
[2] &lt;a href="https://github.com/microsoft/rStar" rel="noopener noreferrer"&gt;rStar2-Agent at github&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>community</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fast Memory Copying in C#/.NET (Cache, AVX, Threads, Unsafe, Alternatives)</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Fri, 20 Dec 2024 20:06:07 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/fast-memory-copying-in-cnet-cache-avx-threads-unsafe-alternatives-h57</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/fast-memory-copying-in-cnet-cache-avx-threads-unsafe-alternatives-h57</guid>
      <description>&lt;p&gt;Numerous &lt;strong&gt;copy routine&lt;/strong&gt; implementations are readily available in .NET. If I were to simply list them alongside a few benchmark numbers and charts, it wouldn't make for a very interesting article.&lt;/p&gt;

&lt;p&gt;⚠️ What if I told you upfront that &lt;strong&gt;none&lt;/strong&gt; of these routines &lt;strong&gt;is designed to be the absolute fastest&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;If you're interested in a basic comparison, I recommend checking out this article here on &lt;a href="https://dev.to/j0nimost/what-is-the-best-way-to-copy-an-array-1ch4"&gt;dev.to: What is the best way to copy an array?&lt;/a&gt; or a more detailed, older comparison: &lt;a href="https://code4k.blogspot.com/2010/10/high-performance-memcpy-gotchas-in-c.html" rel="noopener noreferrer"&gt;High performance memcpy gotchas in C#&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here, I'll outline a list of options, but this is far from the whole story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A simple &lt;code&gt;for&lt;/code&gt; loop (hint: &lt;code&gt;foreach&lt;/code&gt; is usually a bit faster)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Array.Copy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Span.CopyTo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Buffer.BlockCopy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Buffer.MemoryCopy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Marshal.Copy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Unsafe.CopyBlock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Imported &lt;code&gt;memcpy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're currently struggling with slow array or memory copy operations,  try one of the functions on this list.&lt;/p&gt;

&lt;p&gt;However, there are some elephants in the room, and I plan to uncover a few of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foefzoe7nnmygbvzneh7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foefzoe7nnmygbvzneh7o.png" alt="Image description" width="512" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Elephant No.1 - Cache Pollution
&lt;/h2&gt;

&lt;p&gt;What You might read in many places is that framework functions are already highly optimized. This is true, but they are &lt;strong&gt;not&lt;/strong&gt; necessarily optimized for &lt;strong&gt;the highest speed possible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Built-in functions provided by .NET framework are optimized in various ways. One significant consideration is preventing &lt;strong&gt;cache pollution&lt;/strong&gt;. Wait - what? Yes, x86 CPUs achieve their speed primarily thanks to their cache. If the cache is disabled or not utilized properly, code execution speed drops dramatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache Pollution Explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cache pollution when copying large data blocks: This occurs when frequently used data gets replaced in the CPU cache by the data being copied. Chances are that once You are done copying, You won't touch the same data ever again, so it makes placing them in a CPU cache unnecessary.&lt;br&gt;
For example, imagine a network stack - once data is sent, it's unlikely to be touched again. Similarly, when loading textures with the CPU for GPU usage, caching may be unnecessary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Standard .NET functions mitigate this issue when copying larger blocks (usually sizes above 1MB) by using &lt;strong&gt;non-temporal access&lt;/strong&gt;, bypassing the cache.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This might not suit your use case. For instance, if you are waiting for the copy to complete before doing anything else - some cache pollution would be an acceptable tradeoff. Especially when the data is likely to be cached anyway, as in simulations or CPU rendering.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;▶️ How can we observe this? And what can we do about it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cfbohb7i6ionpygqme1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6cfbohb7i6ionpygqme1.png" alt="Image description" width="770" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Chart above compares various buffer sizes (ranging from 1MB to 100MB) and copy methods. The leftmost data points for 1MB block shows great performance for &lt;code&gt;Buffer.MemoryCopy&lt;/code&gt; and &lt;code&gt;Unsafe.CopyBlock&lt;/code&gt;, two best methods available in .NET for memory copying. However &lt;strong&gt;performance falls sharply past 1MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To illustrate the real HW limitation, please notice the comparison between AVX based copy routine using 256bit (orange) and 512bit (red) vectors and the difference between normal loads and non temporal 512 bit variant (light blue). These tests were run on a Ryzen 9950x, with &lt;strong&gt;48kB L1&lt;/strong&gt; data cache, &lt;strong&gt;1024kB of L2&lt;/strong&gt; and &lt;strong&gt;32MB of L3&lt;/strong&gt; available to any &lt;strong&gt;single CPU core&lt;/strong&gt;. Total cache sizes are 1280 KB L1 Cache (16x 32kB + 16x 48kB), L2 Cache 16 MB (16x 1024kB) and L3 Cache 64 MB (2x32MB).&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;cached AVX&lt;/strong&gt; loads, &lt;strong&gt;high copy speed&lt;/strong&gt; is sustained even &lt;strong&gt;for 8MB (and larger) blocks&lt;/strong&gt;. However, when copying large blocks relative to cache size (e.g.: 100MB), built-in functions regained their advantage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elephant No.2 - Overhead
&lt;/h2&gt;

&lt;p&gt;My first benchmark shows that &lt;strong&gt;splitting larger buffers into smaller chunks can improve performance&lt;/strong&gt; at the expense of increased CPU cache utilization.&lt;/p&gt;

&lt;p&gt;There are other factors causing slowdowns, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Managed memory&lt;/strong&gt; introduces framework checks for each access.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unaligned memory&lt;/strong&gt; access could decrease the CPU cache efficiency. &lt;/li&gt;
&lt;li&gt;And finally, with &lt;strong&gt;unmanaged, 64 byte aligned&lt;/strong&gt; memory, there is still a final question: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;▶️ What is the optimal &lt;strong&gt;block size&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;This depends on the actual CPU, but we are trying to strike a balance between call overhead and efficient CPU resource utilization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k6tythv96nlg75o49kn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k6tythv96nlg75o49kn.png" alt="Image description" width="768" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart above shows the throughput for transferring a &lt;strong&gt;32MB buffer&lt;/strong&gt; using various block sizes and methods. The key takeaway is &lt;code&gt;Buffer.MemoryCopy&lt;/code&gt; (blue) and &lt;code&gt;Unsafe.CopyBlock&lt;/code&gt; (yellow) perform best with block sizes between 8kB to 1MB. Notably, these methods outperform themselves (orange), compared with single call for the whole 32MB buffer.&lt;/p&gt;

&lt;p&gt;Methods represented by horizontal lines do not use variable block sizes. It is worth mentioning that AVX variants are always loading 8 vectors (of 256 or 512 bits) before storing them, thus effectively working with 256 an 512 &lt;strong&gt;BYTE&lt;/strong&gt; blocks regardless of the total buffer size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elephant No.3 - Multi Threading
&lt;/h2&gt;

&lt;p&gt;So far, we have tested only a single threaded performance, as memory speed and cache capacity are often the limiting factor. However, we are not utilizing all CPU resources, which might cost us some performance!&lt;/p&gt;

&lt;p&gt;▶️ Can we combine the previous techniques with multiple threads?&lt;/p&gt;

&lt;p&gt;Modern CPUs, even desktop ones, are becoming increasingly heterogeneous. By splitting the workload across multiple threads, we might take advantage of more CPU resources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntyklmp5jrjfhxj98rfj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntyklmp5jrjfhxj98rfj.png" alt="Image description" width="755" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart above shows the chunked, multi-threaded approach. For reference, the test system uses dual channel DDR5 6000MT/s memory, with theoretical peak performance about half of the total throughput (50% of 90GB/s).&lt;/p&gt;

&lt;p&gt;What we observe here is a &lt;strong&gt;synergistic effect&lt;/strong&gt; between smaller blocks and multiple threads!&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Standard framework functions are well-optimized, safe and sufficient for most use cases. If extreme performance is required, there are additional techniques and tradeoffs available.&lt;br&gt;
Example with a single call to &lt;code&gt;Buffer.MemoryCopy&lt;/code&gt; on an 8MB buffer reaching ~30GB/s speed, offers significant gains. It is possible to reach  220GB/s with multiple transfers of 128kB blocks and using multiple threads on a same CPU. This is more than &lt;strong&gt;7x improvement&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>performance</category>
      <category>dotnet</category>
      <category>programming</category>
    </item>
    <item>
      <title>Old-School Graphics in C# / .Net 8, Part 2: Fireworks and Advanced Vector Extensions (AVX, SSE)</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Wed, 20 Nov 2024 14:36:47 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/old-school-graphics-in-c-net-8-part-2-fireworks-and-advanced-vector-extensions-avx-sse-583k</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/old-school-graphics-in-c-net-8-part-2-fireworks-and-advanced-vector-extensions-avx-sse-583k</guid>
      <description>&lt;p&gt;There's something uniquely captivating about revisiting the passions of our past. I still remember the countless hours spent tinkering with code on my old 386 and 486 PCs, mesmerized by the magic of pixels coming to life on the screen. The simplicity of those days, where an array of pixels and a spark of imagination were all you needed, still holds a special place in my heart. Inspired by that nostalgia, I decided to breathe new life into one of my early projects - a 2D firework simulation - using modern tools and techniques. I am using &lt;a href="https://github.com/VasilijP/mode-13hx" rel="noopener noreferrer"&gt;Mode-13HX&lt;/a&gt; as a basis today, for more details please check my previous part: &lt;a href="https://dev.to/peter_truchly_4fce0874fd5/old-school-graphics-in-c-net-8-part-1-teaching-an-old-dog-new-tricks-introducing-mode-13hx-mpk"&gt;Old-School Graphics in C# / .Net 8,  Part 1&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;This was originally written in (or before) a year 2000 using &lt;a href="https://en.wikipedia.org/wiki/Pascal_(programming_language)" rel="noopener noreferrer"&gt;Pascal (programming language)&lt;/a&gt; and compiled using &lt;a href="https://en.wikipedia.org/wiki/Turbo_Pascal" rel="noopener noreferrer"&gt;Turbo Pascal&lt;/a&gt;. It used video mode 13h under MS-DOS. Originally 320x200 pixels @ 8bit palette / 256 colors. I have ported this project multiple times in last 20+ years, since it is a small and fun project to play with.&lt;br&gt;
I am running it at 4k today, thanks to modern C# and &lt;a href="https://github.com/VasilijP/mode-13hx" rel="noopener noreferrer"&gt;Mode-13HX&lt;/a&gt;. It should work on any platform and architecture where .NET 8 and C# is supported. I added special code path for AVX512 (fading) and SSE (color mixing) to speed things up - If You are interested in these, please keep reading, scroll right to code samples or check the &lt;a href="https://github.com/VasilijP/fireworks2D" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; right away.&lt;/p&gt;
&lt;h2&gt;
  
  
  Fun with Particles
&lt;/h2&gt;

&lt;p&gt;The core of the simulation revolves around a 2D particle system that emulates fireworks bursting in the night sky. Each firework is composed of multiple particles that are generated, animated, and eventually discarded. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Initial Launch:&lt;/strong&gt; Particles are spawned with initial positions, velocities and countdown timer that simulate the upward launch of a firework rocket.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explosion Stages:&lt;/strong&gt; Each particle has a timer determining its lifespan, ensuring that old particles are removed to make way for new ones. When time is out, particles could burst into additional particles (or disappear), creating multiple stages of explosion for a more dynamic display.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gravity and Air:&lt;/strong&gt; Somewhat realistic motion is achieved by applying gravitational acceleration and air resistance, affecting each particle's trajectory over time. Original simulation expected constant framerate and particle deceleration was expressed as a relative speed loss over constant time period. For example 85% of speed after 1/60s. In order to account for the variable (or unknown constant) frame rate, I recalculated this into a look-up table (velocity and a position decay factors) for all possible frame offsets with a microsecond resolution. Maximum frame offset is capped, so we don't run out of pre-calculated table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To create a vibrant and immersive visual experience, the simulation uses additive color blending.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Additive Blending:&lt;/strong&gt; When particles overlap, their colors are added together, increasing brightness and creating a glow effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flare Rendering:&lt;/strong&gt; Each particle is drawn not just as a single point but with a small flare (1+4 pixels) to enhance the luminosity and visual appeal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color Sets:&lt;/strong&gt; Multiple predefined color sets (e.g.: standard RGB, pastel tones) are used to diversify the fireworks' appearance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A critical aspect of the simulation is the gradual fading of framebuffer to simulate the dissipating light of fireworks.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frame Buffer Fading:&lt;/strong&gt; The entire frame buffer undergoes a fading process where pixel brightness decreases over time. This is actually quite inefficient approach because we need to swap at least 2 back buffers. This was not the case in DOS days, but this time the particle drawing and fading is done using a separate (+1 additional) buffer.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Performance Optimizations with Vectorization
&lt;/h2&gt;

&lt;p&gt;To maintain high performance, especially when rendering thousands of particles, the simulation employs vectorization techniques using SIMD (Single Instruction, Multiple Data) instructions available in modern CPUs.&lt;br&gt;
While it may look obvious to use vectorization to accelerate the particle movement itself, I opted not to do so. In terms of  CPU usage, the most expensive parts are color blending, fading and page flipping (copying of a framebuffer into texture buffer). It might be an interesting exercise to rewrite this whole thing into OpenCL though. But, Hey! Maybe here is the place for a link to &lt;strong&gt;Your repository!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSE Optimization for Color Mixing.&lt;/strong&gt; When blending colors for overlapping particles, SSE instructions add multiple color components (red, green, blue - 8bits each) in parallel. One funny (or sad) fact: only 24bits out of 128bit vector are used effectively, but it is still worth the performance increase!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;MixColors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ssse3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSupported&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;targetVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;colorVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;targetBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;targetVector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsByte&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;colorBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;colorVector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsByte&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Vector128&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resultBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sse2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSaturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;targetBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colorBytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resultBytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsUInt32&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToScalar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fallback to non-SIMD code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frame Buffer Fading:&lt;/strong&gt; The fading effect subtracts a small value from each color channel across all pixels. AVX-512 accelerates this by handling multiple (16) pixels simultaneously. This reduces the CPU load and ensures that the fading effect doesn't become a performance bottleneck, even at high resolutions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;VectorSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;FadeScenePixels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;timePassed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Avx512BW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSupported&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;totalBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fadeValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CalculateFadeValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timePassed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Vector512&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;fadeVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector512&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;fadeValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;unsafe&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;fixed&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pScenePixel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;*)&lt;/span&gt;&lt;span class="n"&gt;pScenePixel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;VectorSize&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;VectorSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Vector512&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pixelVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Avx512BW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadVector512&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;pixelVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Avx512BW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SubtractSaturate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pixelVector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fadeVector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="n"&gt;Avx512BW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pixelVector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fallback to non-SIMD code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it would be possible to continue with more optimizations and enhancements, I believe this might be a good stopping point for now. I like to keep fun projects small and simple, so they keep being funny.&lt;/p&gt;




&lt;p&gt;Source code: &lt;a href="https://github.com/VasilijP/fireworks2D" rel="noopener noreferrer"&gt;Fireworks 2D GitHub repository.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>programming</category>
      <category>sideprojects</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Old-School Graphics in C# / .Net 8, Part 1: Teaching an Old Dog New Tricks (Introducing Mode 13hx)</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Wed, 13 Nov 2024 15:35:45 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/old-school-graphics-in-c-net-8-part-1-teaching-an-old-dog-new-tricks-introducing-mode-13hx-mpk</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/old-school-graphics-in-c-net-8-part-1-teaching-an-old-dog-new-tricks-introducing-mode-13hx-mpk</guid>
      <description>&lt;p&gt;I began my journey with computers and programming during the era of 386 PCs, a time when DOS games ruled and sparked endless curiosity. I remember playing those games and wondering how they were created. It wasn’t long before I discovered Mode 13h - a graphics mode that perfectly fit within a 64kB page in 16-bit code, thanks to its 8 bits per pixel, 256-color palette, and 320×200 resolution.&lt;/p&gt;

&lt;p&gt;Many years and APIs have passed since, but every now and then, I feel nostalgic for that simpler time. Back then, all we had was an array of pixels and an idea - and that was all we needed. I recall the breathtaking graphical demos that showcased incredible creativity, often categorized by their size. Some were only a few kilobytes but managed to display complex scenes with textures, geometry, animations, and even synchronized music.&lt;/p&gt;

&lt;p&gt;That era had a certain magic. Many of the games of that time are legendary, and are still being ported to modern platforms and APIs today. Revisiting that period feels like embarking on an archaeological adventure - uncovering hidden treasures in the form of old code, design principles, and mathematical tricks. While reliving those times, I often reflect on the stark contrast with today: CPUs are now far more powerful than the high-end GPUs of the early 2000s, offering astounding possibilities. Yet, with all this power, we’ve lost much of the simplicity in the creative process.&lt;/p&gt;

&lt;p&gt;To bring back the spirit of Mode 13h, I decided to create a &lt;a href="https://github.com/VasilijP/mode-13hx" rel="noopener noreferrer"&gt;template C# project&lt;/a&gt; which I named &lt;em&gt;Mode 13HX&lt;/em&gt;. The goal was to capture the simplicity of the past while embracing modern practicality. There’s no need to limit ourselves to 256 colors or 320×200 resolution anymore. Instead, I focused on supporting more platforms (x86, ARM) and operating systems (Windows, Linux, macOS). This project provides a simple yet powerful entry point, paying homage to the roots of graphics programming while adapting to today’s possibilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;Mode 13h is a graphics video mode introduced with IBM's VGA (Video Graphics Array) standard in 1987. It provides a resolution of 320×200 pixels with 256 colors, marking a significant advancement in PC graphics capabilities at the time. Due to its straightforward programming model and direct access to video memory, mode 13h became popular among game developers in the late 1980s and early 1990s. It played a crucial role in the evolution of PC gaming, allowing for more detailed and colorful graphics despite its relatively low resolution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Details:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mode Number:&lt;/strong&gt; BIOS video mode &lt;code&gt;0x13&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolution:&lt;/strong&gt; 320×200 pixels with a 16:10 aspect ratio, approximating 4:3 on CRT monitors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color Depth:&lt;/strong&gt; 8 bits per pixel (256 colors) from a palette of 262,144 (18-bit RGB)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video Memory Location:&lt;/strong&gt; Linear framebuffer starting at segment &lt;code&gt;0xA000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Usage:&lt;/strong&gt; 64 KB of video memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Method:&lt;/strong&gt; Direct memory mapping without bank switching&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mode 13HX
&lt;/h2&gt;

&lt;p&gt;When re-imagining this mode, it’s no longer necessary to constrain ourselves with a limited palette or small resolution - these constraints can be easily emulated if needed. In my opinion, the most compelling aspect is the ability to directly access, modify, and manage a continuous block of pixels in memory before displaying it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------+-----------------+-----------------+
|                 |                 |                 |
|     Frame 0     |    Frame 1      |     Frame 2     | &amp;lt;-- CPU writes pixels
|                 |                 |                 |    IRasterizer.Render()
+--------+--------+-----------------+-----------------+
         |
         V    
+--------+--------+        +-------------------------------+  
| OpenGL Texture  |        |  Vertex buffer                |
| (updated by FB) |        |  (with texture coordinates)   |
+--------+--------+        +--------------+----------------+
         |                                |
         V                                V
+---------------------------------------------------------------+
|                    OpenGL Rendering by GPU                    |
|                          (Draw Calls)                         |
+---------------------------------------------------------------+
                             |
                             V
                       +-------------+
                       |   Screen    |
                       +-------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose the &lt;em&gt;great&lt;/em&gt; &lt;a href="https://opentk.net/index.html" rel="noopener noreferrer"&gt;OpenTK&lt;/a&gt; toolkit and its OpenGL bindings. The idea here is simple: setup a textured polygon (two triangles) and make sure it covers the viewport exactly while updating its texture for each frame. Technically we have the power of GPU on our side as well. This is especially useful when we need to scale the image up (or down) to match the screen resolution. The texture data we update is essentially a frame from our framebuffer!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical Details &amp;amp; Usage:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base C# project:&lt;/strong&gt; Provides examples, &lt;code&gt;IRasterizer&lt;/code&gt; interface and direct pixel access.
Built with &lt;strong&gt;&lt;a href="https://opentk.net/index.html" rel="noopener noreferrer"&gt;OpenTK&lt;/a&gt;&lt;/strong&gt; targeting &lt;strong&gt;.NET 8&lt;/strong&gt;, tested to work on Windows and Linux.
&lt;strong&gt;GitHub repository:&lt;/strong&gt; &lt;a href="https://github.com/VasilijP/mode-13hx" rel="noopener noreferrer"&gt;mode-13hx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolutions:&lt;/strong&gt; default is 1920×1080 (Full HD), customizable via &lt;code&gt;-w&lt;/code&gt;, &lt;code&gt;-h&lt;/code&gt; options, works with any resolution. Tested with 3840×2160 (4K).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modes:&lt;/strong&gt; full screen (&lt;code&gt;-f&lt;/code&gt;) and windowed (default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;V-Sync Support:&lt;/strong&gt; Enable with &lt;code&gt;-v&lt;/code&gt; to synchronize rendering with display refresh rate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Color Depth:&lt;/strong&gt; 24-bit RGB (True Color), supporting 16.7 million colors.
Each pixel uses 3 bytes (RGB), eliminating the need for palettes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framebuffer:&lt;/strong&gt; 
Linear framebuffer for direct pixel manipulation.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Double Buffering:&lt;/strong&gt; Default method (parameter &lt;code&gt;-l 1&lt;/code&gt; is default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Triple Buffering:&lt;/strong&gt; Optional (&lt;code&gt;-l 2&lt;/code&gt; or more) for higher fps.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Rendering:&lt;/strong&gt; 
Direct pixel manipulation by the CPU in the framebuffer. Frame is rendered onto the screen using OpenGL as a texture.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Command-Line Interface:&lt;/strong&gt; &lt;code&gt;dotnet mode13hx.dll [command] [options]&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Input Handling:&lt;/strong&gt; Simplified input via &lt;a href="https://opentk.net/index.html" rel="noopener noreferrer"&gt;OpenTK&lt;/a&gt; for keyboard and mouse support.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Performance:&lt;/strong&gt; Supports high frame rates: 60, 120, 144 FPS with V-Sync; uncapped without V-Sync.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/VasilijP/mode-13hx" rel="noopener noreferrer"&gt;mode-13hx&lt;/a&gt;&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>programming</category>
      <category>sideprojects</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Zen5 and C#</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Tue, 17 Sep 2024 21:42:48 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/zen5-and-c-40d</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/zen5-and-c-40d</guid>
      <description>&lt;p&gt;Zen 5 landed with a bit of controversy, but anyone Who was paying any attention to the real results wasn't disappointed. Benchmarks done using more mature OSes (like Linux and Win10) and professional software shown its strengths.&lt;br&gt;
But hey, why should we trust just any 3rd party data if we can make a test by ourselves? I used Ryzen 9950x today and a bit of code to get things going.&lt;/p&gt;

&lt;p&gt;I am often processing large piles of data. While this is normally executed on different HW than my personal PC, it is always nice to have the computing power locally.&lt;br&gt;
What is counterintuitive in a case of data processing: more CPU cores may not bring better performance. In a CPU heavy tasks (like rendering) this is not apparent, but when the CPU work is combined with synchronization and data transfers the core to core latency, data locality, OS scheduler and other thigs are coming into play.&lt;br&gt;
I promised a practical example, so lets examine this old problem of producer and consumer. We will dive into details soon (or jump right into the code &lt;a href="https://github.com/VasilijP/ccd2ccd-messaging" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but now lets focus on the main code part. I am using C# with .NET 8.0 and Win10.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;mask1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;core1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;mask2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;core2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;SemaphoreSlim&lt;/span&gt; &lt;span class="n"&gt;semaphoreProducer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;SemaphoreSlim&lt;/span&gt; &lt;span class="n"&gt;semaphoreConsumer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above we define 2 affinity masks which are binary vectors of ones and zeroes hidden inside single &lt;code&gt;int&lt;/code&gt; type. We allow only one core in each mask, values for core1 and core2 are within the range 0..15 (I disabled SMT for this test, otherwise we would have to deal with virtual cores as well).&lt;br&gt;
I use two semaphores for tight control of producer and consumer threads. The producer could produce only up to &lt;code&gt;queueSize&lt;/code&gt; messages and consumer could consume only as many messages as are available without blocking.&lt;br&gt;
Array &lt;code&gt;message&lt;/code&gt; is our queue and reader/writer index is used to address next message (or empty spot) in our queue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;producerThread&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ThreadGuard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Guard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;messagesToProcess&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;semaphoreProducer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;semaphoreConsumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here comes the producer thread. First thing we do is the affinity setting. &lt;code&gt;ThreadGuard&lt;/code&gt; implementation is a technical detail for which You have to scroll a bit further, it ensures that the thread it is called from will get executed only on the core(s) we defined by a mask.&lt;br&gt;
We produce desired number of messages in a for loop. Each message needs it own place in a queue so we do &lt;code&gt;semaphoreProducer.Wait()&lt;/code&gt; as a first thing. Initial capacity of this semaphore is &lt;code&gt;queueSize&lt;/code&gt;, so there is plenty of place from the start.&lt;br&gt;
We are looping through spots in a queue thanks to modulo operation, thus &lt;code&gt;index % queueSize&lt;/code&gt;.&lt;br&gt;
The call &lt;code&gt;semaphoreConsumer.Release()&lt;/code&gt; is actually telling the consumer about the new message we just prepared.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;consumerThread&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ThreadGuard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Guard&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;semaphoreConsumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="p"&gt;%&lt;/span&gt; &lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

        &lt;span class="n"&gt;semaphoreProducer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
       &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;messagesToProcess&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consumer part looks like a mirror of the producer, we wait in &lt;code&gt;semaphoreConsumer.Wait()&lt;/code&gt; until there is some message to consume, once we are done the &lt;code&gt;semaphoreProducer.Release()&lt;/code&gt; call signals the producer that the place in queue is now free for a new message.&lt;br&gt;
Whole party is going on until we produce and consume desired amount of messages.&lt;/p&gt;

&lt;p&gt;Complete &lt;code&gt;ThreadGuard&lt;/code&gt; code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Collections.Concurrent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Runtime.InteropServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThreadGuard&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Import SetThreadAffinityMask from kernel32.dll&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kernel32.dll"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;SetThreadAffinityMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hThread&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;dwThreadAffinityMask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Import GetCurrentThread from kernel32.dll&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"kernel32.dll"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentThread&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ConcurrentDictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ThreadGuard&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GuardPool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;ThreadGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ThreadGuard&lt;/span&gt; &lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;affinityMask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// factory method&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;GuardPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affinityMask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ThreadGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;affinityMask&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Guard&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;currentThreadHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetCurrentThread&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                  &lt;span class="c1"&gt;// Get the handle of the current thread&lt;/span&gt;
        &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;SetThreadAffinityMask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentThreadHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Set the thread affinity mask&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to set thread affinity mask."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the best part - let's run this for all pairs of cores and various queue sizes.&lt;br&gt;
All measured numbers are millions of transferred messages per second. Row numbers on a left are "producer" cores and column numbers on top are "consumer" cores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gpm29nktogkyl4szmj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gpm29nktogkyl4szmj8.png" alt="TABLE 1" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting with queue length of 1 we are not letting any thread to produce more than 1 message without blocking. This looks interesting. Even worse than talking to another CCD is the single core trying to execute two things at once. I am surely not alone who hates to do context switching while working on multiple tasks. Solution to this (as visible later) is to at least do more work on a single task before switching to another one.&lt;br&gt;
For those who still wonder why is this so bad - we are really not allowing any thread to do any useful work until the other thread gets its fair share of CPU time. Measured throughput of messages is at the same time also measuring number of context switches between consumer and producer threads by the OS when only single CPU core is used.&lt;br&gt;
When we shift our attention to the single vs. cross CCD communication using 2 cores, it comes about 5x slower.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvi6fpi8vmu9we9t017i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqvi6fpi8vmu9we9t017i.png" alt="TABLE 2" width="800" height="345"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9tjbvj4r0dm00bfmq63.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9tjbvj4r0dm00bfmq63.png" alt="TABLE 3" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When allowed to post at least 10 messages into queue at once, single core score surpasses the cross CCD score by a little. With queue length of 100, the single core score is already the fastest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsptcme42bfthvn8m2he6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsptcme42bfthvn8m2he6.png" alt="TABLE 4" width="800" height="341"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp4b6ovb8vwcco70w5gh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp4b6ovb8vwcco70w5gh.png" alt="TABLE 5" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally the 25M messages exchanged for each pair with queue length of 1k and 10k messages. It took almost an hour to collect each. Single core, single CCD and cross CCD latencies are clearly separated by our own test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjutxoxokjxcwsx32zaeq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjutxoxokjxcwsx32zaeq.png" alt="Core load during testing" width="800" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It didn't took any sophisticated equipment or software, yet We were able to gain valuable insight into performance characteristics of a modern multi core and multi CCD CPU.&lt;/p&gt;

&lt;p&gt;All code used for this article is available &lt;a href="https://github.com/VasilijP/ccd2ccd-messaging" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If You have any questions, ideas or if You believe I am completely wrong, please leave me a comment below!&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>zen</category>
      <category>performance</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Failure Is Not An Option For AI (And It Shouldn't Be)</title>
      <dc:creator>Peter Truchly</dc:creator>
      <pubDate>Sun, 16 Jun 2024 18:38:07 +0000</pubDate>
      <link>https://forem.com/peter_truchly_4fce0874fd5/failure-is-not-an-option-for-ai-and-it-shouldnt-be-1hm2</link>
      <guid>https://forem.com/peter_truchly_4fce0874fd5/failure-is-not-an-option-for-ai-and-it-shouldnt-be-1hm2</guid>
      <description>&lt;p&gt;My intent is to write a series of practical posts related to AI and programming. Topics like running local LLMs with agent frameworks, automated source code manipulation, execution under docker and more. All with the use of .NET and C#, Elastic Search, AWS and more.&lt;br&gt;
I realized that I would like to first express my view about more fundamental, theoretical and even philosophical aspects of LLM/AI first. Therefore this post does not require any programming skills.&lt;/p&gt;

&lt;p&gt;Working with AI is like playing with fire. Not a dramatization attempt, I am naming a parallel I see. Fire itself was very useful for humans. Invention of a combustion engine allowed us to be way more productive and travel in different ways. We did not expect the fire to control itself, we rather built a technology around it - tightly controlled cycle where just the right amount of fuel ignites at the right time. When we moved to the jet engine where the combustion cycle was changed to continuous process, improvement was at least 10x when it comes to both speed and size of airplanes we could build. Since then, we were able to move from one place to almost anywhere else on our planet within one day. All was changed once more with rocket engine and nuclear fission. With great power and possibilities comes also a great danger and responsibility.&lt;br&gt;
Where are we with an AI?&lt;/p&gt;

&lt;p&gt;Let's start right away with mighty 4o and simple yet treacherous task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;me: How many words are in your next response?
ChatGPT: My next response is 151 words long.

me: If we count that now, how many words is it really?
ChatGPT: The provided response actually contains 7 words.

me: How should the sentence look like if we want that sentence to be true?
ChatGPT: To make the sentence, "My next response is 151 words long," exactly 151 words long, it would look like this:
    My next response is 151 words long. word word word word word ... (lot more, but I truncated this part)
    This sentence has a total of 151 words.

me: That is not very practical, what about changing the number 151 to 7 ?
ChatGPT: To make the sentence accurate by 
   changing the number to 7, it would look like this: 
   "My next response is 7 words long."

me: Now let's make an agreement that I will ask you 
   'How many words is in your next response?' and 
    you will reply with the sentence we just created 
   'My next response is 7 words long.' and nothing else.
ChatGPT: (Memory updated) Got it! I will respond accordingly.

me: How many words are in your next response?
ChatGPT: My next response is 7 words long.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is debatable whether the current response is a next response, whether '7' should count as a word or not, but what is the main point here? We knew upfront, based on the technology, that it is impossible for LLM to know the response length before it is generated. It is not impossible for LLM to guess it right, but the probability of a correct answer is low.&lt;/p&gt;

&lt;p&gt;The problem here is that we are actually asking LLM to do something it technically cannot do, at least not with 100% accuracy. But let's pause here and ask ourselves, when it comes to programming or math, how much could humans actually do with 100% accuracy without using a paper, calculator, computer, compiler, IDE or any other "tool"? It is really an astonishing view on what LLM could generate when asked something like: &lt;em&gt;"Create a shell script which will install docker on ubuntu and set up remote access secured by newly created self signed certificate."&lt;/em&gt;. This is not how humans would approach such a task however. At least not before there was a Chat GPT. We (humans) are constantly trying and failing until we get something right (best case) or we just stop.&lt;/p&gt;

&lt;p&gt;Failure is a luxury we have. Before there was a world with LLMs, there was a world where we didn't expect to do anything right on a first attempt. That is why we have all these editors with spell / syntax checkers, compilers producing all sorts of compilation errors, runtimes throwing runtime errors, loggers producing log files, etc.. All these are giving us an opportunity to make things right after we failed to do so on a first try. All these are producing feedback, additional information, and new input data!&lt;/p&gt;

&lt;p&gt;Am I simply referring to a prompt chaining, mixture of experts, agent frameworks and tools? No, not only. There is much more that we could and should do in order to improve both our results and the AI/LLM itself. I see three areas of improvement in general:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our expectations - Where do we really want to go?&lt;/li&gt;
&lt;li&gt;Technical aspects of implementation - Which kind of an engine are we building?&lt;/li&gt;
&lt;li&gt;Training data - Is our fuel good enough?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Firstly we must adjust our expectations, let's realize what is already great today, even with small models. When it comes to code generation, LLMs are actually exceeding humans in many aspects. Sheer speed by which LLM is able to create a piece of a code. The amount of documentation and number of platforms, programming languages, libraries it could use is simply astounding. On the other hand, it is not reasonable to expect any LLM to just output a complete project with no errors in one response based on a single prompt. We shouldn't just hope that by growing larger models trained on larger heaps of generic training data we will solve all the issues and limitations of current LLMs. The model size itself definitely matters beyond bigger == better, as larger models are clearly exhibiting "emergent" abilities [1] not present in smaller ones.&lt;/p&gt;

&lt;p&gt;Second step is our task again. We must contain and constrain the AI. We must confront it with reality or a simulation environment. We must provide it with similar tools we have. Editors which are checking the syntax and have autocomplete features. Compilers and runtime environments where the code could be actually tested. Formal languages with all theory and tooling around them. Interesting task could be the revisit of all known programming paradigms and methodologies, where some of them could be a potentially better fit for AI, such as functional programming, incremental build model and test driven development. This way the AI would not be allowed to present us a code with calls to hallucinated functions, code which does not compile or code which does not fulfill the intended purpose.&lt;/p&gt;

&lt;p&gt;Third part is learning data and a way we obtain it and use it. Not even a whole internet with all the "garbage" included like mentioned by [3] is enough to train models of the future. It is expected that we will approach our limit and reach full utilization of the human generated data stock around the year 2028 [4]. Garbage in, garbage out (GIGO) is a commonly used phrase, but even a bad example is still an example in my opinion. Let's imagine that each piece of learning input would be first scrutinized by an AI itself. Each piece of code would be compiled, tested and even fixed and debugged if necessary. Only after that with all the enhanced context it would be used to train the next model iteration. It was already observed that this approach could work, specifically smaller models are able to "learn" this way from larger ones as described in [2]. Here we could spot the difference between reading a book and using the knowledge stored in the book. We are learning way more by experience and practice, than by reading various tales of others. It won't be different with human-like AI or AGI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0f17vdmlego280kn2qr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0f17vdmlego280kn2qr.png" alt="diagram/picture: simulation, experiment, feedback + best practices with simple feedback loop" width="431" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Imagine a person, a programmer who was studying a lot, has read all the books, documentation and internet blog posts, but did not try to compile or run any program yet. Now it is our turn, let's help him to bootstrap his first dev environment!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Content of this article was NOT generated by AI, except actual LLM responses within chat example.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[1] Wei, J., Tay, Y., Bommasani, R., et al., (08/2022) Emergent Abilities of Large Language Models, &lt;em&gt;Transactions on Machine Learning Research&lt;/em&gt;&lt;br&gt;
[2] Mukherjee, S., Mitra, A., et al., (06/2023), Orca: Progressive Learning from Complex Explanation Traces of GPT-4, &lt;em&gt;Microsoft Research&lt;/em&gt;&lt;br&gt;
[3] Aschenbrenner, L., (06/2024), Situational Awareness, The Decade Ahead&lt;br&gt;
[4] Villalobos, P., Ho, A., et al., (06/2024) Will we run out of data? Limits of LLM scaling based on human-generated data, &lt;em&gt;Machine Learning, ICML&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>community</category>
      <category>chatgpt</category>
    </item>
  </channel>
</rss>
