Skip to content

Commit a449e13

Browse files
committed
adding a discussiong of dispatching
1 parent 7a26ed0 commit a449e13

File tree

3 files changed

+304
-0
lines changed

3 files changed

+304
-0
lines changed

cppcon2025/cppcon_2025_slides.md

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,205 @@ Through concepts and template specialization, we support:
667667
668668
All work seamlessly with reflection!
669669
670+
671+
---
672+
673+
# Runtime dispatching
674+
675+
- One function semantically
676+
- Several implementations
677+
- Select the best one at runtime for performance.
678+
679+
680+
681+
---
682+
683+
# Issue: x64 processors support different instructions
684+
685+
A Zen 5 CPU and a Pentium 4 CPU can be quite different.
686+
687+
```cpp
688+
bool has_sse2() { /* query the CPU */ }
689+
bool has_avx2() { /* query the CPU */ }
690+
bool has_avx512() { /* query the CPU */ }
691+
```
692+
693+
These functions cannot be `consteval`.
694+
695+
696+
---
697+
698+
<img src="images/dispatching.svg" width="50%">
699+
700+
---
701+
702+
# Example: Sum function
703+
704+
```cpp
705+
using SumFunc = float (*)(const float *, size_t);
706+
```
707+
708+
---
709+
710+
# Setup a reassignable implementation
711+
712+
713+
```cpp
714+
SumFunc &get_sum_fnc() {
715+
static SumFunc sum_impl = sum_init;
716+
return sum_impl;
717+
}
718+
```
719+
720+
We initialize it with some special initialization function.
721+
722+
723+
724+
---
725+
726+
```cpp
727+
float sum_init(const float *data, size_t n) {
728+
SumFunc &sum_impl = get_sum_fnc();
729+
if (has_avx2()) {
730+
sum_impl = sum_avx2;
731+
} else if (has_sse2()) {
732+
sum_impl = sum_sse2;
733+
} else {
734+
sum_impl = sum_generic;
735+
}
736+
return sum_impl(data, n);
737+
}
738+
```
739+
740+
On first call, `get_sum_fnc()` is modified, and then it will remain constant.
741+
742+
---
743+
744+
# Runtime dispatching and metaprogramming
745+
746+
- Metaprogramming is at compile-time.
747+
- Runtime dispatching is fundamentally at runtime.
748+
749+
---
750+
751+
# Does your string need escaping?
752+
753+
754+
- In JSON, you must escape control characters, quotes.
755+
- Most strings in practice do not need escaping.
756+
757+
758+
```Cpp
759+
simple_needs_escaping(std::string_view v) {
760+
for (unsigned char c : v) {
761+
if(json_quotable_character[c]) { return true; }
762+
}
763+
return false;
764+
}
765+
```
766+
767+
---
768+
769+
770+
## SIMD
771+
772+
- Stands for Single instruction, multiple data
773+
- Allows us to process 16 (or more) bytes or more with one instruction
774+
- Supported on all modern CPUs (phone, laptop)
775+
776+
---
777+
778+
# SIMD (Pentium 4 and better)
779+
780+
```cpp
781+
__m128i word = _mm_loadu_si128(data); // load 16 bytes
782+
// check for control characters:
783+
_mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
784+
_mm_setzero_si128());
785+
```
786+
787+
---
788+
789+
# SIMD (AVX-512)
790+
791+
```cpp
792+
__m512i word = _mm512_loadu_si512(data); // load 64 bytes
793+
// check for control characters:
794+
_mm512_cmple_epu8_mask(word, _mm512_set1_epi8(31));
795+
```
796+
797+
---
798+
799+
# Runtime dispatching is poor with quick functions
800+
801+
- Calling a fast function like `fast_needs_escaping` without inlining prevents useful optimizations.
802+
- Runtime dispatching implies a function call!
803+
804+
---
805+
806+
# Current solution
807+
808+
- No runtime dispatching (*sad face*).
809+
- All x64 processors support Pentium 4-level SIMD. Use that in a short function.
810+
- *Easy* if programmer builds for specific machine (`-march=native`), use fancier tricks.
811+
812+
---
813+
814+
# Compile-time string escaping
815+
816+
- Often the 'keys' are known at compile time.
817+
818+
819+
```cpp
820+
struct Player {
821+
std::string username;
822+
int level;
823+
double health;
824+
std::vector<std::string> inventory;
825+
};
826+
```
827+
828+
- Keys are: `username`, `level`, `health`, `inventory`.
829+
830+
---
831+
832+
# Escape at compile time.
833+
834+
```cpp
835+
[:expand(std::meta::nonstatic_data_members_of(...)] {
836+
constexpr auto key =
837+
std::define_static_string(consteval_to_quoted_escaped(
838+
std::meta::identifier_of(dm)));
839+
b.append_raw(key);
840+
b.append(':');
841+
// ...
842+
};
843+
```
844+
845+
---
846+
847+
# Otherwise tricky to do
848+
849+
- Outside metaprogramming, lots of values are compile-time constants
850+
- But processing it at compile time is not always easy/convenient.
851+
852+
---
853+
854+
# Example: `g` returns 1
855+
856+
```cpp
857+
constexpr int convert(const char * x) {
858+
if (std::is_constant_evaluated()) { return 0; }
859+
return 1;
860+
}
861+
862+
int g() {
863+
constexpr char key[] = "name";
864+
auto x = convert(key);
865+
return x;
866+
}
867+
```
868+
670869
---
671870
672871
# Conclusion

cppcon2025/images/dispatching.svg

Lines changed: 45 additions & 0 deletions
Loading
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <cstdint>
2+
#include <iostream>
3+
4+
// fake
5+
bool has_sse2() { return true; }
6+
bool has_avx2() { return false; }
7+
8+
using SumFunc = float (*)(const float *, size_t);
9+
10+
float sum_generic(const float *data, size_t n) {
11+
float sum = 0.0f;
12+
for (size_t i = 0; i < n; ++i) {
13+
sum += data[i];
14+
}
15+
return sum;
16+
}
17+
18+
float sum_sse2(const float *data, size_t n) {
19+
printf("sum_sse2...\n");
20+
21+
return 1.0; // fake
22+
}
23+
24+
float sum_avx2(const float *data, size_t n) {
25+
return 1.0; // fake
26+
}
27+
28+
SumFunc &get_sum_fnc();
29+
// Fonction d'initialisation pour le dispatching
30+
float sum_init(const float *data, size_t n) {
31+
std::cout << "Initialisation de la fonction sum...\n";
32+
SumFunc &sum_impl = get_sum_fnc();
33+
if (has_avx2()) {
34+
sum_impl = sum_avx2;
35+
} else if (has_sse2()) {
36+
sum_impl = sum_sse2;
37+
} else {
38+
sum_impl = sum_generic;
39+
}
40+
return sum_impl(data, n);
41+
}
42+
43+
// Gestion du pointeur de fonction statique
44+
SumFunc &get_sum_fnc() {
45+
static SumFunc sum_impl = sum_init;
46+
return sum_impl;
47+
}
48+
49+
// Fonction principale avec dispatching
50+
float sum(const float *data, size_t n) { return get_sum_fnc()(data, n); }
51+
52+
int main() {
53+
float data[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
54+
size_t n = sizeof(data) / sizeof(data[0]);
55+
float result = sum(data, n);
56+
std::cout << "sum : " << result << std::endl;
57+
float result2 = sum(data, n);
58+
std::cout << "sum : " << result2 << std::endl;
59+
return 0;
60+
}

0 commit comments

Comments
 (0)