<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>wency suo - blog</title>
      <link>https://wencysuo.com/blog/</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://wencysuo.com/blog/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Thu, 09 Apr 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>How To Play Cut The Rope on Your TV</title>
          <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
          <author>wency suo</author>
          <link>https://wencysuo.com/blog/cut-the-rope-tv/</link>
          <guid>https://wencysuo.com/blog/cut-the-rope-tv/</guid>
          <description xml:base="https://wencysuo.com/blog/cut-the-rope-tv/">&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;media.tenor.com&#x2F;WPHiaE-KHhUAAAAM&#x2F;eating-om-nom.gif&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;intro&quot;&gt;intro&lt;&#x2F;h3&gt;
&lt;p&gt;It might come as a surprise to most that CutTheRope(CTR), a childhood banger of a game, has a TV distribution on LGTV platforms. The game features the same rope cutting, bubble popping, and whoopee cushion squeezing mechanics modified for a remote controller.&lt;&#x2F;p&gt;
&lt;p&gt;We played significantly more than a few levels on the TV and long story short now the remote is lagging because we’ve drained the battery playing so much CTR.&lt;&#x2F;p&gt;
&lt;p&gt;Having decided that replacing a TV remote battery was simply too difficult, we decided that the remote was unecessary and built a way to play CTR on the TV without it :)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;brainstorming&quot;&gt;brainstorming&lt;&#x2F;h2&gt;
&lt;p&gt;What does it actually look like to play CTR on the TV without a remote? While brainstorming control interface designs, we can generalize this problem statement to simply:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;how can we remotely control TVs without a little plastic box that natively speaks to our tvOS via a mix of bluetooth and infrared signals?&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;With this pretty vague problem statement, let’s define some more concrete project goals that will help tighten the design:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Accuracy! It can be frustrating to play with the remote since it’s not very sensitive, are there ways to improve this?&lt;&#x2F;li&gt;
&lt;li&gt;Reduce lag and intuitive game controls&lt;&#x2F;li&gt;
&lt;li&gt;Identify a consistent playing surface that&#x27;s easy for the user to interact with&lt;&#x2F;li&gt;
&lt;li&gt;Actually cut the ropes and interact with other in game elements&lt;&#x2F;li&gt;
&lt;li&gt;Beat some levels with our setup&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;design-0-gesture-recognition-on-laptop-with-webcam&quot;&gt;Design 0: gesture recognition on laptop with webcam&lt;&#x2F;h2&gt;
&lt;p&gt;As a proof of concept, we wanted to start by seeing if we could play a web-based port of CTR on a laptop with the webcam for gesture recognition.&lt;&#x2F;p&gt;
&lt;p&gt;To build this we relied primarily on computer vision tools like OpenCV and MediaPipe. If you see videos or photos of other people’s projects that use vision recognition, there’s a good chance that at least a portion of their code or a dependency uses OpenCV. OpenCV contains the webcam control and frame processing code we need, wrapped nicely in the &lt;code&gt;VideoCapture&lt;&#x2F;code&gt; class. The class allows you to read a stream of frames from a video file, camera, or image sequence making the setup for frame by frame manipulation a 5 lines-of-code problem. Since we’re using the webcam input channel, OpenCV also spins up a live video player on my laptop when running the program to make visualization super easy.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;mediapipe.dev&#x2F;images&#x2F;mobile&#x2F;hand_crops.png&quot; alt=&quot;Google&amp;#39;s media pipe gestures &quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;MediaPipe provides access to the hand gesture recognition models that are applied to the video data captured by the webcam. Google has several image recognition models in MediaPipe to choose from. We initially experimented with a simple hand recognition model for point tracking but decided to use specifically a gesture recognition model to easily map in-game controls.&lt;&#x2F;p&gt;
&lt;p&gt;For this version of our code, we used 👆 for dragging, 🖐 as a neutral do nothing but move the cursor. 👊 as our click.&lt;&#x2F;p&gt;
&lt;p&gt;Recognizing gestures is only half the problem. Now we need to use a tool like PyAutoGUI, a programmatic gui controller, to actually send native mouse controls like drag, click, cursor movements to my laptop. Each movement is broken down into two steps, &lt;code&gt;pyautogui.moveTo(x,y)&lt;&#x2F;code&gt; and &lt;code&gt;pyautogui.mouseUp()&lt;&#x2F;code&gt; or &lt;code&gt;mouseDown()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;At this stage, we noticed that the gesture recognition model really struggled with recognizing hand gestures that were incomplete or on the edges of the image so we added a small non-playable margin within the full image as an easy fix. All of this gives us a functional, albeit basic, way to control the game via webcam!&lt;&#x2F;p&gt;
&lt;p&gt;You’ll notice that this PoC ignores a big part of our CTR-for-TV dream: LG’s proprietary tvOS…&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-1-gesture-control-on-tv-with-laptop-webcam&quot;&gt;Design 1: gesture control on TV with laptop webcam&lt;&#x2F;h2&gt;
&lt;p&gt;Moving from local laptop control to the TV meant we had to figure out how the TV expects to receive mouse events. This means figuring out how command packets were being sent from the remote to the TV in order to mimic the dragging motion of LG’s Magic Remote. I’m not sure how accessible other tvOS’s are but the target audience for LG TV’s WebOS seems largely to be app developers for the ecosystem. Not many of them are trying to optimize for the smoothest remote-less CTR experience, much to our dismay.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;connecting-to-the-tv&quot;&gt;connecting to the tv&lt;&#x2F;h3&gt;
&lt;p&gt;LG’s webOS exposes a websocket that apps and remote-control clients can use to communicate with the TV. Our client connects to a secure local websocket endpoint on port 3001, registers itself with the TV, and receives a client key after the user approves the connection.&lt;&#x2F;p&gt;
&lt;p&gt;The key can then be saved locally and reused so the TV does not ask for permission every time the app launches. The commands are then sent through a secondary websocket dedicated to mouse and keyboard inputs.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;communing-with-the-tv&quot;&gt;communing with the tv&lt;&#x2F;h3&gt;
&lt;p&gt;After some initial testing we quickly realized that WebOS was going to be a pain for us to work with. LG has proprietary message encodings that are not well documented. Meaning there are any number of private functions that Magic Remote has access to that we don’t even know exist. Values that aren’t used by the TV are ignored, true&#x2F;false string values do nothing, and missing values actively cause errors in the response payloads.&lt;&#x2F;p&gt;
&lt;p&gt;Through repeatedly sending commands until the tv did something and some snooping through source code, we figured out that the move command sent to WebOS has a hidden parameter called drag that is commonly set to down in order to simulate clicking behavior. This was the key to simulating the click-and-hold necessary to actually “cut” the in-game rope, otherwise we would just be moving from one side of the rope to the other without interacting with it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;fighting-with-the-tv&quot;&gt;fighting with the tv&lt;&#x2F;h3&gt;
&lt;p&gt;Tedious experimentation of the controls again revealed some more limiting features of the WebOS movement command. Move commands are all relative to the cursor position which is set to the center position on the TV or (0,0). So move(x,y) is really move(dx, dy). This was fine to work around once discovered, we just have to translate pixels to a coordinate system with (0,0) in the middle of the screen. We thought this would introduce little processing overhead, however getting this to work correctly was probably the hardest part of this project.&lt;&#x2F;p&gt;
&lt;p&gt;Having to recalculate constantly the relative move commands given the detected hand position from the camera feed forced us to keep an internal cursor state that recorded its absolute position and if it was dragging or not. The TV sometimes resets or recenters the cursor after a period of inactivity. So our code needed to also track the time of the previous movement. If less than about 5.5 seconds have passed, it assumes the cursor is still where we left it and sends a delta from the stored position. Otherwise we would need to treat the next movement as originating from (0,0).&lt;&#x2F;p&gt;
&lt;p&gt;This wasn&#x27;t a particularly elegant calibration system but it was enough to keep the prototype moving in roughly the right direction.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;moving-pixel-by-pixel&quot;&gt;moving pixel by pixel&lt;&#x2F;h3&gt;
&lt;p&gt;I like to make extragerated gestures across the camera, full of drama, in an effort to cut the rope when I&#x27;m playing. However, the movements turn out to be pretty chopy if you don&#x27;t break down the &lt;code&gt;moveTo()&lt;&#x2F;code&gt; commands to small, incremental movements.&lt;&#x2F;p&gt;
&lt;p&gt;To abstract away the relative movement commands we used a custom &lt;code&gt;moveArbDistance&lt;&#x2F;code&gt; function which takes any &lt;code&gt;x, y&lt;&#x2F;code&gt; and converts it into a sequence of one-pixel WebOS commands that will get your cursor to the correct final position.&lt;&#x2F;p&gt;
&lt;p&gt;Why 1 pixel at a time, you might ask?&lt;&#x2F;p&gt;
&lt;p&gt;Initially we assumed that &lt;code&gt;dx&lt;&#x2F;code&gt; and &lt;code&gt;dy&lt;&#x2F;code&gt; represented straightforward posiiton changes. Under that model, sending &lt;code&gt;dx: 3&lt;&#x2F;code&gt; should move the cursor three times as far as sending &lt;code&gt;dx: 1&lt;&#x2F;code&gt;. In reality the vaues behaved more like a velocity or movement-intensity vector than a discrete pixel offset. So increasing the step size did not produce a clean propotional movement. This was particuarly noticeable during dragging. Larger values made the cursor accelerate or jump across the screen instead of tracing the smooth path of the user&#x27;s hand.&lt;&#x2F;p&gt;
&lt;p&gt;Using stepsize of 1 ended up being the smoothest way to produce dragging without the cursor jumping all over the screen. So after this, pure horizontal becomes repeated dx steps and pure vertical becomes repeated dy steps. Any diagonal movement uses a basic slope ratio that alternates between horizontal and vertical steps for stable movement. We also had to account for jitter at this point, for example if the user sits at the center of the screen for some amount of time, we need to send tiny (1, 1) and (-1, -1) steps to keep it in place.&lt;&#x2F;p&gt;
&lt;p&gt;At this point if you&#x27;re still reading i think you might guess what became our biggest headache. decompressing single movements into tiny steps works well if you don&#x27;t move too far but this can easily send hundreds or thousands of move requests to the tvOS server, all of which take some time to process. Additionaly, if any requests failed the internal cursor state will drift from the real cursor position and the calcualted relative movements would be incorrect.&lt;&#x2F;p&gt;
&lt;p&gt;A serial dispatch queue helped us confirm that the messages would at least be sent in order. Without serialization, overlapping drag and movement commands could arrive in an exciting but incorrect sequence. If we had more time to improve the project, I would definetly spend more time designing the cursor state management or testing the WebOS commands to see if we can stablize long continuous movement without sending too many requests to get match to the movement smoothness of the Magic Remote.&lt;&#x2F;p&gt;
&lt;p&gt;At this point though, we do have a playable CTR tv version that&#x27;s controlled from my mac webcam.&lt;&#x2F;p&gt;
&lt;p&gt;But holding your hand in front of the laptop while looking sideways at a television is not a particularly natural way to play. We wanted the controls to feel more like touching a physical game board.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;design-2-table-top-gesture-control-and-tv-via-lidar&quot;&gt;Design 2: table top gesture control and tv via lidar&lt;&#x2F;h2&gt;
&lt;p&gt;Design 2 is anchored by the idea of playing CTR on our coffee table top. This would be similar enough to a giant trackpad for the TV or a futureistic terminal ipad console thing that seem to always appear in scifi spaceships.&lt;&#x2F;p&gt;
&lt;p&gt;A phone is mounted approximately 2 feet above the table, looking downward. The camera tracks the player&#x27;s fingertips while the depth sensor determines when those fingertips were close enough to the tabletop to count as a touch.&lt;&#x2F;p&gt;
&lt;p&gt;Moving your finger above the table would do nothing (hovering!). Touching the table would produce a click. Sliding your finger while touching the table woudl drag across the tv screen and cut a rope. We built a quick SwiftUI iOS app called WirelessCandy. Apple nicely packages lidar data access and some cool AR features into RealityKit and ARKit.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;teaching-my-phone-how-to-identify-my-table&quot;&gt;teaching my phone how to identify my table&lt;&#x2F;h3&gt;
&lt;p&gt;The app’s root &lt;code&gt;ContentView&lt;&#x2F;code&gt; is intentionally minimal. Most of the screen is occupied by a full-screen RealityKit &lt;code&gt;ARView&lt;&#x2F;code&gt;.
Apple has a nice plane detection feature that we can enable in the configuration:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;config.planeDetection&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; [.horizontal]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;config.frameSemantics&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; .sceneDepth&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Scene depth is the useful part. On supported devices in our case we used an iPhone 14 Pro, ARKit produces a depth map estimating the distance between the phone and each part of the camera image. We use that map to check if a detected fingertip is hovering over the table or physically touching it.&lt;&#x2F;p&gt;
&lt;p&gt;An &lt;code&gt;ARSessionDelegate&lt;&#x2F;code&gt; receives each new camera frame:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; session&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;_&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;font-style: italic;&quot;&gt; session&lt;&#x2F;span&gt;&lt;span&gt;: ARSession, &lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;didUpdate&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;font-style: italic;&quot;&gt; frame&lt;&#x2F;span&gt;&lt;span&gt;: ARFrame&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F; Hand tracking and collision detection happen here&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;finding-fingertips-with-vision&quot;&gt;finding fingertips with Vision&lt;&#x2F;h3&gt;
&lt;p&gt;For every AR frame, we run a &lt;code&gt;VNDetectHumanHandPoseRequest&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Vision returns recognized hand landmarks, including the tips of all five fingers:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.thumbTip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.indexTip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.middleTip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.ringTip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;.littleTip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We collect the recognized points for each visible hand and pass them into the depth-processing pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;Why all five fingertips instead of just the index finger?&lt;&#x2F;p&gt;
&lt;p&gt;Partially because it makes the interaction more flexible but mostly because detecting five potential collision points made us more certain of the user&#x27;s hand position. Depending on the angle of the phone and how far the user is from the camera the depth data may not be accurate. Initially, the collision detection was too sensitive and anchoring to a index-middle-ring finger bundle for collision detection worked much better for us.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;detecting-a-fingertip-table-collision&quot;&gt;detecting a fingertip-table collision&lt;&#x2F;h3&gt;
&lt;p&gt;To detect a fingertip-table collision we need to convert position data between several different coordinate spaces:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Convert the fingertip location into the depth map&#x27;s coordinate space.&lt;&#x2F;li&gt;
&lt;li&gt;Sample the depth value at that location.&lt;&#x2F;li&gt;
&lt;li&gt;Use the camera intrinsics to reconstruct the corresponding 3D camera-space point.&lt;&#x2F;li&gt;
&lt;li&gt;Transform the point from camera coordinates into AR world coordinates.&lt;&#x2F;li&gt;
&lt;li&gt;Transform it again into the table plane’s local coordinate system.&lt;&#x2F;li&gt;
&lt;li&gt;Measure its distance from the plane.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Camera intrinsics describe how 3D points are projected onto the camera image. To match the resolution of the depth map to the camera image, we scale the intrinsic matrix before reconstructing the point.&lt;&#x2F;p&gt;
&lt;p&gt;This way a depth pixel becomes a camera-space position:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;x = (pixelX - centerX) * depth &#x2F; focalX&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;y = (pixelY - centerY) * depth &#x2F; focalY&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;z = depth&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Luckily, ARKit gives us the camera transform needed to move that point into world space. We then apply the inverse table transform to express it relative to the detected table.&lt;&#x2F;p&gt;
&lt;p&gt;If the point is within approximately five centimeters of the table plane, we treat it as a potential collision:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;distance &lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 0.05&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The final version should use the absolute distance after confirming the plane’s sign convention. Otherwise, a point far below the table could theoretically be considered “close” simply because its signed distance is negative.&lt;&#x2F;p&gt;
&lt;p&gt;Once a fingertip is close enough, we project it onto the table and check whether the point falls within the table plane’s detected bounds.&lt;&#x2F;p&gt;
&lt;p&gt;This converts a real finger touching a real table into a two-dimensional point we can draw on the TV :)&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deciding-between-clicks-and-drags&quot;&gt;deciding between clicks and drags&lt;&#x2F;h3&gt;
&lt;p&gt;A single table collision should behave like a click but a collision that continues across multiple frames should behave like a drag.&lt;&#x2F;p&gt;
&lt;p&gt;The coordinator stores whether the previous frame contained a collision:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hadCollisionLastFrame&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The current frame becomes a drag when both conditions are true:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;swift&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;isDragging &lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    hadCollisionLastFrame &lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;    !&lt;&#x2F;span&gt;&lt;span&gt;collidedPointsInPlane.isEmpty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This gives us a simple interaction state machine:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;no contact → no contact: move nothing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;no contact → contact: click&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;contact → contact: drag&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;contact → no contact: release&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So tada! here&#x27;s the demo of design 1 because I somehow don&#x27;t have a video of the table version:&lt;&#x2F;p&gt;
&lt;video
    src=&quot;https:&amp;#x2F;&amp;#x2F;media.wencysuo.com&amp;#x2F;videos&amp;#x2F;design1-clip.mp4&quot;
    autoplay
    muted
    loop
    playsinline
&gt;&lt;&#x2F;video&gt;
&lt;h2 id=&quot;things-that-almost-worked&quot;&gt;things that almost worked&lt;&#x2F;h2&gt;
&lt;p&gt;I&#x27;ll list here a few things that we couldn&#x27;t debug or discovered too late to design around.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;depth-data-resolution&quot;&gt;depth data resolution&lt;&#x2F;h3&gt;
&lt;p&gt;Apple&#x27;s ARKit scene-depth map has a resolution of only 256x192 pixels. That gives us 49,152 depth samples spread across the phone&#x27;s entire field of view. This means that the object tracking of a small fingertip is only occupying a pretty small portion of the depth map. Apple explicitly describes the lidar feature as sa lower-resolution dpeth map chosen so ARKit can run algorithms in real time. So we end up in the awkward state where the camera can properly track the outline of a finger but the depth map doesn&#x27;t have enough spatial detail to accurately report back the depth position of that finger. As we would try to touch the table with our finger, the depth values around it would often snap to the table&#x27;s depth value. Instead of reporting one region for the finger and anohter slightly further region for the tabletop the depth mapwould flatten both into the same surface. This was pretty hard to design around since we didn&#x27;t have other depth data sources available.&lt;&#x2F;p&gt;
&lt;p&gt;Using multiple fingers made the interaction somewhat more stable because we had more samples to work with. But none of this changed the fact that the data was just too low resolution for design 2 :&#x2F;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;processing-every-frame-is-expensive&quot;&gt;processing every frame is expensive&lt;&#x2F;h3&gt;
&lt;p&gt;Because AR frame launches hand-pose detection and depth processing, if the vision pipeline takes longer than the camera&#x27;s frame interval, work can overlap and create a backlog. This accumulates over time and leads to unpredictable behavior of the cursor. Some finetuning of the fps was necessary but didn&#x27;t solve the issue at the root.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;table-classification-can-be-temperamental&quot;&gt;table classification can be temperamental&lt;&#x2F;h3&gt;
&lt;p&gt;We only accept planes classified by ARKit as &lt;code&gt;.table&lt;&#x2F;code&gt;.
If ARKit detects the surface but classifies it as unknown, the app sees a perfectly good table and chooses not to believe in it. I would have loved a more comprehensive user setup flow where one could select a plane as a playing service and calibrate it before playing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;conclusion&lt;&#x2F;h2&gt;
&lt;p&gt;The 3 prototypes of cut-the-rope-tv we built had varying levels of success. From our original rubric we were able to beat levels consistently, with a little user frustration when it came to design 2. But more importantly:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;We finally touched some fancy AR features and used the lidar sensors in our expensive iphones for something fun.&lt;&#x2F;li&gt;
&lt;li&gt;Swipe madly across the table to cut the rope.&lt;&#x2F;li&gt;
&lt;li&gt;Feed Om Nom.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The project was therefore a success.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>disguising my online traffic :)</title>
          <pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate>
          <author>wency suo</author>
          <link>https://wencysuo.com/blog/disguising-my-online-traffic/</link>
          <guid>https://wencysuo.com/blog/disguising-my-online-traffic/</guid>
          <description xml:base="https://wencysuo.com/blog/disguising-my-online-traffic/">&lt;h3 id=&quot;intro&quot;&gt;intro&lt;&#x2F;h3&gt;
&lt;p&gt;One of my biggest headaches while traveling in China has always been deciding which VPN to use. To watch YouTube or access anything outside the Great Firewall (GFW) I would have to route my traffic through a server outside the country.&lt;&#x2F;p&gt;
&lt;p&gt;In the past I’ve tried a few paid services like Surfshark, NordVPN (their ads are everywhere) but none have worked consistently for me. Granted, there’s a legitimate reason why VPN reliability may degrade over time. VPN traffic can tend to have distinctive patterns that then get detected and blocked, providers have to push fixes to restore access for those on the service periodically.&lt;&#x2F;p&gt;
&lt;p&gt;As with any VPN service you also have to trust a 3rd party vendor with all of your network traffic so they can forward your encrypted requests to your final destination. I’m sure there’s a lot of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;VPN&#x2F;comments&#x2F;s5thfm&#x2F;trustworthiness_of_a_vpn_provider_vs&#x2F;&quot;&gt;discourse online about whether or not to trust VPNs&#x2F;ISPs&lt;&#x2F;a&gt; and the like but for me climbing subscription fees and data privacy is really just a pretense for learning how to set up my own private network!&lt;&#x2F;p&gt;
&lt;p&gt;It can’t be that hard right?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-does-the-internet-work-again&quot;&gt;how does the internet work again&lt;&#x2F;h2&gt;
&lt;p&gt;Let’s start by reviewing some foundational networking concepts. Hypertext Transfer Protocol or HTTP is how data gets passed between machines to render websites. Assuming some familiarity with client&#x2F;server architectures (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.cloudflare.com&#x2F;learning&#x2F;ddos&#x2F;glossary&#x2F;hypertext-transfer-protocol-http&#x2F;&quot;&gt;cloudflare has some decent materials&lt;&#x2F;a&gt;), you can think of the GFW as a monitor at the ISP level.&lt;&#x2F;p&gt;
&lt;p&gt;The GFW is smarter than just checking blacklisted URLs, it uses an advanced network security probing method called Deep Packet Inspection (DPI) to analyze the packet sizes, timing patterns, handshake signatures of your traffic. Even when content is encrypted, traffic can look like VPN traffic, which is exactly how VPN connections get detected and throttled. Your destination IP and requested domain name is also usually public and unencrypted since TLS encryption only protects the content of the client server communication, so everything after a handshake is established not before.&lt;&#x2F;p&gt;
&lt;p&gt;So our goal here isn’t necessarily encryption but actually making our traffic indistinguishable from regular HTTPS browsing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;my-options&quot;&gt;my options&lt;&#x2F;h2&gt;
&lt;p&gt;A few projects have emerged to fill this specific niche over the years. The most popular and well established tool that I came across is Shadowsocks, which introduced its own encrypted SOCKS5 proxy protocol. The protocol unfortunately has a detectable fingerprint, so the GFW has gotten pretty good at identifying it over time. For this reason I chose to keep looking for other tools. Granted, I haven’t tested it out myself so perhaps for my use case this would’ve been fine.&lt;&#x2F;p&gt;
&lt;p&gt;V2Ray adds a layer of obfuscation on top to disguise proxy traffic as WebSocket, gRPC, or HTTP. This gets pretty close to a convincing disguise! However, from what I can tell, V2Ray doesn’t fully hold up against active probing, where the GFW sends it’s own requests to servers it suspects might actually be proxies. To handle this properly, V2Ray requires custom fallback configs to respond properly to requests from unauthorized sources.&lt;&#x2F;p&gt;
&lt;p&gt;Trojan builds on this idea of mimicking regular web servers by actually running a proper web server with a valid TLS certificate, making proxy requests indistinguishable from valid HTTP requests. In the case of active probing, when a request is sent to a trojan server that isn’t a valid trojan request then the server, which also runs Nginx, gets served a real webpage. This does mean that in order to use Trojan I need to actually register a domain which is another upfront cost that might influence which tool you would pick to set up your own proxy server. This wasn’t an issue for me so I chose this option for my server.&lt;&#x2F;p&gt;
&lt;p&gt;It’s worth mentioning that ShadowSocks and V2Ray has some pretty great documentation in comparison to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;p4gefau1t&#x2F;trojan-go&quot;&gt;Trojan&lt;&#x2F;a&gt;. V2Ray is part of a broader ecosystem of tools that help you build your own privacy network called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.v2ray.com&#x2F;en&#x2F;&quot;&gt;Project V&lt;&#x2F;a&gt; and the community investment in maintain documentation shows. Working with Trojan was a little more difficult since there are several unmaintained repos that you have to sift through to find the most recent fork, though the setup instructions have remained consistent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;deep-dive-into-trojan&quot;&gt;deep dive into trojan&lt;&#x2F;h2&gt;
&lt;p&gt;To understand what Trojan is doing, it helps to have a quick mental model of how a request travels from your browser to a server.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;adfa1d6f-371a-41f0-8ce8-c3bedf2af561_800x1064.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;When you type a URL, your machine resolves the domain to an IP address via DNS, opens a TCP connection to that IP, and if it’s HTTPS, negotiates a TLS handshake before any application data gets exchanged. TCP is the transport layer and handles reliable ordered delivery of packets. HTTP sits on top and defines the structure of the actual request and response. TLS sits between them and handles encryption.&lt;&#x2F;p&gt;
&lt;p&gt;What legitimizes this request is the TLS and to complete the TLS handshake we need a properly signed certificate for our web server. When your browser connects to a server, the server presents a certificate signed by a trusted Certificate Authority (CA). Your browser checks that the signature is valid and that the certificate matches the domain you’re visiting to establish a chian of trust from the browser’s built-in root CA down to the certificate on the server.&lt;&#x2F;p&gt;
&lt;p&gt;Trojan requires that you obtain a real certificate for a real domain. That means your server looks exactly like any other HTTPS server. Same handshake, same certificate structure, same everything. There’s nothing synthetic for DPI or active probing to uncover.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;server-setup&quot;&gt;server setup&lt;&#x2F;h2&gt;
&lt;p&gt;I really spent some time thinking about where and how I was going to go about setting up my own server since it seemed like an easy excuse to get my hands on a Raspberry PI. I genuinely did want to explore this option since it would also give me full hardware control but I was on a time crunch before the trip, so this will probably have to wait until I get back.&lt;&#x2F;p&gt;
&lt;p&gt;I also briefly considered repurposing my home router. Routers are essentially small Linux computers that also have network switches so flashing it with tools like OpenWrt would’ve let me protect every device on my home network automatically at the router level with no per-device config needed. The problem is with my ISP which locks down the hardware :( so OpenWrt isn’t really an option, again future project once I get back.&lt;&#x2F;p&gt;
&lt;p&gt;So the easiest and cheapest solution to all of my problems is to secure a free VPS from Oracle. Using the free tier does mean that my server might not have the best uptime since I’m at the mercy of whatever OCI is doing with their servers but honestly I’m not confident that I would have better reliability running a Raspberry Pi out of my apartment anyway.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;managing-proxy-traffic&quot;&gt;managing proxy traffic&lt;&#x2F;h2&gt;
&lt;p&gt;Setting up the proxy is just the first step, to actually use my VPS I also have to connect my devices to my server and manage my proxy requests. The brute force approach is to route everything through the proxy. For this to work you would need to route all of your traffic, all the way down at the system level through my server. This is admittedly wasteful but it does work. The better approach here is to define a set of rules, maybe based on domain, device etc. so that you selectively use your proxy.&lt;&#x2F;p&gt;
&lt;p&gt;This is what proxy managers like Clash handle. You define rules in a config file and Clash decides how to route each request. It’s also possible to define multiple proxies and set fallback behavior if one goes down, which means you’re not betting everything on a single server. Hurrah Redundancy!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;keeping-the-server-running-with-systemctl&quot;&gt;keeping the server running with systemctl&lt;&#x2F;h2&gt;
&lt;p&gt;Right now I’m starting the Trojan-Go process manually over SSH, which is fine for testing and deeply not fine for anything else. If the server reboots, the process dies and stays dead until I notice and SSH back in.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is turning it into a &lt;code&gt;systemd&lt;&#x2F;code&gt; service, which is Linux’s standard way of managing long-running processes. All I have to do is write a unit file that describes how to start the process, what user to run it as, and what to do on failure, then register it with &lt;code&gt;systemctl&lt;&#x2F;code&gt;. After that, &lt;code&gt;systemctl enable trojan-go&lt;&#x2F;code&gt; means it starts automatically on boot and &lt;code&gt;systemctl restart trojan-go&lt;&#x2F;code&gt; gives you a clean way to reload config changes. Logs go through &lt;code&gt;journald&lt;&#x2F;code&gt;, so &lt;code&gt;journalctl -u trojan-go&lt;&#x2F;code&gt; gives you a clean view of what the service is doing if anything goes wrong.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-do-from-here&quot;&gt;what to do from here&lt;&#x2F;h2&gt;
&lt;p&gt;I set things up in kind of a rush so that I can get a minimum viable product for my upcoming trip so there’s still a lot of work to be done. I can surf the open web but I still need to actually measure my performance and request latency. In another post I’ll explore profiling tools and optimizations i can make to my current system to achieve a smoother web browsing experience.&lt;&#x2F;p&gt;
&lt;p&gt;Until next time!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>FastJet but faster? Part II</title>
          <pubDate>Thu, 05 Mar 2026 00:00:00 +0000</pubDate>
          <author>wency suo</author>
          <link>https://wencysuo.com/blog/fastjet-but-faster-part-ii/</link>
          <guid>https://wencysuo.com/blog/fastjet-but-faster-part-ii/</guid>
          <description xml:base="https://wencysuo.com/blog/fastjet-but-faster-part-ii/">&lt;h3 id=&quot;tldr&quot;&gt;&lt;strong&gt;tldr&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;We never set out to make &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;WencySuo&#x2F;fastjet-rs&quot;&gt;fastjet-rs&lt;&#x2F;a&gt; faster than FastJet.&lt;&#x2F;p&gt;
&lt;p&gt;But after some elbow grease, SIMD, and some not-so-micro optimizations we’ve managed to make our Rust simple jet finding algorithms a few microseconds faster than the original C++ implementations.&lt;&#x2F;p&gt;
&lt;p&gt;If you haven’t already gone over the basics of this project in part 1, all you need to know is that fastjet is a particle physics package that performs nearest neighbor calculations to merge subatomic particles into jets. We are trying to rewrite the original C++ as Rust for the language’s memory safety advantages.&lt;&#x2F;p&gt;
&lt;p&gt;The benchmarking results from a comparison of the simple O(n²) clustering algorithm found that our Rust implementation is roughly equivalent in performance to a Julia implementation, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JuliaHEP&#x2F;JetReconstruction.jl&quot;&gt;JetReconstruction.jl&lt;&#x2F;a&gt;, but ultimately slower than the original C++ version by ~130 microseconds… but that’s not the case anymore!&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Jump to the end if you’re just interested in the results of our benchmarking.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;We’ll go over three major categories of code changes since our commit as of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;medium.com&#x2F;@jesus.fileto&#x2F;part-1-rewriting-c-code-to-rust-a-fastjet-experiment-f8bc2fb43bd4&quot;&gt;Part 1&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tiling&quot;&gt;&lt;strong&gt;tiling&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;d9b73303-f255-4e85-9bb2-446771177ffc_2262x1146.png&quot; alt=&quot;Thanks JetReconstruction.jl team for this wonderful graphic!&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Thanks JetReconstruction.jl team for this wonderful graphic!&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Fastjet and JetReconstruction.jl both implement a tiled clustering algorithm in addition to the simple n² clustering algorithm so we thought we should too. The key to this algorithm lies in the fact that clustering particles on an eta, phi coordinate system doesn’t change too much if we localize the clustering. In other words, instead of scanning and calculating distances between one jet and all other jets, we can define a tiled representation of our particles so that we only use the closest particles during distance calculations. Depending on how small we make our tiles, we essentially do the minimum amount of distance calculations for each jet at the expense of extra bookkeeping as we now need to store which jets are in which tiles and actively update it.&lt;&#x2F;p&gt;
&lt;p&gt;In practice, this means implementing a doubly linked list.&lt;&#x2F;p&gt;
&lt;p&gt;Let’s introduce some new structs &lt;code&gt;Tile&lt;&#x2F;code&gt; and &lt;code&gt;TiledJet&lt;&#x2F;code&gt; to represent all of our jet information relationally.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F; The neighbourhood of a tile is set up as follows:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;       LRR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;       LXR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;       LLR&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F; tiles contains XLLLLRRRR with pointers&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;                     |   \ RH_tiles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;    &#x2F;&#x2F;&#x2F;                     \ surrounding_tiles&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Credit&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span&gt; fastjet authors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Choosing a doubly linked list gives us instant access to adjacent jets in both directions, making it easy to rescan neighbors around a merge. Flattening each tile makes traversal linear, all we have to do is a little bit of math to map the grid index of each neighboring jet in a tile to its flattened index. Looks pretty easy to implement right?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-can-doubly-linked-lists-be-unsafe-in-rust&quot;&gt;&lt;strong&gt;why can doubly linked lists be unsafe in Rust?&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Linked lists are a bit of a pain to implement in Rust.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;struct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; val&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; prev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; next&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Box&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since each node needs to hold references to both the prev node and the next node, any interior node will always have multiple simultaneous owners. The moment you try to mutate a node in the middle of the list the Rust borrowchecker will throw a fit since you’ll need to have write permissions to both the node itself, the previous node, and the next node.&lt;&#x2F;p&gt;
&lt;p&gt;Since each node needs to hold references to both the prev node and the next node, any interior node will always have multiple simultaneous owners. The moment you try to mutate a node in the middle of the list the Rust borrowchecker will throw a fit since you’ll need to have write permissions to both the node itself, the previous node, and the next node.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-refcell-is-essential&quot;&gt;&lt;strong&gt;why RefCell is ‘essential’&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Recall &lt;code&gt;RefCell&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; from part 1, paired with &lt;code&gt;Rc&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; they are the Rust sanctioned safe method of allowing shared mutability. If you wrap nodes with &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;Node&amp;gt;&amp;gt;&lt;&#x2F;code&gt; ownership checks will now be performed at runtime instead of compile time. This is significant since the borrow checker, which runs at compile time is quite conservative so it ends up rejecting any borrowing patterns that can’t be proven safe statically. At runtime all of this changes since we can actually check if any simultaneous borrows conflict. Thus our doubly linked list node implementation looks more like this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;struct&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; val&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; u32&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; prev&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Rc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;RefCell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; next&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Option&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Rc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;RefCell&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;Node&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This will compile successfully, but the problem comes when actually accessing any of our jets inside the linked list. Each time you want to read a field within an object of type Node you will need to call &lt;code&gt;.borrow()&lt;&#x2F;code&gt; or &lt;code&gt;.borrow_mut()&lt;&#x2F;code&gt;. Each call to either of these functions is a runtime borrow check, adding a significant overhead anytime we work with jets in our algorithm.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;ok-but-what-if-we-don-t-want-to-always-have-runtime-borrow-checking&quot;&gt;&lt;strong&gt;ok but what if we don’t want to always have runtime borrow checking?&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;A better solution is actually pretty elegant here if you are willing to trade in storage overhead for better runtime performance. The astute reader might have already noticed that we don’t actually need to store references to each jet object, we can just keep a well updated table of jets and use &lt;code&gt;usize&lt;&#x2F;code&gt; indices instead.&lt;&#x2F;p&gt;
&lt;p&gt;Our main clustering logic is implemented inside ClusterSequence so we can give it sole ownership over this table. This is actually a common design pattern more commonly known as &lt;strong&gt;arena-style bookkeeping&lt;&#x2F;strong&gt;. Instead of using pointers or references, we can store all of our objects in contiguous blocks of memory that we access via indices. However, since our clustering algorithm requires we merge quite a few jets the upkeep of marking jet indices stale was nontrivial in our implementation. Many bugs emerged due to an erroneous index to an already removed or merged jet. All of this still ended up being worth it in our case since our runtime performance did improve after removing several repeated runtime borrow checks.&lt;&#x2F;p&gt;
&lt;p&gt;So linked lists really aren’t all that easy to pull off in Rust without giving some thought to ownership models in your data structures. The reason why implementing linked lists might feel easier in other languages is simply because the memory management is abstracted away behind complex garbage collection cleanup that has its own hidden costs. From a pedagogical perspective, tiling our clustering algorithm turned out to be a great way to learn Rust.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For a more thorough explanation of all things linked lists in Rust we highly recommend checking out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;rust-unofficial.github.io&#x2F;too-many-lists&#x2F;&quot;&gt;Learning Rust With Entirely Too Many Linked Lists&lt;&#x2F;a&gt; :)&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;the-fallacy-of-simd&quot;&gt;&lt;strong&gt;the fallacy of SIMD&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Implementing a SIMD version of the simple n² clustering algorithm was the second design change we made to increase our performance.&lt;&#x2F;p&gt;
&lt;p&gt;SIMD stands for (Single Instruction Multiple Data) where the CPU groups together multiple data elements as vectors (defined as LANES) and performs the same instruction to all this data simultaneously. In Fastjet, there are multiple scenarios where our data is highly parallel, finding the distance metric for all jets at once, initializing them, and updating the nearest neighbors each time we remove&#x2F;add a new jet. Some other parts of the code are inherently sequential, such as finding which jet to remove and performing our updating logic on it since it relies on finding the minimum distance jet and then acting upon it. In these cases the latter is more ripe for SIMD than the former. In these cases SIMD can optimistically produce performance gains of &lt;strong&gt;3–4x&lt;&#x2F;strong&gt;, however, the compiler is a fickle thing and it is possible that SIMD can cause performance penalties because nothing beats the compiler.&lt;&#x2F;p&gt;
&lt;p&gt;Normally, SIMD requires fine tuning to specific CPU architectures and calling archaically named functions after scouring through CPU documentation. Thankfully for our purposes we can test drive &lt;code&gt;std::simd&lt;&#x2F;code&gt;, a feature of Rust nightly which has long awaited a stable release, that aims to provide a “universal” SIMD implementation. The initial setup was pretty straightforward other than actually needing to learn SIMD!&lt;&#x2F;p&gt;
&lt;p&gt;When it came to retrofitting our code for SIMD we faced a number of difficulties.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;simd-memory-requirements&quot;&gt;&lt;strong&gt;SIMD memory requirements&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;e6400cc4-5550-4b39-bb3b-f85a99acd798_850x283.webp&quot; alt=&quot;Ref: https:&#x2F;&#x2F;www.researchgate.net&#x2F;figure&#x2F;A-comparison-of-the-Array-of-Structs-AoS-and-Struct-of-Arrays-SoA-memory-layouts_fig2_259132352&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Ref: https:&#x2F;&#x2F;www.researchgate.net&#x2F;figure&#x2F;A-comparison-of-the-Array-of-Structs-AoS-and-Struct-of-Arrays-SoA-memory-layouts_fig2_259132352&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;First off, SIMD requires all the data to be contiguously located in memory, meaning that we had to vectorize our current paradigm where each Jet struct contains elements containing its &lt;code&gt;pt&lt;&#x2F;code&gt;, &lt;code&gt;eta&lt;&#x2F;code&gt;, &lt;code&gt;phi&lt;&#x2F;code&gt;, &lt;code&gt;NN_index&lt;&#x2F;code&gt;, etc. This means that every time we call a SIMD function we need to loop over all vectors and initialize arrays holding just &lt;code&gt;phi&lt;&#x2F;code&gt;, &lt;code&gt;eta&lt;&#x2F;code&gt;, and so on. Initializing this data each SIMD call would increase the overhead so much that the performance gain is unjustified, thus we must shift to running our entire algorithm using structures of arrays instead of arrays of structs.&lt;&#x2F;p&gt;
&lt;p&gt;For this reason, we were drawn to Rusts’ arena (&lt;em&gt;recall the importance of arena-style bookkeeping from earlier!&lt;&#x2F;em&gt;) since it allows for fast memory allocation given that we know the amount of Jets that we account for at a time will never be larger than the initial length of input particles. This required a large overhaul of our code, but it was all going to be worth it right?&lt;&#x2F;p&gt;
&lt;h3 id=&quot;simd-and-primitives&quot;&gt;&lt;strong&gt;SIMD and primitives&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Another issue we ran into was the fact that SIMD arrays only take primitives but our &lt;code&gt;NN_index&lt;&#x2F;code&gt; was encoded as an &lt;code&gt;Option&lt;&#x2F;code&gt; (to highlight jets not having NN) could not be imputed. Then thanks to (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JuliaHEP&#x2F;JetReconstruction.jl&quot;&gt;JetReconstruction.jl&lt;&#x2F;a&gt;) we realized that we can actually modify &lt;code&gt;NN_index&lt;&#x2F;code&gt; to be set to a sentinel value of itself!&lt;&#x2F;p&gt;
&lt;p&gt;When creating new jets (whether merged or initialized) we already know its own index to access it, and most functions where we unwrap can instead just check its index. This mainly improves performance (removing unwrapping and wrapping) but also has the secondary effect of reducing data size, &lt;code&gt;f64&lt;&#x2F;code&gt; is 8 byte while an Option is double that (just to store the None value).&lt;&#x2F;p&gt;
&lt;p&gt;An interesting example to highlight was the following function since the &lt;code&gt;min(a, a) = a&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; _bj_dij&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; ProxyJet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;        *&lt;&#x2F;span&gt;&lt;span&gt; jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;kt2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;nn_jet_index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;                .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;map&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt;index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;|&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;kt2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;())&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;                 .&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;unwrap_or&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt;MAX&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;),&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;        )&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; _bj_dij&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; ProxyJet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;])&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; kt2_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;kt2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; kt2_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;nn_jet_index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;kt2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;        &#x2F;&#x2F; Bypasses the f64::min NaN checks, yielding pure hardware speed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; min_kt2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; if&lt;&#x2F;span&gt;&lt;span&gt; kt2_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; kt2_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; kt2_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; }&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; else&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;span&gt; kt2_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; min_kt2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now with all the setup out of the way we can examine a smaller SIMD function to highlight its technicalities. Since we showed the &lt;code&gt;_bj_dij&lt;&#x2F;code&gt; function earlier it seems right to highlight it parallelized.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;cfg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;feature &lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; “simd”&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;)]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; _dij_simd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;const&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        nn_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        di_j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;    ) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let mut&lt;&#x2F;span&gt;&lt;span&gt; j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;        &#x2F;&#x2F;  SIMD loop&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        while&lt;&#x2F;span&gt;&lt;span&gt; j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;lt;=&lt;&#x2F;span&gt;&lt;span&gt; n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt; &#x2F;&#x2F; note: [j..j + LANES] does not include j + LANES&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; kt2_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Simd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;from_slice&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; dist_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Simd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;from_slice&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; indices&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Simd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;from_slice&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;nn_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt; &#x2F;&#x2F; must store indices as u64 because compiler errors with other funcs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; indices_usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; indices&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;cast&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; kt2_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; Simd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;gather_or&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; indices_usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; kt2_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; kt2_min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; kt2_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;simd_min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;kt2_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; res&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; kt2_min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dist_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            res&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;copy_to_slice&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; di_j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; LANES&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;        &#x2F;&#x2F; scalar handling for remainder of elems aka tail&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;            let&lt;&#x2F;span&gt;&lt;span&gt; idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; nn_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; as usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            di_j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;min&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;kt2_arr&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;        }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Starting from the top, we gate SIMD use only when building with feature “simd” which prevents compiler errors from not being on nightly Rust as the code is basically “ignored” by the preprocessor.&lt;&#x2F;p&gt;
&lt;p&gt;Then when calling these SIMD functions we must pass a size parameter labeled LANES which as mentioned previously is how many elements we wish to pass to a SIMD instruction at a time. It is up to the user to check whether they handle the remainder of elements with the slower sequential instruction which we do in the final loop at the end. We can construct SIMD objects easily using &lt;code&gt;from_slice&lt;&#x2F;code&gt; and just passing a reference to our elements. Here the parallel action we must do is &lt;code&gt;gather_or&lt;&#x2F;code&gt; which retrieves all elements given indices. Funnily enough this is one of the slower operations since the bottleneck for these operations are not the CPU but instead the memory register which must fetch the vectors which are not contiguous and not guaranteed to be in the same cache line as the jets we have already taken in. However, we can see how we can use many other primary operators like inequalities, addition, multiplication in the same way and chain these operations together.&lt;&#x2F;p&gt;
&lt;p&gt;After overhauling all of our code we found that before optimizing our &lt;code&gt;simple_n2_cluster&lt;&#x2F;code&gt; algorithm that we achieved a &lt;strong&gt;45 percent performance improvement&lt;&#x2F;strong&gt;, and we were officially slightly faster than our C++ “competitor” 🥳.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;micro-optimizations-that-are-more-than-micro-in-practice&quot;&gt;&lt;strong&gt;micro optimizations that are more than micro in practice&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;However, it turns out that just a couple of changes in our previous naive implementation that &lt;em&gt;help&lt;&#x2F;em&gt; the compiler do it’s job also did wonders for performance.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;writing-clean-code-really-does-count&quot;&gt;&lt;strong&gt;writing clean code really does count!&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Part of the reason that Rust touts ‘C’ performance with large safety guarantees is through its aggressive use of optimizations to construct Zero-Cost Abstractions. This means more verbose Rust code to check indices in comparison to unsafe pointers, Vectors instead of arrays compile down to almost the same assembly instruction as C’s unsafe implementation.&lt;&#x2F;p&gt;
&lt;p&gt;This is important to note as more convoluted code has the effect of preventing the compiler from making such optimizations since user intent is unknown. This is why inlining most functions can result in faster performance since the compiler knows in what situations called functions are being used by the callee and can optimize accordingly. Take the following for loop:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;   &#x2F;&#x2F; some logic based on the value of nn_idx that only matters if jet_b != i&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;   &#x2F;&#x2F; if we find jet_b_idx is equal to i quit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;   if&lt;&#x2F;span&gt;&lt;span&gt; jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;      if&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;nn_jet_index&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;          jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;set_nn_jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;min_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;      }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;      continue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;   &#x2F;&#x2F; update min dist for jet i and jet_b&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Given this function the compiler does not know that the if loop is only checking for one value, and one that is predetermined before the for loop even starts, since it is in the middle of the function. If we just place it on top, the compiler will split the function into the following:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;jet_idx_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt; &#x2F;&#x2F; do logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;&#x2F;&#x2F; perform jet_b_idx == i case&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;for&lt;&#x2F;span&gt;&lt;span&gt; i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; jet_idx_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt; &#x2F;&#x2F; do logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A small change like moving the if loop to the beginning of the for loop had around a &lt;em&gt;6 percent improvement&lt;&#x2F;em&gt; since this was a part of the code that was called very frequently with depth O(n²).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;performance-impact-of-muts&quot;&gt;&lt;strong&gt;performance impact of &lt;code&gt;mut&lt;&#x2F;code&gt;s&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The next example highlighted to me why &lt;code&gt;mut&lt;&#x2F;code&gt;s can actually decrease performance if not used sparingly especially in conjunction with the next example. When a jet is merged, we must set the NN for the old jets which previously pointed to its constituents, since we don’t have to update the other jets to point to it (since we removed them) as a NN we only need to update that jet. Here is the code to highlight this.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;#&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;inline&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; bj_set_nn_nocross&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; ProxyJet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;  &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D20F39;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;  curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;  head_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;  tail_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;  jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;)  {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;   let mut&lt;&#x2F;span&gt;&lt;span&gt; nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D20F39;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;r2&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;   let mut&lt;&#x2F;span&gt;&lt;span&gt; nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;   for&lt;&#x2F;span&gt;&lt;span&gt; jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; head_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;       let&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;_bj_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;curr_jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;jets_ref&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;       if&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;           nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;       }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;   for&lt;&#x2F;span&gt;&lt;span&gt; jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; in&lt;&#x2F;span&gt;&lt;span&gt; curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;tail_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;       let&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;_bj_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;curr_jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;jets_ref&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;       if&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;            nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;       }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;   }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;set_nn_jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;]&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;set_nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The important things to watch are the function signature, and the last two lines. Since we need to modify &lt;code&gt;jets[curr_idx]&lt;&#x2F;code&gt; we pass the entire jets as mutable, we also can’t just pass &lt;code&gt;jets[curr_idx]&lt;&#x2F;code&gt; as mutable because then we would break Rust’s borrowing rules. Thus the only solution is to tell the callee function which element changes, and then modify that element. Here is how that following code looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span&gt;new_nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; new_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D20F39;&quot;&gt; self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;bj_set_nn_nocross&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; 0&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; jet_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; = &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span&gt;i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jet_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;set_nn_jet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;new_nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;jet_i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;set_nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;new_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;);&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; bj_set_nn_nocross&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; ProxyJet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #179299;&quot;&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D20F39;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        curr_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        head_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        tail_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;        jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;    )&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;font-style: italic;&quot;&gt;        &#x2F;&#x2F; logic&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;        (&lt;&#x2F;span&gt;&lt;span&gt;nn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt; nn_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This allows us to avoid having to pass the entire jets collection as mutable preventing the need for the compiler to reload the entire jets collection after passing into the function. From the compiler’s eyes the &lt;em&gt;&lt;strong&gt;entire&lt;&#x2F;strong&gt;&lt;&#x2F;em&gt; vector could change. With this declaration we know that only &lt;code&gt;jets[i]&lt;&#x2F;code&gt; changes. This change resulted in a &lt;em&gt;6 percent performance improvement&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;eliding-compiler-bound-checks&quot;&gt;&lt;strong&gt;eliding compiler bound checks&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This last example is arguably the most important. Each time a vector element is indexed, Rust checks whether the element is out of bounds (OOB) which in C leads to a host of UB. Given that our entire code relies on &lt;code&gt;NN_index&lt;&#x2F;code&gt; and indexing our jets almost every time we require them, this becomes a large part of our performance penalty.&lt;&#x2F;p&gt;
&lt;p&gt;Iterator efficiency in Rust can also be explained by the same phenomenon, since iterators already check elements exist, the compiler does not need to perform auxiliary bounds checking!&lt;&#x2F;p&gt;
&lt;p&gt;For us, we can’t use iterators here because we need to pass the entire vector slice to internal functions alongside a jet from that slice. This doesn’t mean we have no options left though. Instead we can tell the compiler to stop bounds checking indexes by proving that we will never use an index that is OOB via assertions.&lt;&#x2F;p&gt;
&lt;p&gt;In the code highlighted in the first optimization example, there is a lot of indexing. Almost everything is being indexed since we also have &lt;code&gt;muts&lt;&#x2F;code&gt; sprinkled in the for loop. Thus before this entire loop we index according to &lt;code&gt;i&lt;&#x2F;code&gt; and &lt;code&gt;jet_b_idx&lt;&#x2F;code&gt; thus we add the following code before the loop.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; = &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; bj_jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; dijs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; = &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; di_j&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;..&lt;&#x2F;span&gt;&lt;span&gt;tail&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;assert!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;jet_b_idx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; &amp;lt;&lt;&#x2F;span&gt;&lt;span&gt; jets&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;());&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since &lt;code&gt;i&lt;&#x2F;code&gt; loops up to &lt;code&gt;tail&lt;&#x2F;code&gt;, and &lt;code&gt;jet_b&lt;&#x2F;code&gt; is less than the entire length of the jet, OOB cannot occur and the compiler removes all bounds checking for our code in this for loop and in our NN search due to the changes mentioned in the second example. &lt;em&gt;&lt;strong&gt;This led to an astonishing 20% percent from just 3 lines of code!&lt;&#x2F;strong&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Other not so micro optimizations that accounted for another 20% of performance improvements:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Knowing our elements were never &lt;code&gt;NaN&lt;&#x2F;code&gt; or &lt;code&gt;INF&lt;&#x2F;code&gt; which require another check for &lt;code&gt;f64&lt;&#x2F;code&gt; when sorting or min&lt;&#x2F;li&gt;
&lt;li&gt;Removing lazy init since just calculating these numbers are cheaper than all of the dereferencing and heap retrieval&lt;&#x2F;li&gt;
&lt;li&gt;Using Fused &lt;code&gt;add_mul&lt;&#x2F;code&gt; in math calculations&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Lastly, we found that testing different performance improvements with Rust was fairly easy as we didn’t have to worry about UB caused by our code changes that would normally be present in C++ pointer handling.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-how-much-faster-are-we-actually&quot;&gt;&lt;strong&gt;so how much faster are we actually?&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;We used &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;criterion&quot;&gt;Criterion&lt;&#x2F;a&gt; and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;google&#x2F;benchmark&quot;&gt;Google Benchmarks&lt;&#x2F;a&gt; to benchmark our Rust functions &lt;code&gt;simple_n2_cluster&lt;&#x2F;code&gt;, &lt;code&gt;simple_n2_cluster_simd&lt;&#x2F;code&gt;, and &lt;code&gt;tiled_n2_cluster&lt;&#x2F;code&gt; against the original C++ versions of the code. We initially wanted to also compare our implementation against the Julia code as well but after discovering that we were actually faster than the C++ implementation we thought that deserved our full attention.&lt;&#x2F;p&gt;
&lt;p&gt;Previously, we just used the &lt;code&gt;single-event.dat&lt;&#x2F;code&gt; file located in one of the example data folders of Fastjet to benchmark all of our code. This was good for debugging since we could make sure that our final calculated jets were the same as Fastjet but for performance testing we wanted a more diverse dataset, with varying numbers of particles to process.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily for us, there’s a way to generate simulated events using a package called &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;pythia.org&#x2F;&quot;&gt;Pythia&lt;&#x2F;a&gt;. We spent some time generating a diverse dataset of events ranging from 50 to 1500+ particles per event and ran all 3 algorithms on it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;4e5a20fe-060e-400a-9e69-27f88898a6de_1400x1050.jpeg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Let’s start with a macro view of our performance. Tiled clustering in C++ is still the fastest out of all the algorithms, however our tiling implementation is a pretty close second. The simple C++ implementation is also the slowest out of the algorithms with our SIMD and simple clustering consistently beating C++.&lt;&#x2F;p&gt;
&lt;p&gt;To us, this is pretty surprising! We assumed that Rust would at best match C++ performance with the added benefit of memory safety (which was already a huge win from our perspective).&lt;&#x2F;p&gt;
&lt;p&gt;Zooming in to a particular section gives us some more information too. Our tiled clustering algorithm has a slightly more unstable performance but the same points as the C++ code. We posit that this could be related to memory allocation and cache misses during reading tile neighbors? However, this would require some benchmarking with Memory profiling packages like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;dhat&#x2F;latest&#x2F;dhat&#x2F;&quot;&gt;dhat&lt;&#x2F;a&gt; that would allow us to observe the exact heap allocations made during each run.&lt;&#x2F;p&gt;
&lt;p&gt;Another note here is that the benefits from a lot of the optimizations have a constant factor overhead (SIMD, tiling) so their benefit is really only felt at large input sizes. In fact, you can see that for smaller inputs, the simple n2 algorithm often outperforms our simd or tiled implementations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;a115a131-d58d-453c-8d45-bd439fbf0145_1400x1050.jpeg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;We hope by now it’s clear that paying attention to how code gets compiled can really pay off compared to manually hand tuning for performance. Who knew all of these seemingly trivial changes could actually turn out to be so powerful? Just as important though, is the fact that we could actually measure and test the impact of these changes with &lt;code&gt;Criterion&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Never forget to benchmark your code folks!&lt;&#x2F;p&gt;
&lt;p&gt;Until next time then :)&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Part 1: Rewriting C++ code to Rust (a FastJet experiment)</title>
          <pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
          <author>wency suo</author>
          <link>https://wencysuo.com/blog/part-1-rewriting-c-code-to-rust-a/</link>
          <guid>https://wencysuo.com/blog/part-1-rewriting-c-code-to-rust-a/</guid>
          <description xml:base="https://wencysuo.com/blog/part-1-rewriting-c-code-to-rust-a/">&lt;h3 id=&quot;what-we-are-doing&quot;&gt;&lt;strong&gt;what we are doing&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;TLDR: This blog documents what we learned rewriting a scoped down &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fastjet.fr&#x2F;&quot;&gt;fastjet 3.5.1&lt;&#x2F;a&gt; from C++ to Rust as our first real Rust crate, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;WencySuo&#x2F;fastjet-rs&quot;&gt;fastjet-rs&lt;&#x2F;a&gt;. We benchmark carefully hand-tuned work of physicists from 20 years ago and a more recent &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JuliaHEP&#x2F;JetReconstruction.jl&quot;&gt;Julia rewrite&lt;&#x2F;a&gt; against our Rust implementation. This project is largely educational, but we also wanted to produce a crate that could become a meaningful contribution to the Rust ecosystem.&lt;&#x2F;p&gt;
&lt;p&gt;One emerging area of Rust development is scientific high-performance computing. Workloads in computational biology or particle physics routinely handle petabytes of data and demand heavy optimization and parallelization to run. For this project, we took a particular interest in particle physics Rust crates since my collaborator started programming by analyzing lepton jets for CMS at the LHC (the world’s largest particle accelerator in CERN).&lt;&#x2F;p&gt;
&lt;p&gt;Scientific computing seemed like natural Rust territory, but some deep diving into &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;rust&#x2F;comments&#x2F;1jjf96y&#x2F;why_isnt_rust_used_more_for_scientific_computing&#x2F;&quot;&gt;reddit threads&lt;&#x2F;a&gt; revealed that adoption outside of ML and computational biology is still nascent. In fact, Fortran and C&#x2F;C++ still have a chokehold on HPC programs even when Rust is beginning to define modern programming elsewhere. The discussion boils down to three points:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Scientists aren’t programers and rust has a steep learning curve&lt;&#x2F;li&gt;
&lt;li&gt;C&#x2F;C++ code when written well is already performant&lt;&#x2F;li&gt;
&lt;li&gt;C&#x2F;C++ ecosystems have decades of niche library support that Rust can’t match yet&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Any memory bugs within applications like fastjet have been weeded out and corrected by the community over the past 20 years at the expense of many segfaults and memory leaks. When the community is already comfortable with a battle-tested Fortran or C program, fighting the borrow checker won’t always be worth it.&lt;&#x2F;p&gt;
&lt;p&gt;That said, particle physicists haven’t been standing still. In the past few years, Python has gained serious traction among scientists who value ease of use and development speed. Binding tools like pybind11, Cython, SWIG give user-friendly interfaces to the underlying C++ code. There is no doubt that this is a step in the right direction. But accumulating tech debt by layering abstractions over aging C++ code risks the same fate as Fortran, where supporting new features and maintenance becomes more similar to witchcraft than engineering. One new line of code breaks a thousand others without rhyme or reason.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;a661a5c1-1b94-4f16-8a75-eee01571c726_3456x1940.png&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Julia has emerged as one solution to this problem. Our hypothesis is that Rust could be another. Rust consistently matches or beats optimized C in benchmarks and its ownership model gives you fearless concurrency without a garbage collector. These are guarantees that Julia, for all its strengths, doesn’t offer at compile time.&lt;&#x2F;p&gt;
&lt;p&gt;Our goal is to test this hypothesis by benchmarking fastjet across C++, Rust, and Julia.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;some-basic-things-to-know-about-jets-and-the-problem-we-re-solving&quot;&gt;&lt;strong&gt;some basic things to know about jets and the problem we’re solving&lt;&#x2F;strong&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I had no physics background coming into this project, so we started with a crash course.&lt;&#x2F;p&gt;
&lt;iframe
  src=&quot;https:&#x2F;&#x2F;www.youtube-nocookie.com&#x2F;embed&#x2F;HVxBdMxgVX0&quot;
  title=&quot;Introduction to particle jets&quot;
  loading=&quot;lazy&quot;
  referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
  allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
  allowfullscreen
&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;Watching the two part series is not entirely necessary, although very entertaining. The production quality is peak 2006 internet and honestly that only makes them better.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;ok-so-with-that-what-are-jets&quot;&gt;Ok so with that, what are jets?&lt;&#x2F;h4&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;26839d7d-0f05-4409-9b76-64847922d526_1600x441.png&quot; alt=&quot;proton proton collision that produces quarks and gluons which get recorded by the detectors calorimeters as jets&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;proton proton collision that produces quarks and gluons which get recorded by the detectors calorimeters as jets&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;As &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;profmattstrassler.com&#x2F;articles-and-posts&#x2F;particle-physics-basics&#x2F;the-known-apparently-elementary-particles&#x2F;jets-the-manifestation-of-quarks-and-gluons&#x2F;&quot;&gt;Matt Strassler’s&lt;&#x2F;a&gt; explains, jets are sprays of high-energy subatomic particles (hence the name jets) composing the building blocks of the universe that we examine to understand the inner workings of protons, neutrons and other hadrons. The particles that produce jets are extremely short-lived and never reach our detectors, so jets are the only observable evidence of their existence.&lt;&#x2F;p&gt;
&lt;p&gt;Before we can analyze jets we need to reconstruct them from scattered event outputs collected by the calorimeters inside particle colliders. Historically, there are two methods for reconstructing particle collision events into jets: cone algorithms and hierarchical clustering. From the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;fastjet.fr&#x2F;repo&#x2F;fastjet-doc-3.5.1.pdf&quot;&gt;fastjet manual&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Cone algorithms put together particles within specific conical angular regions, notably such that the momentum sum of the particles contained in a given cone coincides with the cone axis (a “stable cone”).&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;What the manual calls sequential recombination algorithms can be understood more broadly as hierarchical clustering. Pairs of particles bound by their distance are clustered repeatedly until all particles are merged into stable jets. This statistical pattern can be found everywhere today. For our purposes, we can approximate jet reconstruction as an application of the nearest-neighbor problem common in CS.&lt;&#x2F;p&gt;
&lt;p&gt;Both methods rely on a tunable distance parameter delta R to define either the radius of a cone from the origin particle or the furthest distance between two points that we would collimate into the same jet. Set delta R too low and we miss lower energy descendant particles (normally called “children”) or too high and background noise from other particles are included in our final jets.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-did-we-do&quot;&gt;&lt;strong&gt;what did we do?&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Fastjet’s has three core classes that configure how jets are reconstructed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fastjet::PseudoJet&lt;&#x2F;code&gt; which defines jet objects as four-momenta vectors with additional values needed for different clustering algorithms&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;fastjet::JetDefinition&lt;&#x2F;code&gt; which contains the specifications for how jet cluster should be carried out&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;fastjet::ClusterSequence&lt;&#x2F;code&gt; which is the class that outputs the final jets after performing clustering as specified by JetDefinition on input PseudoJet objects&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It is important to us that we keep the functionality of fastjet-rs close to fastjet which meant reimplementing PseudoJet, JetDefinition, ClusterSequence code as best we could in a rust-y manner.&lt;&#x2F;p&gt;
&lt;p&gt;Heavy pointer manipulation and use of templates were the two characteristics of fastjet that made it difficult to translate to rust. You can read more about why raw pointer handling is an unsafe action in rust &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;book&#x2F;ch20-01-unsafe-rust.html&quot;&gt;here&lt;&#x2F;a&gt;, but the tldr is that dereferencing raw pointers breaks rust’s memory safety guarantees. Of course, the advantage of pointer manipulation when done correctly lies in the performance benefits. So the main challenge is really just being explicit with memory safety while maintaining performance parity with fastjet’s unencumbered C++ implementations.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;writing-getters-for-pseudojet&quot;&gt;Writing getters for PseudoJet&lt;&#x2F;h3&gt;
&lt;p&gt;Each PseudoJet object is initialized by a four-momentum vector describing a particle’s x, y, z planar coordinates and an energy value e. These values are then used to derive the information we will need for clustering: kt2, rap, and phi. These values are expensive to calculate, and some jets may never need to call them so Fastjet initializes them lazily with an internal function &lt;code&gt;_finish_init()&lt;&#x2F;code&gt;. &lt;code&gt;_finish_init()&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Our first design of some of the PseudoJets get methods made them mutable. This seems fine at a glance but quickly complicated our code quite dramatically. During the clustering step we often need to calculate the distances between two jets using the following function:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #4C4F69; background-color: #EFF1F5;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt; _bj_dist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt;J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; ProxyJet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #04A5E5;&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt;jet_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E64553;&quot;&gt; jet_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;font-style: italic;&quot;&gt; J&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; dphi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt; PI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;abs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #FE640B;&quot;&gt;PI&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #DF8E1D;&quot;&gt;f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;abs&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;jet_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;phi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;jet_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;phi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()));&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt;        let&lt;&#x2F;span&gt;&lt;span&gt; deta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #8839EF;&quot;&gt; f64&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; =&lt;&#x2F;span&gt;&lt;span&gt; jet_a&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;eta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;()&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;-&lt;&#x2F;span&gt;&lt;span&gt;jet_b&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #1E66F5;font-style: italic;&quot;&gt;eta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;();&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        dphi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; dphi&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; deta&lt;&#x2F;span&gt;&lt;span style=&quot;color: #179299;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span&gt; deta&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #7C7F93;&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Since we’re only accessing fields of &lt;code&gt;jet_a&lt;&#x2F;code&gt; and &lt;code&gt;jet_b&lt;&#x2F;code&gt;, theoretically we don’t actually need to pass in mutable references to &lt;code&gt;jet_a&lt;&#x2F;code&gt; or &lt;code&gt;jet_b&lt;&#x2F;code&gt;. However, since phi is a lazily initialized value with a mutable get method this aspect is passed onto the &lt;code&gt;_bj_dist&lt;&#x2F;code&gt; function. Furthermore, jets are stored in a vector so when calling &lt;code&gt;_bj_dist&lt;&#x2F;code&gt; we need to pass in two mutable references to elements of the same vector. This creates a major violation in rust since there cannot be multiple, simultaneous mutable references to the same object.&lt;&#x2F;p&gt;
&lt;p&gt;Even though we could guarantee that there would be no data races when running our code single threaded, the Rust compiler does not. Our temporary fix was to create a helper function which breaks our jet vector into two separate mutable components each time we had to call &lt;code&gt;_bj_dist&lt;&#x2F;code&gt;. You can probably see that this was a messy solution, not to mention a big performance hit to our naive implementation that we needed to fix.&lt;&#x2F;p&gt;
&lt;p&gt;Our second solution was to turn to unsafe Rust, since as stated earlier we guaranteed that we would never be calling &lt;code&gt;mut&lt;&#x2F;code&gt; on the same index, we could reduce the bloat of our function by calling the pointers directly. Unsafe code isn’t always bad, Rust just forces you to be deliberate about the abstractions that often lead to nasty memory bugs.&lt;&#x2F;p&gt;
&lt;p&gt;Luckily, there is a much better way to solve this problem in Rust with a concept called interior mutability, a design pattern that allows you to mutate data even when there are immutable references to that data. I told you earlier that this is explicitly disallowed by rust, nothing has changed. Implementations of interior mutability like &lt;code&gt;OnceCell&lt;&#x2F;code&gt; are just safe APIs for unsafe code, making it easier for us to directly use helpful abstractions for unsafe code.&lt;&#x2F;p&gt;
&lt;p&gt;For lazy initialization specifically, the standard rust library has a type called &lt;code&gt;OnceCell&lt;&#x2F;code&gt; which we can use to obtain shared &lt;strong&gt;immutable references&lt;&#x2F;strong&gt; to uninitialized data that becomes initialized once written. Getting rid of our vector split workarounds improves the performance, readability of our code, and prepares us for properly implementing concurrency.&lt;&#x2F;p&gt;
&lt;p&gt;In fact, the authors of fastjet ran into this very problem with these getter methods when multithreading their code. There are many preprocessing macros modifying how these getters work for the exact reason that they can’t prevent data races here. This is where the rust compiler’s foresight really pays off, by designing early with restrictive borrowing and ownership rules from the start we get “fearless concurrency” for free!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;benchmarking&quot;&gt;&lt;strong&gt;benchmarking&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;To compare our implementations, we timed and generated flamegraphs for the C++, Julia, and Rust versions of the simple n² algorithm using the basic clustering strategy.&lt;&#x2F;p&gt;
&lt;p&gt;Setting up profiling turned out to be more annoying than expected, especially on mac. We found that sample worked best for our purposes, but getting useful flamegraphs required some care. We had to reduce inlining (&lt;code&gt;-g&lt;&#x2F;code&gt; for C++, &lt;code&gt;#[inline(never)]&lt;&#x2F;code&gt; in Rust) and keep frame pointers intact so the profiler could actually record meaningful stack frames instead of collapsing everything into “the clustering function”.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;96b6964c-914f-40ba-ae59-c2b5eb2285e4_2048x396.png&quot; alt=&quot;C++ Flamegraph&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;C++ Flamegraph&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;6dccb2fa-a403-410c-8962-fb6644122bb6_1600x549.png&quot; alt=&quot;Rust Flamegraph&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Rust Flamegraph&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;In our Rust flamegraph, we found that roughly half of the runtime was spent actual clustering while most of the remainder went to parsing event files. Some nice improvements can be seen when compared to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;share.firefox.dev&#x2F;4qTDrfY&quot;&gt;earlier flamegraphs&lt;&#x2F;a&gt;, calculations for &lt;code&gt;bj_dist&lt;&#x2F;code&gt; (which required calculating &lt;code&gt;eta&lt;&#x2F;code&gt; and &lt;code&gt;phi&lt;&#x2F;code&gt;) no longer take half the time that clustering currently takes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;226206cc-fdb3-4fad-ba01-b67a3b7ece2b_1600x773.png&quot; alt=&quot;Julia Flamegraph&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Julia Flamegraph&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is where things got interesting. We initially thought Julia was running significantly faster than our Rust implementation, but through observing the flamegraph it turned out the Julia benchmarks were only measuring jet creation not event parsing or output. Once we accounted for that, the playing field leveled out considerably:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;substack-post-media.s3.amazonaws.com&#x2F;public&#x2F;images&#x2F;babbd4dd-80f8-47ea-9cd4-77050a6dd4b3_309x400.jpeg&quot; alt=&quot;&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rust:&lt;&#x2F;strong&gt; 498.003 µs ± 0.687 µs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Julia:&lt;&#x2F;strong&gt; 487.902 µs ± 22.156 µs&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;C++:&lt;&#x2F;strong&gt; 365.222 µs ± 0.901 µs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Julia and Rust land in essentially the same ballpark, with C++ still comfortably ahead. Currently, Julia has similar performance to our Rust implementation because of SIMD (Single Instruction Multiple Data), which tells the compiler to vectorize loops in assembly. Rust currently only supports SIMD for floats in the &lt;code&gt;nightly&lt;&#x2F;code&gt; crate, so for this first iteration we wanted to avoid its use, with it we most likely will pull fairly ahead of Julia.&lt;&#x2F;p&gt;
&lt;p&gt;However since Julia is a runtime language, the first iteration of the code runs extremely slow, otherwise known as the ‘Time to First Plot” problem. Lengthened compile time in Rust is a deliberate sacrifice for faster runtime programs and guaranteed memory safety. Compared to C++, Rust loses on the compile time game, taking around 30 seconds to fully compile versus C++’s three seconds. Rust is thus a happy middle ground between Julia and C++ that balances speed and memory management.&lt;&#x2F;p&gt;
&lt;p&gt;So why is our Rust implementation slower than C++?&lt;&#x2F;p&gt;
&lt;p&gt;A few reasons most likely. Because we use indices to track nearest neighbors, we need an extra pass through all active particles after each merge to make sure NN pointers stay valid. That introduces an additional constant-factor loop inside the O(n²) main loop. One potential fix is wrapping each PseudoJet in an Rc, which would allow multiple references to mutable jet instances and eliminate the index bookkeeping. Although, there is a possibility that the reference counting overhead could end up costing more than the loop it replaces. We’re also paying a cost by passing entire vectors by reference into one of our distance functions, and we’re investigating whether leveraging const more aggressively in our Rust code could close the gap, since the C++ implementation makes heavy use of inline const templating.&lt;&#x2F;p&gt;
&lt;p&gt;That said, profiling and iterating paid off. With the help of the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nnethercote.github.io&#x2F;perf-book&#x2F;&quot;&gt;Rust Performance Book&lt;&#x2F;a&gt; and some build configuration changes, we brought our runtime down from 1.4 milliseconds to 650 microseconds which is roughly a 2x improvement from relatively simple optimizations (lazy initialization with OnceCell and updating NN indices for only active particles rather than the full list.)!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;&lt;strong&gt;next steps&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Fastjet-rs is very much an ongoing project and there are many other features that still need to be implemented. To start, we want to increase the number of jet finding algorithms we support as we currently only have Anti-kt. Another major feature we are missing is an O(nlogn) Voronio clustering algorithm that leverages a fascinating geometry based optimization trick.&lt;&#x2F;p&gt;
&lt;p&gt;So if you’re interested in seeing what we learn from any of these small projects stay tuned for the next part :)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;references&quot;&gt;&lt;strong&gt;references&lt;&#x2F;strong&gt;&lt;&#x2F;h2&gt;
&lt;ol&gt;
&lt;li&gt;https:&#x2F;&#x2F;fastjet.fr&#x2F;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;JuliaHEP&#x2F;JetReconstruction.jl?tab=readme-ov-file&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;JuliaHEP&#x2F;JetReconstruction.jl?tab=readme-ov-file&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;discord.com&#x2F;blog&#x2F;why-discord-is-switching-from-go-to-rust&quot;&gt;https:&#x2F;&#x2F;discord.com&#x2F;blog&#x2F;why-discord-is-switching-from-go-to-rust&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.figma.com&#x2F;blog&#x2F;rust-in-production-at-figma&#x2F;&quot;&gt;https:&#x2F;&#x2F;www.figma.com&#x2F;blog&#x2F;rust-in-production-at-figma&#x2F;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;blogs&#x2F;opensource&#x2F;why-aws-loves-rust-and-how-wed-like-to-help&#x2F;&quot;&gt;https:&#x2F;&#x2F;aws.amazon.com&#x2F;blogs&#x2F;opensource&#x2F;why-aws-loves-rust-and-how-wed-like-to-help&#x2F;&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</description>
      </item>
    </channel>
</rss>
