mirror of
https://github.com/openmv/openmv.git
synced 2025-09-26 23:09:13 +08:00
common: Introduce profiling support.
This adds a simple code profiler that leverages both SysTick and the Performance Monitoring Unit (PMU) available on Armv8.1-M cores. SysTick provides a simple high-resolution timer source, while the PMU offers a cycle counter and multiple configurable event counters that can track low-level performance metrics such as cache misses, pipeline stalls, etc... See the Armv8.1-M Performance Monitoring User Guide for additional details. Function-level instrumentation is enabled using -finstrument-functions on select files to automatically record statistics on entry/exit. Signed-off-by: iabdalkader <i.abdalkader@gmail.com>
This commit is contained in:
parent
cf7b84d682
commit
e5cdccc72e
18
Makefile
18
Makefile
@ -107,10 +107,24 @@ CFLAGS += -DFB_ALLOC_STATS
|
||||
endif
|
||||
|
||||
# Enable timing for some functions.
|
||||
ifeq ($(PROFILE), 1)
|
||||
CFLAGS += -DOMV_PROFILE_ENABLE=1
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(info ===================================)
|
||||
$(info ======= Profiling Enabled =======)
|
||||
$(info ===================================)
|
||||
|
||||
# Enable profiling in IRQ context (default: enabled)
|
||||
PROFILE_IRQ ?= 0
|
||||
|
||||
# Default profiler hash table size (must be power of 2)
|
||||
ifeq ($(PROFILE_HASH),)
|
||||
PROFILE_HASH=256
|
||||
endif
|
||||
|
||||
CFLAGS += -DOMV_PROFILER_ENABLE=1
|
||||
CFLAGS += -DOMV_PROFILER_HASH_SIZE=$(PROFILE_HASH)
|
||||
CFLAGS += -DOMV_PROFILER_IRQ_ENABLE=$(PROFILE_IRQ)
|
||||
CFLAGS += -finstrument-functions-exclude-file-list=lib/cmsis,lib/stm32,/lib/mimxrt,lib/alif,simd.h
|
||||
endif
|
||||
|
||||
# Include OpenMV board config first to set the port.
|
||||
include $(OMV_BOARD_CONFIG_DIR)/omv_boardconfig.mk
|
||||
|
@ -39,6 +39,7 @@ COMMON_SRC_C += \
|
||||
usbdbg.c \
|
||||
vospi.c \
|
||||
queue.c \
|
||||
omv_profiler.c \
|
||||
|
||||
CFLAGS += -I$(TOP_DIR)/common
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/common/, $(COMMON_SRC_C:.c=.o))
|
||||
|
@ -31,6 +31,7 @@
|
||||
#define OMV_ATTR_ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define OMV_ATTR_OPTIMIZE(o) __attribute__((optimize(o)))
|
||||
#define OMV_ATTR_SEC_ALIGN(x, s, a) x __attribute__((section(s), aligned(a)))
|
||||
#define OMV_ATTR_NO_INSTRUMENT __attribute__((no_instrument_function))
|
||||
#define OMV_DEBUG_BREAKPOINT() __asm__ volatile ("BKPT")
|
||||
|
||||
#ifndef __DCACHE_PRESENT
|
||||
@ -76,17 +77,11 @@
|
||||
|
||||
#define OMV_ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||
|
||||
#if OMV_PROFILE_ENABLE
|
||||
#include <stdio.h>
|
||||
#include "py/mphal.h"
|
||||
#define OMV_CONCATENATE_DETAIL(x, y) x##y
|
||||
#define OMV_CONCATENATE(x, y) OMV_CONCATENATE_DETAIL(x, y)
|
||||
#define OMV_PROFILE_START(F) mp_uint_t OMV_CONCATENATE(_ticks_start_, F) = mp_hal_ticks_us()
|
||||
#define OMV_PROFILE_PRINT(F) printf("%s:%s %u us\n", __FUNCTION__, #F, mp_hal_ticks_us() - OMV_CONCATENATE(_ticks_start_, F))
|
||||
#else
|
||||
#define OMV_PROFILE_START(F)
|
||||
#define OMV_PROFILE_PRINT(F)
|
||||
#endif
|
||||
// Token concatenation macros
|
||||
// OMV_CONCAT expands its arguments before pasting, while
|
||||
// OMV_CONCAT_HELPER performs the raw token pasting (x##y).
|
||||
#define OMV_CONCAT_HELPER(x, y) x##y
|
||||
#define OMV_CONCAT_STR(x, y) OMV_CONCAT_HELPER(x, y)
|
||||
|
||||
// Returns a pointer to the containing structure
|
||||
// ptr: Pointer to the member within the structure
|
||||
|
365
common/omv_profiler.c
Normal file
365
common/omv_profiler.c
Normal file
@ -0,0 +1,365 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2025 OpenMV, LLC.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* OpenMV code profiler.
|
||||
*/
|
||||
#if OMV_PROFILER_ENABLE
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "py/mphal.h"
|
||||
#include "omv_profiler.h"
|
||||
|
||||
#if __PMU_PRESENT
|
||||
#define OMV_GET_CYCLE_COUNT() ARM_PMU_Get_CCNTR()
|
||||
#define OMV_GET_EVENT_COUNT(i) ARM_PMU_Get_EVCNTR(i)
|
||||
#else
|
||||
#define OMV_GET_CYCLE_COUNT() 0
|
||||
#define OMV_GET_EVENT_COUNT(i) 0
|
||||
#endif // __PMU_PRESENT
|
||||
#define OMV_GET_TICKS_COUNT() ticks_us_monotonic()
|
||||
|
||||
// Call stack entry for tracking nested calls
|
||||
typedef struct {
|
||||
void *func_addr; // Function address
|
||||
void *call_addr; // Call site address
|
||||
|
||||
uint32_t enter_ticks; // Timestamp on entry
|
||||
uint32_t enter_cycles; // Cycle count on entry
|
||||
|
||||
uint64_t child_ticks; // Total child execution time
|
||||
uint64_t child_cycles; // Total child cycle count
|
||||
|
||||
#if __PMU_PRESENT
|
||||
uint16_t enter_events[__PMU_NUM_EVENTCNT]; // PMU events on entry
|
||||
uint64_t child_events[__PMU_NUM_EVENTCNT]; // Total child PMU events
|
||||
#endif
|
||||
} omv_stack_entry_t;
|
||||
|
||||
typedef struct {
|
||||
bool initialized; // Profiler state.
|
||||
mutex_t mutex; // Protects profile data
|
||||
bool reset_pending; // Reset requested flag
|
||||
omv_profiler_mode_t mode; // Inclusive/exclusive mode
|
||||
|
||||
uint32_t collisions; // Hash table collision count
|
||||
omv_profiler_data_t *hash[OMV_PROFILER_HASH_SIZE]; // Hash table buckets
|
||||
|
||||
uint32_t pool_index; // Next free pool entry
|
||||
omv_profiler_data_t pool[OMV_PROFILER_HASH_SIZE]; // Entry pool
|
||||
|
||||
int32_t stack_top; // Current stack position
|
||||
uint32_t stack_depth; // Max stack depth reached
|
||||
omv_stack_entry_t stack[OMV_PROFILER_STACK_DEPTH]; // Call stack
|
||||
} omv_profiler_state_t;
|
||||
|
||||
static omv_profiler_state_t profiler;
|
||||
|
||||
#define profiler_stack_top(x) profiler.stack[profiler.stack_top].x
|
||||
#define profiler_stack_prv(x) profiler.stack[profiler.stack_top - 1].x
|
||||
|
||||
static OMV_ATTR_NO_INSTRUMENT mp_uint_t ticks_us_monotonic(void) {
|
||||
static mp_uint_t last_timestamp = 0;
|
||||
mp_uint_t current = mp_hal_ticks_us();
|
||||
|
||||
// Ensure monotonic behavior
|
||||
if (current > last_timestamp) {
|
||||
last_timestamp = current;
|
||||
}
|
||||
|
||||
return last_timestamp;
|
||||
}
|
||||
|
||||
static inline uint32_t OMV_ATTR_NO_INSTRUMENT hash_address(void *addr) {
|
||||
uint32_t x = (uint32_t) addr >> 2; // drop thumb bit + alignment bits
|
||||
// mix (Murmur finalizer style)
|
||||
x ^= x >> 16;
|
||||
x *= 0x7feb352dU;
|
||||
x ^= x >> 15;
|
||||
x *= 0x846ca68bU;
|
||||
x ^= x >> 16;
|
||||
return x & (OMV_PROFILER_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
// Initialize profiling system
|
||||
void omv_profiler_init(void) {
|
||||
if (!profiler.initialized) {
|
||||
// Reset state
|
||||
memset(&profiler, 0, sizeof(profiler));
|
||||
#if __PMU_PRESENT
|
||||
// Enable PMU
|
||||
ARM_PMU_Enable();
|
||||
// Disable all event counters
|
||||
ARM_PMU_CNTR_Disable(((1U << __PMU_NUM_EVENTCNT) - 1));
|
||||
// Enable cycles counter.
|
||||
ARM_PMU_CNTR_Enable(PMU_CNTENSET_CCNTR_ENABLE_Msk);
|
||||
// Enable Trace
|
||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize mutex
|
||||
mutex_init0(&profiler.mutex);
|
||||
profiler.initialized = true;
|
||||
}
|
||||
|
||||
void omv_profiler_reset(void) {
|
||||
if (!mutex_try_lock(&profiler.mutex, MUTEX_TID_OMV)) {
|
||||
// Set a pending reset if the data is locked.
|
||||
profiler.reset_pending = true;
|
||||
} else {
|
||||
// Reset state
|
||||
profiler.pool_index = 0;
|
||||
profiler.stack_top = -1;
|
||||
profiler.stack_depth = 0;
|
||||
profiler.collisions = 0;
|
||||
profiler.reset_pending = false;
|
||||
|
||||
// Clear hash table
|
||||
memset(profiler.hash, 0, sizeof(profiler.hash));
|
||||
memset(profiler.pool, 0, sizeof(profiler.pool));
|
||||
memset(profiler.stack, 0, sizeof(profiler.stack));
|
||||
|
||||
mutex_unlock(&profiler.mutex, MUTEX_TID_OMV);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_t *omv_profiler_lock(void) {
|
||||
return &profiler.mutex;
|
||||
}
|
||||
|
||||
void *omv_profiler_get_data(size_t *count) {
|
||||
*count = profiler.pool_index;
|
||||
return profiler.pool;
|
||||
}
|
||||
|
||||
void omv_profiler_set_mode(uint32_t mode) {
|
||||
profiler.mode = mode;
|
||||
profiler.reset_pending = true;
|
||||
}
|
||||
|
||||
void omv_profiler_set_event(uint32_t num, uint32_t type) {
|
||||
#if __PMU_PRESENT
|
||||
if (num < __PMU_NUM_EVENTCNT) {
|
||||
ARM_PMU_Disable();
|
||||
|
||||
// Configure and enable event counter.
|
||||
ARM_PMU_Set_EVTYPER(num, type);
|
||||
ARM_PMU_CNTR_Enable(1 << num);
|
||||
|
||||
// Reset all event counters
|
||||
ARM_PMU_EVCNTR_ALL_Reset();
|
||||
|
||||
// Enable PMU
|
||||
ARM_PMU_Enable();
|
||||
}
|
||||
#endif
|
||||
profiler.reset_pending = true;
|
||||
}
|
||||
|
||||
static omv_profiler_data_t *omv_profiler_get_entry(void *func_addr) {
|
||||
uint32_t hash = hash_address(func_addr);
|
||||
omv_profiler_data_t *entry = profiler.hash[hash];
|
||||
|
||||
// Search existing entries in collision chain
|
||||
while (entry != NULL) {
|
||||
if (entry->func_addr == func_addr) {
|
||||
return entry;
|
||||
}
|
||||
entry = entry->next;
|
||||
}
|
||||
|
||||
// Create new entry if not found
|
||||
if (profiler.pool_index >= OMV_PROFILER_HASH_SIZE) {
|
||||
// Pool exhausted
|
||||
return NULL;
|
||||
}
|
||||
|
||||
entry = &profiler.pool[profiler.pool_index++];
|
||||
|
||||
memset(entry, 0, sizeof(omv_profiler_data_t));
|
||||
entry->min_ticks = UINT32_MAX;
|
||||
|
||||
// Insert at head of collision chain
|
||||
if (profiler.hash[hash] != NULL) {
|
||||
profiler.collisions++;
|
||||
}
|
||||
|
||||
entry->next = profiler.hash[hash];
|
||||
profiler.hash[hash] = entry;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
void OMV_ATTR_NO_INSTRUMENT __cyg_profile_func_enter(void *func_addr, void *call_addr) {
|
||||
uint32_t enter_ticks = OMV_GET_TICKS_COUNT();
|
||||
uint32_t enter_cycles = OMV_GET_CYCLE_COUNT();
|
||||
|
||||
if (func_addr == NULL || call_addr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip profiling if called from IRQ context
|
||||
#if !OMV_PROFILER_IRQ_ENABLE
|
||||
if ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restart profiler if needed
|
||||
if (profiler.reset_pending) {
|
||||
return omv_profiler_reset();
|
||||
}
|
||||
|
||||
// Update call stack
|
||||
profiler.stack_top++;
|
||||
if (profiler.stack_top >= OMV_PROFILER_STACK_DEPTH) {
|
||||
profiler.stack_top = OMV_PROFILER_STACK_DEPTH - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (profiler.stack_top > (int32_t)profiler.stack_depth) {
|
||||
profiler.stack_depth = profiler.stack_top;
|
||||
}
|
||||
|
||||
profiler_stack_top(func_addr) = func_addr;
|
||||
profiler_stack_top(call_addr) = call_addr;
|
||||
|
||||
profiler_stack_top(child_ticks) = 0;
|
||||
profiler_stack_top(enter_ticks) = enter_ticks;
|
||||
|
||||
profiler_stack_top(child_cycles) = 0;
|
||||
profiler_stack_top(enter_cycles) = enter_cycles;
|
||||
|
||||
#if __PMU_PRESENT
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
profiler_stack_top(child_events[i]) = 0;
|
||||
profiler_stack_top(enter_events[i]) = OMV_GET_EVENT_COUNT(i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void OMV_ATTR_NO_INSTRUMENT __cyg_profile_func_exit(void *func_addr, void *call_addr) {
|
||||
uint32_t exit_ticks = OMV_GET_TICKS_COUNT();
|
||||
uint32_t exit_cycles = OMV_GET_CYCLE_COUNT();
|
||||
|
||||
if (func_addr == NULL || call_addr == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip profiling if called from IRQ context
|
||||
#if !OMV_PROFILER_IRQ_ENABLE
|
||||
if ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk)) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Restart profiler if needed
|
||||
if (profiler.reset_pending) {
|
||||
return omv_profiler_reset();
|
||||
}
|
||||
|
||||
// Update events
|
||||
#if __PMU_PRESENT
|
||||
uint16_t exit_events[__PMU_NUM_EVENTCNT];
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
exit_events[i] = OMV_GET_EVENT_COUNT(i);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (profiler.stack_top < 0 || profiler.stack_top >= OMV_PROFILER_STACK_DEPTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Function address should match the stack's top.
|
||||
if (func_addr != profiler_stack_top(func_addr) ||
|
||||
call_addr != profiler_stack_top(call_addr)) {
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
// Protect profile data update via mutex.
|
||||
if (!mutex_try_lock(&profiler.mutex, MUTEX_TID_OMV)) {
|
||||
goto exit_cleanup;
|
||||
}
|
||||
|
||||
// Calculate inclusive ticks and cycles
|
||||
uint32_t incl_ticks = exit_ticks - profiler_stack_top(enter_ticks);
|
||||
uint32_t incl_cycles = exit_cycles - profiler_stack_top(enter_cycles);
|
||||
|
||||
// Calculate exclusive ticks and cycles
|
||||
uint32_t excl_ticks = incl_ticks - profiler_stack_top(child_ticks);
|
||||
uint32_t excl_cycles = incl_cycles - profiler_stack_top(child_cycles);
|
||||
|
||||
// Check if this entry makes sense.
|
||||
if (incl_ticks < excl_ticks || incl_cycles < excl_cycles) {
|
||||
goto mutex_unlock;
|
||||
}
|
||||
|
||||
// Get or create a new entry
|
||||
omv_profiler_data_t *entry = omv_profiler_get_entry(func_addr);
|
||||
if (entry == NULL) {
|
||||
goto mutex_unlock;
|
||||
}
|
||||
|
||||
entry->call_count++;
|
||||
entry->func_addr = func_addr;
|
||||
entry->call_addr = call_addr;
|
||||
|
||||
entry->total_ticks += excl_ticks;
|
||||
entry->total_cycles += excl_cycles;
|
||||
|
||||
if (excl_ticks > entry->max_ticks) {
|
||||
entry->max_ticks = excl_ticks;
|
||||
}
|
||||
|
||||
if (excl_ticks < entry->min_ticks) {
|
||||
entry->min_ticks = excl_ticks;
|
||||
}
|
||||
|
||||
// In exclusive mode, updated the parent's child counters.
|
||||
if (profiler.stack_top && profiler.mode == OMV_PROFILER_EXCLUSIVE) {
|
||||
profiler_stack_prv(child_ticks) += incl_ticks;
|
||||
profiler_stack_prv(child_cycles) += incl_cycles;
|
||||
}
|
||||
|
||||
#if __PMU_PRESENT
|
||||
for (size_t i = 0; i < __PMU_NUM_EVENTCNT; i++) {
|
||||
uint16_t incl_events = exit_events[i] - profiler_stack_top(enter_events[i]);
|
||||
uint16_t excl_events = incl_events - profiler_stack_top(child_events[i]);
|
||||
|
||||
entry->total_events[i] += excl_events;
|
||||
|
||||
// In exclusive mode, updated the parent's child counters.
|
||||
if (profiler.stack_top && profiler.mode == OMV_PROFILER_EXCLUSIVE) {
|
||||
profiler_stack_prv(child_events[i]) += incl_events;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
mutex_unlock:
|
||||
mutex_unlock(&profiler.mutex, MUTEX_TID_OMV);
|
||||
exit_cleanup:
|
||||
profiler.stack_top--;
|
||||
}
|
||||
#endif // OMV_PROFILER_ENABLE
|
102
common/omv_profiler.h
Normal file
102
common/omv_profiler.h
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Copyright (C) 2025 OpenMV, LLC.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* OpenMV code profiler.
|
||||
*/
|
||||
#ifndef __OMV_PROFILER_H__
|
||||
#define __OMV_PROFILER_H__
|
||||
|
||||
#if OMV_PROFILER_ENABLE
|
||||
#include <stdio.h>
|
||||
#include "py/mphal.h"
|
||||
#if __PMU_PRESENT
|
||||
#include "pmu_armv8.h"
|
||||
#endif
|
||||
#ifndef __cplusplus
|
||||
#include "common/mutex.h"
|
||||
#endif
|
||||
#include "common/omv_common.h"
|
||||
|
||||
#ifndef __PMU_NUM_EVENTCNT
|
||||
#define __PMU_NUM_EVENTCNT 0
|
||||
#endif
|
||||
|
||||
// Must be a power of 2
|
||||
#ifndef OMV_PROFILER_HASH_SIZE
|
||||
#define OMV_PROFILER_HASH_SIZE 256
|
||||
#endif
|
||||
|
||||
#ifndef OMV_PROFILER_STACK_DEPTH
|
||||
#define OMV_PROFILER_STACK_DEPTH 32
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
OMV_PROFILER_INCLUSIVE = 0,
|
||||
OMV_PROFILER_EXCLUSIVE = 1,
|
||||
} omv_profiler_mode_t;
|
||||
|
||||
// Profile record structure
|
||||
typedef struct __attribute__((packed)) _prof_data {
|
||||
void *func_addr; // Function address
|
||||
void *call_addr; // Caller address
|
||||
uint32_t call_count; // Number of times called
|
||||
uint32_t min_ticks; // Minimum execution time
|
||||
uint32_t max_ticks; // Maximum execution time
|
||||
uint64_t total_ticks; // Total time in ticks
|
||||
uint64_t total_cycles; // Total CPU cycles
|
||||
#if __PMU_PRESENT
|
||||
uint64_t total_events[__PMU_NUM_EVENTCNT];
|
||||
#endif
|
||||
struct _prof_data *next; // Next in hash collision chain
|
||||
} omv_profiler_data_t;
|
||||
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_init(void);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_reset(void);
|
||||
OMV_ATTR_NO_INSTRUMENT void *omv_profiler_get_data(size_t *count);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_set_mode(uint32_t mode);
|
||||
OMV_ATTR_NO_INSTRUMENT void omv_profiler_set_event(uint32_t num, uint32_t type);
|
||||
#ifndef __cplusplus
|
||||
OMV_ATTR_NO_INSTRUMENT mutex_t *omv_profiler_lock(void);
|
||||
#endif
|
||||
|
||||
// Manual instrumentation macros
|
||||
#define OMV_PROFILER_ENTER(func) \
|
||||
do { \
|
||||
void *func_addr = (void*)(func); \
|
||||
void *call_addr = __builtin_return_address(0); \
|
||||
__cyg_profile_func_enter(func_addr, call_addr); \
|
||||
} while(0)
|
||||
|
||||
#define OMV_PROFILER_EXIT(func) \
|
||||
do { \
|
||||
void *func_addr = (void*)(func); \
|
||||
void *call_addr = __builtin_return_address(0); \
|
||||
__cyg_profile_func_exit(func_addr, call_addr); \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
// Disabled - empty macros
|
||||
#define OMV_PROFILER_ENTER(func) do {} while(0)
|
||||
#define OMV_PROFILER_EXIT(func) do {} while(0)
|
||||
#endif // OMV_PROFILER_ENABLE
|
||||
#endif // __OMV_PROFILER_H__
|
@ -44,6 +44,7 @@
|
||||
#include "imlib_config.h"
|
||||
#include "omv_boardconfig.h"
|
||||
#include "omv_common.h"
|
||||
#include "omv_profiler.h"
|
||||
#include "py/runtime.h"
|
||||
|
||||
// Enables 38 TensorFlow Lite operators.
|
||||
|
@ -85,4 +85,11 @@ ifeq ($(CLANG_ENABLE),1)
|
||||
OMV_CLANG_OBJ = $(BUILD)/lib/imlib/bayer.o
|
||||
endif
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/lib/imlib/%.o: override CFLAGS += -finstrument-functions
|
||||
# Clang does not support -finstrument-functions-exclude-file-list.
|
||||
$(OMV_CLANG_OBJ): override CFLAGS := $(filter-out -finstrument-functions-exclude-file-list=%,$(CFLAGS))
|
||||
endif
|
||||
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/imlib/, $(IMLIB_SRC_C:.c=.o))
|
||||
|
@ -78,6 +78,11 @@ $(BUILD)/lib/stai/libstai/ll_aton/%.o: override CFLAGS += \
|
||||
-Wno-double-promotion \
|
||||
$(STAI_CFLAGS) \
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/lib/stai/stai_backend.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/stai/libstai/include
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/stai/, $(STAI_SRC_C:.c=.o))
|
||||
endif
|
||||
|
@ -50,6 +50,5 @@ $(BUILD)/lib/tflm/tflm_backend.o: CXXFLAGS = \
|
||||
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/tflm/libtflm/include
|
||||
OMV_CFLAGS += -I$(TOP_DIR)/lib/tflm/libtflm/include/third_party/ethos_u_core_driver/include
|
||||
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/lib/tflm/, $(TFLM_SRC_CC:.cc=.o))
|
||||
endif
|
||||
|
@ -66,3 +66,10 @@ ifeq ($(DEBUG), 0)
|
||||
# Use a higher optimization level for user C modules.
|
||||
$(BUILD)/modules/%.o: override CFLAGS += $(USERMOD_OPT)
|
||||
endif
|
||||
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/modules/py_ml.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/modules/py_image.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/modules/ulab/%.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
||||
|
@ -27,3 +27,9 @@ CFLAGS += -I$(TOP_DIR)/ports/$(PORT)/modules
|
||||
|
||||
PORT_SRC_C = $(wildcard ports/$(PORT)/*.c)
|
||||
OMV_FIRM_OBJ += $(addprefix $(BUILD)/, $(PORT_SRC_C:.c=.o))
|
||||
|
||||
# Enable instrumentation.
|
||||
ifeq ($(PROFILE_ENABLE), 1)
|
||||
$(BUILD)/ports/alif/%.o: override CFLAGS += -finstrument-functions
|
||||
$(BUILD)/ports/stm32/%.o: override CFLAGS += -finstrument-functions
|
||||
endif
|
||||
|
Loading…
Reference in New Issue
Block a user