RF-DETR C++ is a production-grade TensorRT inference engine for RF-DETR
https://github.com/infracv/rf-detr-cpp
https://redd.it/1up1p41
@r_cpp
https://github.com/infracv/rf-detr-cpp
https://redd.it/1up1p41
@r_cpp
GitHub
GitHub - infracv/rf-detr-cpp: Production-ready C++/TensorRT inference engine for RF-DETR. Object detection and instance segmentation…
Production-ready C++/TensorRT inference engine for RF-DETR. Object detection and instance segmentation with FP32/FP16/INT8 support. Optimized for NVIDIA GPUs, Jetson (Orin, AGX Thor). - infracv/rf-...
New C++ Conference Videos Released This Month - July 2026
C++Online
2026-06-22 - 2026-06-28
Why std::vector Can't Save You (And What to Use Next) - Kevin Carpenter - [https://youtu.be/78fYPix0mN4](https://youtu.be/78fYPix0mN4)
Modern C++ for Embedded Systems - From Fundamentals To Real-Time Solutions - Rutvij Karkhanis - https://youtu.be/XxeqHRDhHkU
ADC
2026-06-22 - 2026-06-28
Beyond iLok: Advanced Code Protection and Cryptography for the Next Generation - Protecting the Next Generation of Applications, Plug-ins, and AI Models - Neal Michie, Ryan Wardell & Bob Brown - [https://youtu.be/dbbK\_ry2cgo](https://youtu.be/dbbK_ry2cgo)
Database Synchronisation for Audio Plugins, Part Two - Here's One I Made Earlier - Adam Wilson - https://youtu.be/wJCy2G969ro
Perfect Oscillators in Less Than One Clock Cycle - Angus Hewlett - [https://youtu.be/Ssq0a-YdamM](https://youtu.be/Ssq0a-YdamM)
Driving Chaos - Virtual Analog Modelling of a Chaotic Circuit with Wave Digital Filters - Francisco Bernardo - https://youtu.be/PnEZNqyKlIw
There is also a teaser trailer for a new documentary on Boost https://www.youtube.com/watch?v=87jvuDbnwqQ which will have its first showing at CppCon this year
https://redd.it/1up11y7
@r_cpp
C++Online
2026-06-22 - 2026-06-28
Why std::vector Can't Save You (And What to Use Next) - Kevin Carpenter - [https://youtu.be/78fYPix0mN4](https://youtu.be/78fYPix0mN4)
Modern C++ for Embedded Systems - From Fundamentals To Real-Time Solutions - Rutvij Karkhanis - https://youtu.be/XxeqHRDhHkU
ADC
2026-06-22 - 2026-06-28
Beyond iLok: Advanced Code Protection and Cryptography for the Next Generation - Protecting the Next Generation of Applications, Plug-ins, and AI Models - Neal Michie, Ryan Wardell & Bob Brown - [https://youtu.be/dbbK\_ry2cgo](https://youtu.be/dbbK_ry2cgo)
Database Synchronisation for Audio Plugins, Part Two - Here's One I Made Earlier - Adam Wilson - https://youtu.be/wJCy2G969ro
Perfect Oscillators in Less Than One Clock Cycle - Angus Hewlett - [https://youtu.be/Ssq0a-YdamM](https://youtu.be/Ssq0a-YdamM)
Driving Chaos - Virtual Analog Modelling of a Chaotic Circuit with Wave Digital Filters - Francisco Bernardo - https://youtu.be/PnEZNqyKlIw
There is also a teaser trailer for a new documentary on Boost https://www.youtube.com/watch?v=87jvuDbnwqQ which will have its first showing at CppCon this year
https://redd.it/1up11y7
@r_cpp
YouTube
Why std::vector Can't Save You (And What to Use Next)
Online Workshops + Training Sessions Available through April-June 2026 from only £150 ($200)
14 Sessions Available on a range of topics. Student Rates available from only £45/$60! US and EU friendly session times.
https://cpponline.uk/
---
O(1) or O(no-no…
14 Sessions Available on a range of topics. Student Rates available from only £45/$60! US and EU friendly session times.
https://cpponline.uk/
---
O(1) or O(no-no…
What should I expect in C++ interviews for chip/semiconductor companies, especially HPC/GPU roles?
Hey everyone, I’m looking for some advice from people who have interviewed for C++ roles, especially at chip/semiconductor companies like NVIDIA, AMD, Intel, Apple, etc.
I graduated in Dec 2025 and I’m mainly targeting C++ roles in areas related to HPC, GPU programming, systems, compilers, performance, or low-level libraries. I’d say I’m not a beginner in C++, but also not an expert yet. I’m currently improving by contributing to open-source C++ codebases and studying more deeply.
I’ve been finding it hard to even get interviews in the C++ domain, so I’m trying to understand what I should focus on more seriously.
From what I understand, chip/semiconductor companies may not interview exactly like typical LeetCode-heavy software companies. I’m assuming there may be more focus on C++, systems knowledge, memory, concurrency, debugging, performance, templates, or real-world code understanding — but I’m not fully sure.
For people who have gone through these interviews, what do C++ interviews for these kinds of companies usually look like?
Do they focus more on:
Modern C++ language details?
Templates / metaprogramming?
STL and standard library internals?
Data structures and algorithms / LeetCode?
OS, memory, concurrency, multithreading?
CUDA / GPU / HPC concepts?
Debugging and performance optimization?
Real-world code review style questions?
Also, if my specific interest is HPC / GPU / performance-oriented C++, what should I expect in interviews and what should I prioritize learning?
Any guidance, interview experiences, or roadmap suggestions would be really appreciated.
https://redd.it/1uphbsy
@r_cpp
Hey everyone, I’m looking for some advice from people who have interviewed for C++ roles, especially at chip/semiconductor companies like NVIDIA, AMD, Intel, Apple, etc.
I graduated in Dec 2025 and I’m mainly targeting C++ roles in areas related to HPC, GPU programming, systems, compilers, performance, or low-level libraries. I’d say I’m not a beginner in C++, but also not an expert yet. I’m currently improving by contributing to open-source C++ codebases and studying more deeply.
I’ve been finding it hard to even get interviews in the C++ domain, so I’m trying to understand what I should focus on more seriously.
From what I understand, chip/semiconductor companies may not interview exactly like typical LeetCode-heavy software companies. I’m assuming there may be more focus on C++, systems knowledge, memory, concurrency, debugging, performance, templates, or real-world code understanding — but I’m not fully sure.
For people who have gone through these interviews, what do C++ interviews for these kinds of companies usually look like?
Do they focus more on:
Modern C++ language details?
Templates / metaprogramming?
STL and standard library internals?
Data structures and algorithms / LeetCode?
OS, memory, concurrency, multithreading?
CUDA / GPU / HPC concepts?
Debugging and performance optimization?
Real-world code review style questions?
Also, if my specific interest is HPC / GPU / performance-oriented C++, what should I expect in interviews and what should I prioritize learning?
Any guidance, interview experiences, or roadmap suggestions would be really appreciated.
https://redd.it/1uphbsy
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
Implementing a C++ runtime library to enforce heap memory safety Research / Source-Available
Hi everyone,
With the recent ISO committee and compiler-level debates surrounding memory safety in C++, I have been researching some alternative, library-based ways to enforce deterministic heap-bound protection without having to modify the compiler frontend or language specification itself.
I’ve been working on a runtime library called Safe--Cpp, which specifically focuses on ensuring that heap allocations achieve the same level of compile-time safety as Rust, but managed purely through language runtime mechanics rather than compile-time static borrow checking or ownership checking. I want to emphasize that this research strictly focuses on a custom safe context to prevent 4 types of memory errors: Double Deletion, Access Violation, Buffer Overflow and Memory Leaks.
# Core Architectural Concepts Under Investigation:
1. Strict Heap Boundary Enforcement: Tracking the initialization and destruction boundaries of objects explicitly allocated on the heap, ensuring references cannot outlive their allocation scope.
2. Explicit Lifetime Invalidation: The runtime library tracks every heap-allocated instance of types that inherit from
3. No External Tooling Dependencies: The runtime mechanics are implemented strictly using platform capabilities and the standard C++ language.
# Seeking Feedback on the Implementation
I have opened up the complete source and headers of this implementation under a Source-Available model (PolyForm Noncommercial License) so that other system engineers and language researchers can audit the exact low-level mechanics.
👉 GitHub Repository: https://www.github.com/ducna-vbee/Safe--Cpp
Rather than discussing the philosophical pros and cons of memory models, I am looking for concrete technical review, potential bug identification, and feature suggestions to help push the boundaries of what standard C++ can do here.
Specifically, I would love your insights on:
1. Bugs & Safety Violations: Are there subtle ways to bypass the context boundaries or trick the `SafeContextBase` lifecycle tracking using advanced modern C++ features (e.g., specific combinations of move semantics, perfect forwarding, or custom allocators) that could still lead to a leak or access violation?
2. Performance Improvements & Language Limits: The engine bypasses OS kernel allocations by providing instance recycling and repurposing mechanics. How can this layout be optimized further to reduce CPU cache misses or minimize the tracking metadata overhead? Which aspects of memory allocation can be made safe under the safe context? Can the memory stack also be as safe as the memory heap, like in Rust, without the borrow checker?
3. API & New Feature Suggestions: What missing features or API improvements would make this runtime context significantly easier to integrate into existing real-world standard C++ codebases without degrading performance?
Please feel free to check out the source, run your own benchmarks, and leave your feedback or file an issue directly on the repository!
https://redd.it/1upj6at
@r_cpp
Hi everyone,
With the recent ISO committee and compiler-level debates surrounding memory safety in C++, I have been researching some alternative, library-based ways to enforce deterministic heap-bound protection without having to modify the compiler frontend or language specification itself.
I’ve been working on a runtime library called Safe--Cpp, which specifically focuses on ensuring that heap allocations achieve the same level of compile-time safety as Rust, but managed purely through language runtime mechanics rather than compile-time static borrow checking or ownership checking. I want to emphasize that this research strictly focuses on a custom safe context to prevent 4 types of memory errors: Double Deletion, Access Violation, Buffer Overflow and Memory Leaks.
# Core Architectural Concepts Under Investigation:
1. Strict Heap Boundary Enforcement: Tracking the initialization and destruction boundaries of objects explicitly allocated on the heap, ensuring references cannot outlive their allocation scope.
2. Explicit Lifetime Invalidation: The runtime library tracks every heap-allocated instance of types that inherit from
Safe::SafeContextBase and offers recycling/repurpose mechanisms to gain performance instead of relying on deallocations which require accessing the operating system kernels to perform system calls, invalidates the need of reference counting like in `std::shared_ptr`.3. No External Tooling Dependencies: The runtime mechanics are implemented strictly using platform capabilities and the standard C++ language.
# Seeking Feedback on the Implementation
I have opened up the complete source and headers of this implementation under a Source-Available model (PolyForm Noncommercial License) so that other system engineers and language researchers can audit the exact low-level mechanics.
👉 GitHub Repository: https://www.github.com/ducna-vbee/Safe--Cpp
Rather than discussing the philosophical pros and cons of memory models, I am looking for concrete technical review, potential bug identification, and feature suggestions to help push the boundaries of what standard C++ can do here.
Specifically, I would love your insights on:
1. Bugs & Safety Violations: Are there subtle ways to bypass the context boundaries or trick the `SafeContextBase` lifecycle tracking using advanced modern C++ features (e.g., specific combinations of move semantics, perfect forwarding, or custom allocators) that could still lead to a leak or access violation?
2. Performance Improvements & Language Limits: The engine bypasses OS kernel allocations by providing instance recycling and repurposing mechanics. How can this layout be optimized further to reduce CPU cache misses or minimize the tracking metadata overhead? Which aspects of memory allocation can be made safe under the safe context? Can the memory stack also be as safe as the memory heap, like in Rust, without the borrow checker?
3. API & New Feature Suggestions: What missing features or API improvements would make this runtime context significantly easier to integrate into existing real-world standard C++ codebases without degrading performance?
Please feel free to check out the source, run your own benchmarks, and leave your feedback or file an issue directly on the repository!
https://redd.it/1upj6at
@r_cpp
GitHub
GitHub - ducna-vbee/Safe--Cpp: A runtime library that defines a safe context for C++, no reference counting involved.
A runtime library that defines a safe context for C++, no reference counting involved. - ducna-vbee/Safe--Cpp
MSVC optimization
I am learning reverse engineering on Windows applications such as Adobe, Foxit PDF, and Steam, and I noticed that I waste a very large amount of time trying to understand something that I should not focus on.
I started noticing strange and confusing patterns in the assembly and the C code generated by IDA, and when I try to understand some functions, I feel that the function has no meaning.
When I searched, I found that this topic is related to the compiler and compiler optimizations. However, I could not find many articles or discussions about the compiler topic in reverse engineering.
So I started experimenting and trying, but every time I fail and cannot reach a solution or understanding.
Apart from the fact that reverse engineering a C++ program is already a difficult task.
If there is someone who has faced the same problem and found a solution, I would like to know. It is not a problem itself; it is a pattern or a way of thinking used by the compiler. I need to understand how the compiler generates these patterns.
I want someone to suggest books, articles, courses, or anything that can help me understand the MSVC compiler, how it generates patterns, and how to understand the behavior and logic of a function after compiler optimization.
I hope I explained my question correctly.
https://redd.it/1upiduk
@r_cpp
I am learning reverse engineering on Windows applications such as Adobe, Foxit PDF, and Steam, and I noticed that I waste a very large amount of time trying to understand something that I should not focus on.
I started noticing strange and confusing patterns in the assembly and the C code generated by IDA, and when I try to understand some functions, I feel that the function has no meaning.
When I searched, I found that this topic is related to the compiler and compiler optimizations. However, I could not find many articles or discussions about the compiler topic in reverse engineering.
So I started experimenting and trying, but every time I fail and cannot reach a solution or understanding.
Apart from the fact that reverse engineering a C++ program is already a difficult task.
If there is someone who has faced the same problem and found a solution, I would like to know. It is not a problem itself; it is a pattern or a way of thinking used by the compiler. I need to understand how the compiler generates these patterns.
I want someone to suggest books, articles, courses, or anything that can help me understand the MSVC compiler, how it generates patterns, and how to understand the behavior and logic of a function after compiler optimization.
I hope I explained my question correctly.
https://redd.it/1upiduk
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
[LLVM libc++] A size-based representation for vector in the unstable ABI
https://github.com/llvm/llvm-project/commit/bd338806e423
https://redd.it/1upo87m
@r_cpp
https://github.com/llvm/llvm-project/commit/bd338806e423
https://redd.it/1upo87m
@r_cpp
GitHub
[libc++] Add a size-based representation for `vector` in the unstable… · llvm/llvm-project@bd33880
… ABI (#155330)
This commit adds an alternative representation for `std::vector` in a similar
manner to `__split_buffer` in #139632. This alternative representation was
measured to provide signifi...
This commit adds an alternative representation for `std::vector` in a similar
manner to `__split_buffer` in #139632. This alternative representation was
measured to provide signifi...
I built a lightweight native C++ IDE for Linux that doesn't require CMake for small projects
Over the last few months I've been working on a small personal project called FeatherForge. The original goal was simple: I wanted an IDE where I could open a folder, write some C++, press F5, and run my code without spending time configuring build systems for small projects.
FeatherForge is written entirely in C++ using ImGui/OpenGL, and currently includes:
Automatic project detection
Auto-discovery of headers in
Automatic library linking from `lib/` (prefers static libraries when available)
Compile target selection for multi-file projects
Background Clang diagnostics
Clickable compiler errors that jump to the correct line
Basic Git integration
Native Linux UI (no Electron)
It's still an early v1.0 release, so there are plenty of things missing (LSP, integrated debugger, project-wide search, etc.), but it's already usable for small C++ projects like Raylib experiments.
I'd really appreciate any feedback on the project, especially from people who regularly write C++.
GitHub:
https://github.com/KinetiNode/Featherforge/blob/main/README.md
https://redd.it/1uprqpy
@r_cpp
Over the last few months I've been working on a small personal project called FeatherForge. The original goal was simple: I wanted an IDE where I could open a folder, write some C++, press F5, and run my code without spending time configuring build systems for small projects.
FeatherForge is written entirely in C++ using ImGui/OpenGL, and currently includes:
Automatic project detection
Auto-discovery of headers in
include/Automatic library linking from `lib/` (prefers static libraries when available)
Compile target selection for multi-file projects
Background Clang diagnostics
Clickable compiler errors that jump to the correct line
Basic Git integration
Native Linux UI (no Electron)
It's still an early v1.0 release, so there are plenty of things missing (LSP, integrated debugger, project-wide search, etc.), but it's already usable for small C++ projects like Raylib experiments.
I'd really appreciate any feedback on the project, especially from people who regularly write C++.
GitHub:
https://github.com/KinetiNode/Featherforge/blob/main/README.md
https://redd.it/1uprqpy
@r_cpp
GitHub
Featherforge/README.md at main · KinetiNode/Featherforge
Hyper-lightweight, zero-config C++ IDE built with ImGui & OpenGL. Automatically links libraries without CMake, parses GCC errors, and runs async linting. - KinetiNode/Featherforge
Awesome AI for C/C+++: A curated list of AI, LLM, and agent tools for writing, securing, and maintaining C and C++ code.
https://github.com/anirudhakulkarni/awesome-ai-cpp
https://redd.it/1upx416
@r_cpp
https://github.com/anirudhakulkarni/awesome-ai-cpp
https://redd.it/1upx416
@r_cpp
GitHub
GitHub - anirudhakulkarni/awesome-ai-cpp: A curated list of AI, LLM, and agent tools for writing, securing, and maintaining C and…
A curated list of AI, LLM, and agent tools for writing, securing, and maintaining C and C++ code. - anirudhakulkarni/awesome-ai-cpp
HedgeDB key value store
Hey guys,
I was looking around on LinkedIn (here) and found this project called HedgeDB, which is an embedded key-value store inspired by RocksDB but written with C++20 coroutines, io_uring and O_DIRECT. I am not the creator or anything, I am just trying to understand storage engines and the architectural choices behind them.
The repo shows some crazy benchmarks compared to RocksDB, like 3x or 5x better throughput, but the project is a very early prototype (v0.0.1) and misses basic things like block compression or handling values larger than 4KB. Everytime I see this high performamce metrics I am always skeptical, and never know which path to follow.
As any of you tried it? What's your take on it and where is the catch?
Here is the repo: https://github.com/fede-vaccaro/HedgeDB
https://redd.it/1upzhfz
@r_cpp
Hey guys,
I was looking around on LinkedIn (here) and found this project called HedgeDB, which is an embedded key-value store inspired by RocksDB but written with C++20 coroutines, io_uring and O_DIRECT. I am not the creator or anything, I am just trying to understand storage engines and the architectural choices behind them.
The repo shows some crazy benchmarks compared to RocksDB, like 3x or 5x better throughput, but the project is a very early prototype (v0.0.1) and misses basic things like block compression or handling values larger than 4KB. Everytime I see this high performamce metrics I am always skeptical, and never know which path to follow.
As any of you tried it? What's your take on it and where is the catch?
Here is the repo: https://github.com/fede-vaccaro/HedgeDB
https://redd.it/1upzhfz
@r_cpp
LinkedIn
I'm looking forward to hear your feedbacks about the first version of my 𝗛𝗲𝗱𝗴𝗲𝗗𝗕 https://lnkd.in/dz8ZvPpn, a high-performance and…
I'm looking forward to hear your feedbacks about the first version of my 𝗛𝗲𝗱𝗴𝗲𝗗𝗕 https://lnkd.in/dz8ZvPpn, a high-performance and persisted Key Value store, inspired from RocksDB!
By leveraging asynchronous execution with modern C++20 coroutines, lock-free…
By leveraging asynchronous execution with modern C++20 coroutines, lock-free…
Upcoming LA Sprawl C++ Meetups
https://www.meetup.com/los-angeles-sprawl-cpp-meetup-group/events/315505501/?utm_medium=referral&utm_campaign=share-btn_savedevents_share_modal&utm_source=link&utm_version=v2&member_id=285837689
https://redd.it/1uq12o3
@r_cpp
https://www.meetup.com/los-angeles-sprawl-cpp-meetup-group/events/315505501/?utm_medium=referral&utm_campaign=share-btn_savedevents_share_modal&utm_source=link&utm_version=v2&member_id=285837689
https://redd.it/1uq12o3
@r_cpp
Meetup
C++ Show & Tell (in-person), Thu, Aug 6, 2026, 6:30 PM | Meetup
At this in-person meetup, we are welcoming people to show off whatever cool \`c++\` project(s) you might like to show off. Not pure \`c++\`? That's okay, I'd like to share
libcwd (C++ debugging library) released under MIT license!
Hi all,
I am happy to announce that after 333 commits spanning two months of continuous work, I released version 2 of libcwd, now under a new license: the MIT license!
The website has been re-done (as well as a lot of other things);
see https://carlowood.github.io/libcwd/index.html?libcwd-theme=dark
There you can also find how to get it (basically, from the git repository; there is no tar ball (yet)).
Let me know what you think or if you need help,
my email address is at the bottom of the INSTALL file.
Carlo Wood
---
Background
For those unfamiliar with libcwd. Version 0.99 was the first public release in 2000 under the QPL; I've used and tuned it for more than two decades, being a very active C++ developer myself (on linux).
Version 1.x had memory allocation support; I removed this in version 2 because it made things very very complicated, and I never needed that myself anymore since a decade anyway.
Version 2 still does, as did version 1, ELF and DWARF decoding of the executable and linked shared libraries. For this a POSIX system with ELF is necessary. But libcwd can be configured without Location support too; you should be able to use it for just (multi-threaded) debug output on, for example, Windows.
https://redd.it/1uq2fql
@r_cpp
Hi all,
I am happy to announce that after 333 commits spanning two months of continuous work, I released version 2 of libcwd, now under a new license: the MIT license!
The website has been re-done (as well as a lot of other things);
see https://carlowood.github.io/libcwd/index.html?libcwd-theme=dark
There you can also find how to get it (basically, from the git repository; there is no tar ball (yet)).
Let me know what you think or if you need help,
my email address is at the bottom of the INSTALL file.
Carlo Wood
---
Background
For those unfamiliar with libcwd. Version 0.99 was the first public release in 2000 under the QPL; I've used and tuned it for more than two decades, being a very active C++ developer myself (on linux).
Version 1.x had memory allocation support; I removed this in version 2 because it made things very very complicated, and I never needed that myself anymore since a decade anyway.
Version 2 still does, as did version 1, ELF and DWARF decoding of the executable and linked shared libraries. For this a POSIX system with ELF is necessary. But libcwd can be configured without Location support too; you should be able to use it for just (multi-threaded) debug output on, for example, Windows.
https://redd.it/1uq2fql
@r_cpp
carlowood.github.io
libcwd: Introduction
libcwd is a C++ debugging support library with ostream debug output, debug channels, source locations, and demangled type names.
C++ Primer 6th edition by Stanley Lippman et al
Wondering if anyone has news on whether the 6th edition will be released? It has been slated for march 2025 but it's more than a year since then.
Stanley Lippman has passed in 2022, rip, so is the 6th edition never going to be released?
Have seen listings for the 6th edition on online shop pages but they are stated as unavailable yet.
https://redd.it/1uq3vl5
@r_cpp
Wondering if anyone has news on whether the 6th edition will be released? It has been slated for march 2025 but it's more than a year since then.
Stanley Lippman has passed in 2022, rip, so is the 6th edition never going to be released?
Have seen listings for the 6th edition on online shop pages but they are stated as unavailable yet.
https://redd.it/1uq3vl5
@r_cpp
Reddit
From the cpp community on Reddit
Explore this post and more from the cpp community
The State Design pattern in C++ using timer and notification
https://som-itsolutions.blogspot.com/2022/05/the-state-design-pattern-in-c-using.html
https://redd.it/1uqj9vd
@r_cpp
https://som-itsolutions.blogspot.com/2022/05/the-state-design-pattern-in-c-using.html
https://redd.it/1uqj9vd
@r_cpp
Blogspot
The State Design pattern in C++ using timer and notification
As a #guru, let me pay my respects today to my #guru in the tech world, who has influenced my technical skills and mindset. Although I h...
C++26: Standard library hardening -- Sandor Dargo
https://isocpp.org//blog/2026/07/cpp26-standard-library-hardening-sandor-dargo
https://redd.it/1uqlmt3
@r_cpp
https://isocpp.org//blog/2026/07/cpp26-standard-library-hardening-sandor-dargo
https://redd.it/1uqlmt3
@r_cpp
Reddit
From the cpp community on Reddit: C++26: Standard library hardening -- Sandor Dargo
Posted by Secret_Regret7798 - 10 votes and 0 comments
Stream compaction on NEON: vectorizing copy_if by hand (30x)
## Problem
Given two arrays `a` and `out`, write into `out`, with no gaps, only those elements of `a` that satisfy a given
condition.
Here, the condition is `a[i] > threshold`, with `a[i] ∈ (0, 1)` and `threshold ∈ {0, 0.5, 1}`.
## Why the compiler gives up
A single `if` in a copy loop drops throughput from 112 to as low as 2.6 GB/s:
the compiler can't vectorize it, because NEON has no compress instruction. Here's how to build it.
```cpp
auto copy_if(const float* a, float* out, size_t n) {
size_t j = 0;
for (size_t i = 0; i < n; ++i) {
if (a[i] > 0) out[j++] = a[i];
}
return j;
}
```
In copy_if, the output cursor `j` depends on the data. To vectorize the loop, the compiler needs a compress instruction
(one that collects selected elements at the front of the register, with no gaps). NEON has no such instruction, so the
compiler
gives up:
```text
clang++ -O3 -Rpass-analysis=loop-vectorize -std=c++23 main.cpp -o main
main.cpp:5:5: remark: loop not vectorized: value that could not be identified as reduction is used outside the loop [-Rpass-analysis=loop-vectorize]
5 | for (size_t i = 0; i < n; ++i) {
| ^
main.cpp:6:23: remark: loop not vectorized: cannot identify array bounds [-Rpass-analysis=loop-vectorize]
6 | if (a[i] > 0) out[j++] = a[i];
```
The clang vectorizer can only classify `j` as either an induction (fixed step) or a reduction,
but `j` is neither of those. It's a data-dependent cursor.
The compiler cannot vectorize this type of cursor.
The second remark has the same cause: it cannot compute the range of accesses to `out`.
## Benchmark: two scalar problems
*All benchmarks: Apple M5; clang++ -O3 -std=c++23 -march=native; GB/s = (2n * 4 bytes) / time, min of 3e9 / n runs;
cache: n=1e5, DRAM: n=1e7*
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] | 0.004 | 195 | 0.71 | 112 |
| copy a[i] if a[i] > 0 | 0.022 | 37 | 2.41 | 33 |
| copy a[i] if a[i] > 0.5 | 0.258 | 3.1 | 30.61 | 2.6 |
| copy a[i] if a[i] > 1 | 0.021 | 37 | 2.39 | 33 |
"copy a[i]" is the same loop, but with no condition. The compiler vectorizes it. The only difference is a single `if`.
The same data, only the branch predictability changes:
1. \> 0 (always true) and > 1 (always false): branch predictor never misses → 33 GB/s. The lack of vectorization costs
3x.
2. \> 0.5 (50/50): the branch predictor misses on every second element → 3 GB/s
The trick fixes both problems.
## Trick 1: compress emulation
Let `n` be a multiple of the register width; the tail is a separate topic and has nothing to do with this trick.
Also:
1. The size of `out` must be >= `n`.
2. Suppose the algorithm selected `cnt` elements. Then all elements in `out[cnt, n)` are left undefined (garbage).
An algorithm that keeps the tail clean adds nothing new to the idea, so it will not be considered.
NEON - the SIMD instruction set used in Apple M-series chips and almost every mobile core - has no instruction for compressing a
register, so we have to emulate it.
(To be fair, the trick itself is not new. Lemire
[used it on SSE](https://lemire.me/blog/2017/01/20/how-quickly-can-you-remove-spaces-from-a-string/) back in 2017.
But NEON has no movemask and no cheap popcnt.)
Here's how to build it from what we do have.
What our compress analog needs to be able to do:
1. Accept a register from `a` and a mask register that says which elements to keep.
2. Return the number of elements we selected (to move the `out` pointer).
3. Store the selected elements in `out`.
### tbl: arbitrary byte selection
NEON has the table-lookup (`tbl`) instruction family. Its purpose is arbitrary byte permutation/selection.
The instruction accepts two registers:
1. `table` - the bytes to select from.
2. `index` - the positions of the bytes to take.
In other
## Problem
Given two arrays `a` and `out`, write into `out`, with no gaps, only those elements of `a` that satisfy a given
condition.
Here, the condition is `a[i] > threshold`, with `a[i] ∈ (0, 1)` and `threshold ∈ {0, 0.5, 1}`.
## Why the compiler gives up
A single `if` in a copy loop drops throughput from 112 to as low as 2.6 GB/s:
the compiler can't vectorize it, because NEON has no compress instruction. Here's how to build it.
```cpp
auto copy_if(const float* a, float* out, size_t n) {
size_t j = 0;
for (size_t i = 0; i < n; ++i) {
if (a[i] > 0) out[j++] = a[i];
}
return j;
}
```
In copy_if, the output cursor `j` depends on the data. To vectorize the loop, the compiler needs a compress instruction
(one that collects selected elements at the front of the register, with no gaps). NEON has no such instruction, so the
compiler
gives up:
```text
clang++ -O3 -Rpass-analysis=loop-vectorize -std=c++23 main.cpp -o main
main.cpp:5:5: remark: loop not vectorized: value that could not be identified as reduction is used outside the loop [-Rpass-analysis=loop-vectorize]
5 | for (size_t i = 0; i < n; ++i) {
| ^
main.cpp:6:23: remark: loop not vectorized: cannot identify array bounds [-Rpass-analysis=loop-vectorize]
6 | if (a[i] > 0) out[j++] = a[i];
```
The clang vectorizer can only classify `j` as either an induction (fixed step) or a reduction,
but `j` is neither of those. It's a data-dependent cursor.
The compiler cannot vectorize this type of cursor.
The second remark has the same cause: it cannot compute the range of accesses to `out`.
## Benchmark: two scalar problems
*All benchmarks: Apple M5; clang++ -O3 -std=c++23 -march=native; GB/s = (2n * 4 bytes) / time, min of 3e9 / n runs;
cache: n=1e5, DRAM: n=1e7*
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] | 0.004 | 195 | 0.71 | 112 |
| copy a[i] if a[i] > 0 | 0.022 | 37 | 2.41 | 33 |
| copy a[i] if a[i] > 0.5 | 0.258 | 3.1 | 30.61 | 2.6 |
| copy a[i] if a[i] > 1 | 0.021 | 37 | 2.39 | 33 |
"copy a[i]" is the same loop, but with no condition. The compiler vectorizes it. The only difference is a single `if`.
The same data, only the branch predictability changes:
1. \> 0 (always true) and > 1 (always false): branch predictor never misses → 33 GB/s. The lack of vectorization costs
3x.
2. \> 0.5 (50/50): the branch predictor misses on every second element → 3 GB/s
The trick fixes both problems.
## Trick 1: compress emulation
Let `n` be a multiple of the register width; the tail is a separate topic and has nothing to do with this trick.
Also:
1. The size of `out` must be >= `n`.
2. Suppose the algorithm selected `cnt` elements. Then all elements in `out[cnt, n)` are left undefined (garbage).
An algorithm that keeps the tail clean adds nothing new to the idea, so it will not be considered.
NEON - the SIMD instruction set used in Apple M-series chips and almost every mobile core - has no instruction for compressing a
register, so we have to emulate it.
(To be fair, the trick itself is not new. Lemire
[used it on SSE](https://lemire.me/blog/2017/01/20/how-quickly-can-you-remove-spaces-from-a-string/) back in 2017.
But NEON has no movemask and no cheap popcnt.)
Here's how to build it from what we do have.
What our compress analog needs to be able to do:
1. Accept a register from `a` and a mask register that says which elements to keep.
2. Return the number of elements we selected (to move the `out` pointer).
3. Store the selected elements in `out`.
### tbl: arbitrary byte selection
NEON has the table-lookup (`tbl`) instruction family. Its purpose is arbitrary byte permutation/selection.
The instruction accepts two registers:
1. `table` - the bytes to select from.
2. `index` - the positions of the bytes to take.
In other
Daniel Lemire's blog
How quickly can you remove spaces from a string?
Sometimes programmers want to prune out characters from a string of characters. For example, maybe you want to remove all line-ending characters from a piece of text. Let me consider the problem where I want to remove all spaces (' ') and linefeed characters…
words, this is a SIMD analog of `out[i] = table[index[i]]`.
We will use the `vqtbl1q_u8` instruction:
| part | meaning |
|:----:|------------------------------------------|
| v | vector intrinsic |
| q | table consists of 128-bit registers |
| tbl | table lookup |
| 1 | number of registers in table |
| q | result and indices are 128-bit registers |
| u8 | elements of table are `uint8_t` |
`tbl` permutes bytes, but we need to select floats (4 bytes). So, we will create `index` in blocks of 4 bytes:
to select the second (0-based) float of the register, `index` will contain its bytes [8, 9, 10, 11] (the second element
starts at an offset of `2 * sizeof(float) = 8`).
Computing `index` every time is slow. There are 16 variants in total (4 elements to take/drop), so we will precompute all the `index` variants.
But to select the `index` using the mask, we need to convert the mask to a number (call it `idx`):
### mask → idx
The mask consists of 4 elements, each either 0x00000000 (false) or 0xFFFFFFFF (true).
If the i-th element is true, we want to set the i-th bit in idx.
Trick: `mask & [1, 2, 4, 8]`. Because `0xFFFFFFFF & x = x`, the true elements keep their weight (1/2/4/8), while the false ones become 0.
We add all elements together and get a number between 0 and 15.
```cpp
std::array<uint32_t, 4> weights{1, 2, 4, 8};
size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
```
- `vld1q_u32(weights.data())` - load 4 values from memory at address `weights.data()` into a register (ld - load)
- `vandq_u32` - elementwise & (and)
- `vaddvq_u32` - sum of all the elements in the register (addv - add across vector)
### Precompute the `index` table
There is no way to compute registers at compile time, so instead of `uint8x16_t` (register of 16 `uint8_t`) we will store `std::array<uint8_t, 16>`.
For each `idx` we will go through the 4 elements of `mask`. If the element is selected,
we append the indices of its 4 bytes into `index` at the cursor position and advance the cursor by 4.
```cpp
consteval auto make_index_table() {
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) { // iterate over all masks
size_t j = 0; // j is the cursor
for (size_t i = 0; i < 4; ++i) // iterate over the mask's elements
if (idx & (1 << i)) // if the i-th element is selected
for (size_t k = 0; k < 4; ++k) // iterate over its bytes
index[idx][j++] = i * 4 + k; // store the indices of its bytes
}
return index;
}
```
The `j` cursor advances only on selected elements, so their bytes are placed in `index` consecutively. `tbl` with that `index`
collects floats into a register. Unused positions in `index` are zeros, so in the tail, after `count` elements, there will be garbage.
### The `count` table
Next we need to compute the number of elements we select. Similarly we can precompute a table for this:
```cpp
consteval auto make_count_table() {
std::array<uint8_t, 16> count{};
for (size_t idx = 0; idx < 16; ++idx)
for (size_t i = 0; i < 4; ++i)
if (idx & 1 << i)
++count[idx];
return count;
}
```
### The full `compress`
```cpp
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1, 2, 4, 8};
const size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
static constexpr auto count = make_count_table();
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data()); // at runtime, loads only one row of the table into a register
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count[idx]};
}
```
Because `tbl` works only with u8, we need to cast `a` to u8 and then cast the result back to f32.
We write full
We will use the `vqtbl1q_u8` instruction:
| part | meaning |
|:----:|------------------------------------------|
| v | vector intrinsic |
| q | table consists of 128-bit registers |
| tbl | table lookup |
| 1 | number of registers in table |
| q | result and indices are 128-bit registers |
| u8 | elements of table are `uint8_t` |
`tbl` permutes bytes, but we need to select floats (4 bytes). So, we will create `index` in blocks of 4 bytes:
to select the second (0-based) float of the register, `index` will contain its bytes [8, 9, 10, 11] (the second element
starts at an offset of `2 * sizeof(float) = 8`).
Computing `index` every time is slow. There are 16 variants in total (4 elements to take/drop), so we will precompute all the `index` variants.
But to select the `index` using the mask, we need to convert the mask to a number (call it `idx`):
### mask → idx
The mask consists of 4 elements, each either 0x00000000 (false) or 0xFFFFFFFF (true).
If the i-th element is true, we want to set the i-th bit in idx.
Trick: `mask & [1, 2, 4, 8]`. Because `0xFFFFFFFF & x = x`, the true elements keep their weight (1/2/4/8), while the false ones become 0.
We add all elements together and get a number between 0 and 15.
```cpp
std::array<uint32_t, 4> weights{1, 2, 4, 8};
size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
```
- `vld1q_u32(weights.data())` - load 4 values from memory at address `weights.data()` into a register (ld - load)
- `vandq_u32` - elementwise & (and)
- `vaddvq_u32` - sum of all the elements in the register (addv - add across vector)
### Precompute the `index` table
There is no way to compute registers at compile time, so instead of `uint8x16_t` (register of 16 `uint8_t`) we will store `std::array<uint8_t, 16>`.
For each `idx` we will go through the 4 elements of `mask`. If the element is selected,
we append the indices of its 4 bytes into `index` at the cursor position and advance the cursor by 4.
```cpp
consteval auto make_index_table() {
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) { // iterate over all masks
size_t j = 0; // j is the cursor
for (size_t i = 0; i < 4; ++i) // iterate over the mask's elements
if (idx & (1 << i)) // if the i-th element is selected
for (size_t k = 0; k < 4; ++k) // iterate over its bytes
index[idx][j++] = i * 4 + k; // store the indices of its bytes
}
return index;
}
```
The `j` cursor advances only on selected elements, so their bytes are placed in `index` consecutively. `tbl` with that `index`
collects floats into a register. Unused positions in `index` are zeros, so in the tail, after `count` elements, there will be garbage.
### The `count` table
Next we need to compute the number of elements we select. Similarly we can precompute a table for this:
```cpp
consteval auto make_count_table() {
std::array<uint8_t, 16> count{};
for (size_t idx = 0; idx < 16; ++idx)
for (size_t i = 0; i < 4; ++i)
if (idx & 1 << i)
++count[idx];
return count;
}
```
### The full `compress`
```cpp
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1, 2, 4, 8};
const size_t idx = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
static constexpr auto count = make_count_table();
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data()); // at runtime, loads only one row of the table into a register
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count[idx]};
}
```
Because `tbl` works only with u8, we need to cast `a` to u8 and then cast the result back to f32.
We write full
registers of 4 floats to memory, but advance the cursor only by `cnt`.
`compress` stores valid elements at the front of the register, at `[j, j + cnt)`, and garbage at `[j + cnt, j + 4)`.
The next iteration will start at `j + cnt` and overwrite the garbage from the previous step.
Garbage will remain only in `out[cnt, n)` after the last store.
We don't go out of bounds because the cursor never overtakes the elements that have been read.
## The `copy_if` loop
```cpp
auto copy_if_neon(const float* __restrict a,
float* __restrict out,
float threshold,
size_t n) {
auto thd = vdupq_n_f32(threshold); // load threshold into a register
size_t j = 0;
for (size_t i = 0; i < n; i += 4) {
auto v = vld1q_f32(a + i); // load the current 4 elements of a into a register
auto mask = vcgtq_f32(v, thd); // compute the mask
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed); // store packed into out[j, j + 4). [j + cnt, j + 4) will hold garbage
j += cnt;
}
return j;
}
```
- `vcgtq_f32(v, thd)` - calculate elementwise `v[i] > thd[i]`. `cgt` - compare greater
- `vst1q_f32` - store 4 floats from a register into memory. `st` - store
## Result
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0104 | 77 | 1.11 | 72 |
| copy a[i] if a[i] > 0.5 | 0.01063 | 75 | 1.13 | 71 |
| copy a[i] if a[i] > 1 | 0.01 | 80 | 1.06 | 76 |
\> 0.5 was the worst case for the scalar version, 3 GB/s. Now 71 GB/s. A more than 20x speedup.
Now there are no branches, so speed doesn't depend on data.
## Trick 2: calculating `idx` and `count` in a single `addv`
`idx` is always less than 16, so let weights = {1 + 16, 2 + 16, 4 + 16, 8 + 16}
and s = sum across mask & weights. Then s / 16 is the element count and s % 16 is `idx`.
So, we don't need to compute the `count` table.
`compress` now:
```cpp
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
const size_t count = s >> 4; // same as s / 16
const size_t idx = s & 15; // same as s % 16
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data());
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}
```
And now the speed climbs again:
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0095 | 84 | 1.015 | 79 |
| copy a[i] if a[i] > 0.5 | 0.0095 | 84 | 1.007 | 79 |
| copy a[i] if a[i] > 1 | 0.0096 | 83 | 1.008 | 79 |
## Unroll
We can squeeze out more speed by unrolling the loop 4x (16 elements per iteration):
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0081 | 98 | 0.882 | 91 |
| copy a[i] if a[i] > 0.5 | 0.0083 | 97 | 0.892 | 90 |
| copy a[i] if a[i] > 1 | 0.0082 | 97 | 0.869 | 92 |
Final code ([godbolt](https://godbolt.org/z/n8E6ocKoj)):
```cpp
consteval auto make_index_table() {
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) {
size_t j = 0;
for (size_t i = 0; i < 4; ++i)
if (idx & (1 << i))
for (size_t k = 0; k < 4; ++k)
index[idx][j++] = i * 4 + k;
}
return
`compress` stores valid elements at the front of the register, at `[j, j + cnt)`, and garbage at `[j + cnt, j + 4)`.
The next iteration will start at `j + cnt` and overwrite the garbage from the previous step.
Garbage will remain only in `out[cnt, n)` after the last store.
We don't go out of bounds because the cursor never overtakes the elements that have been read.
## The `copy_if` loop
```cpp
auto copy_if_neon(const float* __restrict a,
float* __restrict out,
float threshold,
size_t n) {
auto thd = vdupq_n_f32(threshold); // load threshold into a register
size_t j = 0;
for (size_t i = 0; i < n; i += 4) {
auto v = vld1q_f32(a + i); // load the current 4 elements of a into a register
auto mask = vcgtq_f32(v, thd); // compute the mask
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed); // store packed into out[j, j + 4). [j + cnt, j + 4) will hold garbage
j += cnt;
}
return j;
}
```
- `vcgtq_f32(v, thd)` - calculate elementwise `v[i] > thd[i]`. `cgt` - compare greater
- `vst1q_f32` - store 4 floats from a register into memory. `st` - store
## Result
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0104 | 77 | 1.11 | 72 |
| copy a[i] if a[i] > 0.5 | 0.01063 | 75 | 1.13 | 71 |
| copy a[i] if a[i] > 1 | 0.01 | 80 | 1.06 | 76 |
\> 0.5 was the worst case for the scalar version, 3 GB/s. Now 71 GB/s. A more than 20x speedup.
Now there are no branches, so speed doesn't depend on data.
## Trick 2: calculating `idx` and `count` in a single `addv`
`idx` is always less than 16, so let weights = {1 + 16, 2 + 16, 4 + 16, 8 + 16}
and s = sum across mask & weights. Then s / 16 is the element count and s % 16 is `idx`.
So, we don't need to compute the `count` table.
`compress` now:
```cpp
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
const size_t count = s >> 4; // same as s / 16
const size_t idx = s & 15; // same as s % 16
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data());
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}
```
And now the speed climbs again:
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0095 | 84 | 1.015 | 79 |
| copy a[i] if a[i] > 0.5 | 0.0095 | 84 | 1.007 | 79 |
| copy a[i] if a[i] > 1 | 0.0096 | 83 | 1.008 | 79 |
## Unroll
We can squeeze out more speed by unrolling the loop 4x (16 elements per iteration):
| function | ms (cache) | GB/s (cache) | ms (DRAM) | GB/s (DRAM) |
|-------------------------|-----------:|-------------:|----------:|------------:|
| copy a[i] if a[i] > 0 | 0.0081 | 98 | 0.882 | 91 |
| copy a[i] if a[i] > 0.5 | 0.0083 | 97 | 0.892 | 90 |
| copy a[i] if a[i] > 1 | 0.0082 | 97 | 0.869 | 92 |
Final code ([godbolt](https://godbolt.org/z/n8E6ocKoj)):
```cpp
consteval auto make_index_table() {
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) {
size_t j = 0;
for (size_t i = 0; i < 4; ++i)
if (idx & (1 << i))
for (size_t k = 0; k < 4; ++k)
index[idx][j++] = i * 4 + k;
}
return
godbolt.org
Compiler Explorer - C++ (armv8-a clang (trunk))
consteval auto make_index_table() {
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) {
size_t j = 0;
for (size_t i = 0; i < 4; ++i)
if (idx & (1 << i))
for (size_t k…
std::array<std::array<uint8_t, 16>, 16> index{};
for (size_t idx = 0; idx < 16; ++idx) {
size_t j = 0;
for (size_t i = 0; i < 4; ++i)
if (idx & (1 << i))
for (size_t k…
index;
}
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
const size_t count = s >> 4;
const size_t idx = s & 15;
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data());
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}
auto copy_if_neon_unroll(const float* __restrict a,
float* __restrict out,
float threshold,
size_t n) {
auto thd = vdupq_n_f32(threshold);
size_t j = 0;
size_t i = 0;
for (; i + 16 <= n; i += 16) {
#pragma unroll
for (size_t i0 = 0; i0 < 16; i0 += 4) {
auto v = vld1q_f32(a + i + i0);
auto mask = vcgtq_f32(v, thd);
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed);
j += cnt;
}
}
for (; i + 4 <= n; i += 4) {
auto v = vld1q_f32(a + i);
auto mask = vcgtq_f32(v, thd);
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed);
j += cnt;
}
return j;
}
```
`tbl` and the index table provide `compress`, something that NEON doesn't have out of the box.
This isn't just about `> threshold`. Filter, remove and other data-dependent functions are built the same way.
https://redd.it/1uqscnr
@r_cpp
}
auto compress(uint32x4_t mask, float32x4_t a) {
static constexpr std::array<uint32_t, 4> weights{1 + 16, 2 + 16, 4 + 16, 8 + 16};
const size_t s = vaddvq_u32(vandq_u32(mask, vld1q_u32(weights.data())));
const size_t count = s >> 4;
const size_t idx = s & 15;
static constexpr auto index_table = make_index_table();
const auto index = vld1q_u8(index_table[idx].data());
return std::pair{vreinterpretq_f32_u8(vqtbl1q_u8(vreinterpretq_u8_f32(a), index)), count};
}
auto copy_if_neon_unroll(const float* __restrict a,
float* __restrict out,
float threshold,
size_t n) {
auto thd = vdupq_n_f32(threshold);
size_t j = 0;
size_t i = 0;
for (; i + 16 <= n; i += 16) {
#pragma unroll
for (size_t i0 = 0; i0 < 16; i0 += 4) {
auto v = vld1q_f32(a + i + i0);
auto mask = vcgtq_f32(v, thd);
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed);
j += cnt;
}
}
for (; i + 4 <= n; i += 4) {
auto v = vld1q_f32(a + i);
auto mask = vcgtq_f32(v, thd);
auto [packed, cnt] = compress(mask, v);
vst1q_f32(out + j, packed);
j += cnt;
}
return j;
}
```
`tbl` and the index table provide `compress`, something that NEON doesn't have out of the box.
This isn't just about `> threshold`. Filter, remove and other data-dependent functions are built the same way.
https://redd.it/1uqscnr
@r_cpp
Reddit
From the cpp community on Reddit: Stream compaction on NEON: vectorizing copy_if by hand (30x)
Explore this post and more from the cpp community