Skip to content

Commit 15a0068

Browse files
jbachorikclaude
andcommitted
Add likely/unlikely branch prediction hints to hot-path subsystems
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent abe43f3 commit 15a0068

10 files changed

Lines changed: 70 additions & 70 deletions

ddprof-lib/src/main/cpp/buffers.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Buffer {
4242
virtual int limit() const { return _limit; }
4343

4444
bool flushIfNeeded(FlushCallback callback, int limit = BUFFER_LIMIT) {
45-
if (_offset > limit) {
45+
if (unlikely(_offset > limit)) {
4646
if (callback(_data, _offset) == _offset) {
4747
reset();
4848
return true;

ddprof-lib/src/main/cpp/callTraceHashTable.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class LongHashTable {
4848

4949
static LongHashTable *allocate(LongHashTable *prev, u32 capacity, LinearAllocator* allocator) {
5050
void *memory = allocator->alloc(getSize(capacity));
51-
if (memory != nullptr) {
51+
if (likely(memory != nullptr)) {
5252
// Use placement new to invoke constructor in-place with parameters
5353
// LinearAllocator doesn't zero memory like OS::safeAlloc with anon mmap
5454
// so we need to explicitly clear the keys and values (should_clean = true)
@@ -174,7 +174,7 @@ CallTrace *CallTraceHashTable::storeCallTrace(int num_frames,
174174
const size_t total_size = header_size + num_frames * sizeof(ASGCT_CallFrame);
175175
void *memory = _allocator.alloc(total_size);
176176
CallTrace *buf = nullptr;
177-
if (memory != nullptr) {
177+
if (likely(memory != nullptr)) {
178178
// Use placement new to invoke constructor in-place
179179
buf = new (memory) CallTrace(truncated, num_frames, trace_id);
180180
// Do not use memcpy inside signal handler
@@ -199,7 +199,7 @@ CallTrace *CallTraceHashTable::findCallTrace(LongHashTable *table, u64 hash) {
199199
if (keys[slot] == 0) {
200200
return nullptr;
201201
}
202-
if (!probe.hasNext()) {
202+
if (unlikely(!probe.hasNext())) {
203203
break;
204204
}
205205
slot = probe.next();
@@ -213,7 +213,7 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
213213
u64 hash = calcHash(num_frames, frames, truncated);
214214

215215
LongHashTable *table = _table;
216-
if (table == nullptr) {
216+
if (unlikely(table == nullptr)) {
217217
// Table allocation failed or was cleared - drop sample
218218
Counters::increment(CALLTRACE_STORAGE_DROPPED);
219219
return CallTraceStorage::DROPPED_TRACE_ID;
@@ -230,7 +230,7 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
230230
CallTrace* current_trace = table->values()[slot].acquireTrace();
231231

232232
// If another thread is preparing this slot, wait for completion
233-
if (current_trace == CallTraceSample::PREPARING) {
233+
if (unlikely(current_trace == CallTraceSample::PREPARING)) {
234234
// Wait for the preparing thread to complete, with timeout
235235
int wait_cycles = 0;
236236
const int MAX_WAIT_CYCLES = 1000; // ~1000 cycles should be enough for allocation
@@ -252,13 +252,13 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
252252
} while (current_trace == CallTraceSample::PREPARING && wait_cycles < MAX_WAIT_CYCLES);
253253

254254
// If still preparing after timeout, something is wrong - continue search
255-
if (current_trace == CallTraceSample::PREPARING) {
255+
if (unlikely(current_trace == CallTraceSample::PREPARING)) {
256256
continue;
257257
}
258258
}
259259

260260
// Check final state after waiting
261-
if (current_trace != nullptr && current_trace != CallTraceSample::PREPARING) {
261+
if (likely(current_trace != nullptr && current_trace != CallTraceSample::PREPARING)) {
262262
// Trace is ready, use it
263263
return current_trace->trace_id;
264264
} else {
@@ -279,7 +279,7 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
279279
}
280280

281281
// Mark the slot as being prepared so other threads know to wait
282-
if (!table->values()[slot].markPreparing()) {
282+
if (unlikely(!table->values()[slot].markPreparing())) {
283283
// Failed to mark as preparing (shouldn't happen), clear key with full barrier and retry
284284
__atomic_thread_fence(__ATOMIC_SEQ_CST);
285285
__atomic_store_n(&keys[slot], 0, __ATOMIC_RELEASE);
@@ -293,10 +293,10 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
293293
probe.updateCapacity(new_size);
294294

295295
// EXPANSION LOGIC: Check if 75% capacity reached after incrementing size
296-
if (new_size == capacity * 3 / 4) {
296+
if (unlikely(new_size == capacity * 3 / 4)) {
297297
// Allocate new table with double capacity using LinearAllocator
298298
LongHashTable* new_table = LongHashTable::allocate(table, capacity * 2, &_allocator);
299-
if (new_table != nullptr) {
299+
if (likely(new_table != nullptr)) {
300300
// Atomic table swap - only one thread succeeds
301301
__atomic_compare_exchange_n(&_table, &table, new_table, false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
302302
}
@@ -315,7 +315,7 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
315315
u64 instance_id = _instance_id.load(std::memory_order_acquire);
316316
u64 trace_id = (instance_id << 32) | slot;
317317
trace = storeCallTrace(num_frames, frames, truncated, trace_id);
318-
if (trace == nullptr) {
318+
if (unlikely(trace == nullptr)) {
319319
// Allocation failure - reset trace first, then clear key
320320
table->values()[slot].setTrace(nullptr);
321321
__atomic_thread_fence(__ATOMIC_SEQ_CST);
@@ -330,7 +330,7 @@ u64 CallTraceHashTable::put(int num_frames, ASGCT_CallFrame *frames,
330330
return trace->trace_id;
331331
}
332332

333-
if (!probe.hasNext()) {
333+
if (unlikely(!probe.hasNext())) {
334334
// Table overflow - very unlikely with expansion logic
335335
atomicIncRelaxed(_overflow);
336336
return OVERFLOW_TRACE_ID;

ddprof-lib/src/main/cpp/callTraceStorage.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ u64 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, bool truncate
345345
CallTraceHashTable* active = const_cast<CallTraceHashTable*>(__atomic_load_n(&_active_storage, __ATOMIC_ACQUIRE));
346346

347347
// Safety check - if null, system is shutting down
348-
if (active == nullptr) {
348+
if (unlikely(active == nullptr)) {
349349
Counters::increment(CALLTRACE_STORAGE_DROPPED);
350350
return DROPPED_TRACE_ID;
351351
}
@@ -355,7 +355,7 @@ u64 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, bool truncate
355355
RefCountGuard guard(active);
356356

357357
// Check if refcount guard allocation failed (slot exhaustion)
358-
if (!guard.isActive()) {
358+
if (unlikely(!guard.isActive())) {
359359
// No refcount protection available - return dropped trace ID
360360
Counters::increment(CALLTRACE_STORAGE_DROPPED);
361361
return DROPPED_TRACE_ID;
@@ -373,7 +373,7 @@ u64 CallTraceStorage::put(int num_frames, ASGCT_CallFrame* frames, bool truncate
373373
//
374374
// Memory ordering: ACQUIRE load ensures we see scanner's ACQ_REL exchange to nullptr
375375
CallTraceHashTable* original_active = const_cast<CallTraceHashTable*>(__atomic_load_n(&_active_storage, __ATOMIC_ACQUIRE));
376-
if (original_active != active || original_active == nullptr) {
376+
if (unlikely(original_active != active || original_active == nullptr)) {
377377
// Storage was swapped or nullified during guard construction
378378
// SAFE: We detected the race, drop this trace, never use the table pointer
379379
Counters::increment(CALLTRACE_STORAGE_DROPPED);

ddprof-lib/src/main/cpp/codeCache.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ char *NativeFunc::create(const char *name, short lib_index) {
2525
void NativeFunc::destroy(char *name) { free(from(name)); }
2626

2727
char NativeFunc::read_mark(const char* name) {
28-
if (name == nullptr) {
28+
if (unlikely(name == nullptr)) {
2929
return 0;
3030
}
3131
NativeFunc* func = from(name);
32-
if (!is_aligned(func, sizeof(func))) {
32+
if (unlikely(!is_aligned(func, sizeof(func)))) {
3333
return 0;
3434
}
3535
// Use SafeAccess to read the mark field in signal handler context
@@ -394,7 +394,7 @@ void CodeCache::setDwarfTable(FrameDesc *table, int length) {
394394
}
395395

396396
FrameDesc CodeCache::findFrameDesc(const void *pc) {
397-
if (_dwarf_table == NULL || _dwarf_table_length == 0) {
397+
if (unlikely(_dwarf_table == NULL || _dwarf_table_length == 0)) {
398398
// No DWARF data available - use default frame pointer unwinding
399399
// This handles OpenJ9 and other VMs that don't provide DWARF info
400400
return FrameDesc::default_frame;
@@ -415,7 +415,7 @@ FrameDesc CodeCache::findFrameDesc(const void *pc) {
415415
}
416416
}
417417

418-
if (low > 0) {
418+
if (likely(low > 0)) {
419419
return _dwarf_table[low - 1];
420420
} else if (target_loc - _plt_offset < _plt_size) {
421421
return FrameDesc::empty_frame;

ddprof-lib/src/main/cpp/context.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ Context& Contexts::initializeContextTls() {
4040

4141
Context& Contexts::get() {
4242
ProfiledThread* thrd = ProfiledThread::currentSignalSafe();
43-
if (thrd == nullptr || !thrd->isContextTlsInitialized()) {
43+
if (unlikely(thrd == nullptr || !thrd->isContextTlsInitialized())) {
4444
return DD_EMPTY_CONTEXT;
4545
}
4646
// Return via stored pointer - never access context_tls_v1 from signal handler

ddprof-lib/src/main/cpp/flightRecorder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ void Recording::flush(Buffer *buf) {
794794
}
795795

796796
void Recording::flushIfNeeded(Buffer *buf, int limit) {
797-
if (buf->offset() >= limit) {
797+
if (unlikely(buf->offset() >= limit)) {
798798
flush(buf);
799799
}
800800
}

ddprof-lib/src/main/cpp/linearAllocator.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ void *LinearAllocator::alloc(size_t size) {
138138

139139
// CRITICAL FIX: After detachChunks() fails, _tail may be nullptr.
140140
// We must handle this gracefully to prevent crash.
141-
if (chunk == nullptr) {
141+
if (unlikely(chunk == nullptr)) {
142142
return nullptr;
143143
}
144144

@@ -157,7 +157,7 @@ void *LinearAllocator::alloc(size_t size) {
157157
ASAN_UNPOISON_MEMORY_REGION(allocated_ptr, size);
158158
#endif
159159

160-
if (_chunk_size / 2 - offs < size) {
160+
if (unlikely(_chunk_size / 2 - offs < size)) {
161161
// Stepped over a middle of the chunk - it's time to prepare a new one
162162
reserveChunk(chunk);
163163
}
@@ -171,7 +171,7 @@ void *LinearAllocator::alloc(size_t size) {
171171

172172
Chunk *LinearAllocator::allocateChunk(Chunk *current) {
173173
Chunk *chunk = (Chunk *)OS::safeAlloc(_chunk_size);
174-
if (chunk != NULL) {
174+
if (likely(chunk != NULL)) {
175175
chunk->prev = current;
176176
chunk->offs = sizeof(Chunk);
177177

@@ -208,18 +208,18 @@ void LinearAllocator::reserveChunk(Chunk *current) {
208208
Chunk *LinearAllocator::getNextChunk(Chunk *current) {
209209
Chunk *reserve = _reserve;
210210

211-
if (reserve == current) {
211+
if (unlikely(reserve == current)) {
212212
// Unlikely case: no reserve yet.
213213
// It's probably being allocated right now, so let's compete
214214
reserve = allocateChunk(current);
215-
if (reserve == NULL) {
215+
if (unlikely(reserve == NULL)) {
216216
// Not enough memory
217217
return NULL;
218218
}
219219

220220
Chunk *prev_reserve =
221221
__sync_val_compare_and_swap(&_reserve, current, reserve);
222-
if (prev_reserve != current) {
222+
if (unlikely(prev_reserve != current)) {
223223
freeChunk(reserve);
224224
reserve = prev_reserve;
225225
}

ddprof-lib/src/main/cpp/profiler.cpp

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -764,9 +764,9 @@ u64 Profiler::recordJVMTISample(u64 counter, int tid, jthread thread, jint event
764764
atomicIncRelaxed(_total_samples);
765765

766766
u32 lock_index = getLockIndex(tid);
767-
if (!_locks[lock_index].tryLock() &&
767+
if (unlikely(!_locks[lock_index].tryLock() &&
768768
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
769-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
769+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
770770
// Too many concurrent signals already
771771
atomicIncRelaxed(_failures[-ticks_skipped]);
772772

@@ -811,9 +811,9 @@ void Profiler::recordDeferredSample(int tid, u64 call_trace_id, jint event_type,
811811
atomicIncRelaxed(_total_samples);
812812

813813
u32 lock_index = getLockIndex(tid);
814-
if (!_locks[lock_index].tryLock() &&
814+
if (unlikely(!_locks[lock_index].tryLock() &&
815815
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
816-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
816+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
817817
// Too many concurrent signals already
818818
atomicIncRelaxed(_failures[-ticks_skipped]);
819819
return;
@@ -829,9 +829,9 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
829829
atomicIncRelaxed(_total_samples);
830830

831831
u32 lock_index = getLockIndex(tid);
832-
if (!_locks[lock_index].tryLock() &&
832+
if (unlikely(!_locks[lock_index].tryLock() &&
833833
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
834-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
834+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
835835
// Too many concurrent signals already
836836
atomicIncRelaxed(_failures[-ticks_skipped]);
837837

@@ -849,7 +849,7 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
849849
// record a null stacktrace we can skip the unwind if we've got a
850850
// call_trace_id determined to be reusable at a higher level
851851

852-
if (!_omit_stacktraces && call_trace_id == 0) {
852+
if (likely(!_omit_stacktraces && call_trace_id == 0)) {
853853
u64 startTime = TSC::ticks();
854854
ASGCT_CallFrame *frames = _calltrace_buffer[lock_index]->_asgct_frames;
855855

@@ -859,7 +859,7 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
859859
ASGCT_CallFrame *native_stop = frames + num_frames;
860860
num_frames += getNativeTrace(ucontext, native_stop, event_type, tid,
861861
&java_ctx, &truncated, lock_index);
862-
if (num_frames < _max_stack_depth) {
862+
if (likely(num_frames < _max_stack_depth)) {
863863
int max_remaining = _max_stack_depth - num_frames;
864864
if (_features.mixed) {
865865
int vm_start = num_frames;
@@ -887,14 +887,14 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
887887
}
888888
}
889889
}
890-
if (num_frames == 0) {
890+
if (unlikely(num_frames == 0)) {
891891
num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_Java_frame");
892892
}
893893

894894
call_trace_id =
895895
_call_trace_storage.put(num_frames, frames, truncated, counter);
896896
ProfiledThread *thread = ProfiledThread::currentSignalSafe();
897-
if (thread != nullptr) {
897+
if (likely(thread != nullptr)) {
898898
thread->recordCallTraceId(call_trace_id);
899899
}
900900
u64 duration = TSC::ticks() - startTime;
@@ -909,9 +909,9 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
909909

910910
void Profiler::recordWallClockEpoch(int tid, WallClockEpochEvent *event) {
911911
u32 lock_index = getLockIndex(tid);
912-
if (!_locks[lock_index].tryLock() &&
912+
if (unlikely(!_locks[lock_index].tryLock() &&
913913
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
914-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
914+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
915915
return;
916916
}
917917
_jfr.wallClockEpoch(lock_index, event);
@@ -920,9 +920,9 @@ void Profiler::recordWallClockEpoch(int tid, WallClockEpochEvent *event) {
920920

921921
void Profiler::recordTraceRoot(int tid, TraceRootEvent *event) {
922922
u32 lock_index = getLockIndex(tid);
923-
if (!_locks[lock_index].tryLock() &&
923+
if (unlikely(!_locks[lock_index].tryLock() &&
924924
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
925-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
925+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
926926
return;
927927
}
928928
_jfr.recordTraceRoot(lock_index, tid, event);
@@ -931,9 +931,9 @@ void Profiler::recordTraceRoot(int tid, TraceRootEvent *event) {
931931

932932
void Profiler::recordQueueTime(int tid, QueueTimeEvent *event) {
933933
u32 lock_index = getLockIndex(tid);
934-
if (!_locks[lock_index].tryLock() &&
934+
if (unlikely(!_locks[lock_index].tryLock() &&
935935
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
936-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
936+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
937937
return;
938938
}
939939
_jfr.recordQueueTime(lock_index, tid, event);
@@ -957,9 +957,9 @@ void Profiler::recordExternalSample(u64 weight, int tid, int num_frames,
957957

958958
u64 call_trace_id =
959959
_call_trace_storage.put(num_frames, extended_frames, truncated, weight);
960-
if (!_locks[lock_index].tryLock() &&
960+
if (unlikely(!_locks[lock_index].tryLock() &&
961961
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
962-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
962+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
963963
// Too many concurrent signals already
964964
atomicIncRelaxed(_failures[-ticks_skipped]);
965965
return;
@@ -982,9 +982,9 @@ void Profiler::writeDatadogProfilerSetting(int tid, int length,
982982
const char *name, const char *value,
983983
const char *unit) {
984984
u32 lock_index = getLockIndex(tid);
985-
if (!_locks[lock_index].tryLock() &&
985+
if (unlikely(!_locks[lock_index].tryLock() &&
986986
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
987-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
987+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
988988
return;
989989
}
990990
_jfr.recordDatadogSetting(lock_index, length, name, value, unit);
@@ -997,9 +997,9 @@ void Profiler::writeHeapUsage(long value, bool live) {
997997
return;
998998
}
999999
u32 lock_index = getLockIndex(tid);
1000-
if (!_locks[lock_index].tryLock() &&
1000+
if (unlikely(!_locks[lock_index].tryLock() &&
10011001
!_locks[lock_index = (lock_index + 1) % CONCURRENCY_LEVEL].tryLock() &&
1002-
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock()) {
1002+
!_locks[lock_index = (lock_index + 2) % CONCURRENCY_LEVEL].tryLock())) {
10031003
return;
10041004
}
10051005
_jfr.recordHeapUsage(lock_index, value, live);

0 commit comments

Comments
 (0)