C++
[C++] Value vs. Reference type conversion overload ranking tie breaker rule. (compile time conversion deduction)
Symphilosophein 狂者進取,狷者有所不為也。
Reference:
A deep dive into SmallVector::push_back
https://llvm-compile-time-tracker.com/
LLVM_ATTRIBUTE_NOINLINE void growAndPushBack(ValueParamT Elt) {
// in case Elt aliases storage that grow() invalidates
// This is very much edge-case considered.
T Tmp = Elt;
// +1 is sufficient, while internally doing
// exponential growth algorithm.
this->grow(this->size() + 1);
std::memcpy(reinterpret_cast<void *>(this->end()), &Tmp, sizeof(T));
// size is not just +1 but applied with
// exponential growth algorithm.
this->set_size(this->size() + 1);
}
void push_back(ValueParamT Elt) {
if (LLVM_UNLIKELY(this->size() >= this->capacity()))
return growAndPushBack(Elt);
std::memcpy(reinterpret_cast<void *>(this->end()), &Elt, sizeof(T));
this->set_size(this->size() + 1);
}
mov eax, [rdi + 8]
cmp eax, [rdi + 12]
jae growAndPushBack # TAILCALL
mov rcx, [rdi]
mov [rcx + rax*4], esi
inc dword ptr [rdi + 8]
ret// noinline growAndPushBack is load-bearing for both Clang and GCC.
void DecodeMOVDDUPMask(unsigned n, llvm::SmallVectorImpl<int> &v) {
for (unsigned l = 0; l < n; l += 2)
for (unsigned i = 0; i < 2; ++i)
v.push_back(i);
}The noinline attribute is required here. Without it, Clang and GCC may inline the helper function, which defeats the optimization by reintroducing the prologue.uint64_t rdtsc_start() {
uint32_t lo; uint32_t hi;
__asm__ __volatile__(
"lfence\n\t"
"rdtsc"
: "=a"(lo), "=d"(hi)
:
: "memory");
return ((uint64_t)hi << 32) | lo;
}
uint64_t rdtsc_end() {
uint32_t lo; uint32_t hi;
__asm__ __volatile__(
"rdtscp\n\t"
"lfence"
: "=a"(lo), "=d"(hi)
:
: "rcx", "memory");
return ((uint64_t)hi << 32) | lo;
}
Reference:
What do you mean by "Cache Friendly"? - Björn Fahller
What Every Programmer Should Know About Memory
Includes
Excludes
All models are wrong, but some are useful
Reference:
https://github.com/crill-dev/crill
Low latency <-> High throughput
Server side is in the middle.
Rarely have to worry about this breaking your program because the system handles it automatically:
function multiversioning:
#include <iostream>
// 1. Version optimized for modern CPUs with AVX2
__attribute__((target("avx2")))
void process_data() {
std::cout << "Running high-performance AVX2 vectorized version!\n";
// Fast vector math goes here
}
// 2. Version optimized for older SSE4.2 capable CPUs
__attribute__((target("sse4.2")))
void process_data() {
std::cout << "Running mid-tier SSE4.2 version!\n";
}
// 3. The mandatory default fallback version
__attribute__((target("default")))
void process_data() {
std::cout << "Running generic baseline version.\n";
}
int main() {
// You call it like a regular function.
// The resolution happens completely behind the scenes.
process_data();
return 0;
}
std::unique_ptr‹biquad_coefficients> storage;
std::atomic<biquad_coefficients*> coeffs;
void process(audio_buffer& buffer) {
auto* current_coeffs = coeffs.exchange(nullptr);
process_biquad(buffer, *current_coffs) ;
coeffs.store(current_coeffs) ;
}
void update_coeffs (biquad_coefficients new_coeffs) {
auto new_coeffs = std::make_unique‹biquad_coefficients>(new_coeffs);
for (auto* expected = storage.get();
!coeffs.compare_exchange_weak(expected, new_coeffs.get());
expected = storage.get() /* spin */;)
storage = std::move(new_coffs);
}
// Or just use crill lib
crill::spin_on_write_object<biquad_coefficients> coeffs;
void process (audio_buffer& buffer) {
auto read_ptr = coeffs.lock_read();
process_biquad (buffer, *read_ptr);
}
void update_coeffs(biquad_coefficients new_coeffs) {
coeffs.update(new_coeffs);
}
crill::defer_reclaim_object<biquad_coefficients> coeffs;
void process(audio_buffer& buffer) {
uto read_ptr = coeffs.Lock_read() ;
process_biquad (buffer, *read_ptr);
}
void update_coeffs (biquad_coefficients new_coeffs) {
coeffs.update(new_coeffs);
}
void timer_callback(){
coeffs.reclaim();
}std::array<frequency_spectrum, 2> slots;
std::atomic<int> idx = {0};
void process (audio_buffer& audio_in) {
auto spectrum = calculate_spectrum(audio_in);
int write_id = id.load();
slots [write_idx] = spectrum;
}
void update_spectrum() {
int read_id = idx.fetch_xor(1);
draw_spectrum(slots[read_idx]);
}
// Solve the ABA problem
std::array<frequency_spectrum, 2> slots;
std::atomic<int> idx = {0};
enum {
BIT_IDX = (1 << 0),
BIT_NEWDATA = (1 << 1),
BIT_BUSY = (1 << 2),
};
void process (audio_buffer& audio_in) {
auto spectrum = calculate_spectrum(audio_in);
int write_idx = idx.fetch_or(BIT_BUSY) & BIT_IDX;
slots [write_idx] = spectrum;
idx.store ((write_idx & BIT_IDX) | BIT_NEWDATA);
}
// ...
std::atomic<std::size_t> seq = 0;
// single thread doing the write.
void store(T t) noexcept {
std::size_t old_seq = seq.fetch_add(1);
/*
// Faster
auto old_seq = seq.load(std::memory_order_relaxed);
seq.store(old_seq + 1, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_release);
*/
// write data...
seq.store(old_seq + 2);
}
bool try_load(T& t) const noexcept {
std::size_t seq1 = seq.load(std::memory_order_acquired);
if (seq1 % 2 != 0) return false;
// read data...
/*
Use Byte-wise atomic memcpy to read the data;
or chunk it; DO NOT USE memcpy
for (size_t i = 0; i < count; ++i) {
reinterpret_cast<char*>(dest)[i] =
atomic_ref<char>(reinterpret_cast<char*>(source)[i]).load(memory_order_relaxed);
}
atomic_thread_fence(order);
*/
std::atomic_thread_fence(std::memory_order_acquired);
std: :size_t seq2 = seq.load(std::memory_order_relaxed);
return seq1 == seq2;
}
Reference:
https://blog.regehr.org/archives/2485
The 4 RACI Roles