review Tengine code
随便找一个测试的example看一下,init_tengine( )函数被用来初始化tengine。
来看一下init_tengine( )这个函数的构成,tengine_c_api.c中代码如下:
int DLLEXPORT init_tengine(void)
{
// if (enable_intern_allocator)
// enable_intern_allocator();
set_log_level(LOG_ERR);
if (enable_mem_stat)
enable_mem_stat();
int ret = 0;
ret = init_op_name_map();
if (0 != ret)
{
TLOG_ERR("init map of operator names failed: %d\n", ret);
return ret;
}
ret = init_op_registry();
if (0 != ret)
{
TLOG_ERR("register operators failed: %d\n", ret);
return ret;
}
ret = init_nn_dev_registry();
if (0 != ret)
{
TLOG_ERR("register device failed: %d\n", ret);
return ret;
}
ret = init_serializer_registry();
if (0 != ret)
{
TLOG_ERR("register serializer failed: %d\n", ret);
return ret;
}
ret = exec_module_init(0);
if (0 != ret)
{
TLOG_ERR("init exec module failed: %d\n", ret);
return ret;
}
return 0;
}
我们在这里逐句分析:
- set_log_level(LOG_ERR);这个函数有点类似linux内核中的操作,用于设置log的等级,这样在后面就可以选择性的打印log。
跟踪进这个函数:
void DLLEXPORT set_log_level(enum log_level level)
{
SET_LOG_LEVEL(level);
}
继续:
#define SET_LOG_LEVEL(level) \
do \
{ \
struct logger* logger = get_default_logger(); \
logger->set_log_level(logger, level); \
} while(0)
到这里我们看到了logger这个结构体,这个应该是整个Tengine负责日志管理的组件,查看一下logger的结构,tengine_log.h中定义如下:
struct logger
{
const char* prefix;
int log_level;
struct log_option option;
void (*output_func)(const char*);
void (*log)(struct logger*, int level, const char* fmt, ...);
void (*set_log_level)(struct logger*, int level);
void (*set_output_func)(struct logger*, void (*func)(const char*));
};
现在先回到上面定义的宏SET_LOG_LEVEL中logger被get_default_logger();函数初始化了。进入函数中查看:
struct logger* get_default_logger(void)
{
static int inited = 0;
static struct logger default_logger;
if (inited)
return &default_logger;
lock(&log_lock);
if (!inited)
{
inited = 1;
default_logger.prefix = NULL;
default_logger.log_level = DEFAULT_LOG_LEVEL;
default_logger.output_func = output_stderr;
default_logger.log = do_log;
default_logger.set_log_level = change_log_level;
default_logger.set_output_func = set_output_func;
default_logger.option.print_prefix = 0;
default_logger.option.print_time = 0;
default_logger.option.print_level = 0;
}
unlock(&log_lock);
return &default_logger;
}
这个函数用inited这个变量来控制防止重复初始化这个函数,这在设计模式中被称为”单例模式“,一般在这种全局日志组件中被使用。结合上面的logger结构体来看:
default_logger.prefix = NULL; 前缀为NULL
default_logger.log_level = DEFAULT_LOG_LEVEL; 日志等级为DEFAULT_LOG_LEVEL,跳进去查看可以看到日志一共"EMERG", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG"这几种等级。
-
default_logger.output_func = output_stderr; 这是一个函数指针,去查看这个具体的函数实现。
static void output_stderr(const char* msg) { fprintf(stderr, "%s", msg); }直接输出msg这个字符串到stderr标准错误流。
-
default_logger.log = do_log; 查看do_log函数
static void do_log(struct logger* logger, int level, const char* fmt, ...) { va_list ap; char msg[256]; int max_len = 256; int left = max_len; char* p = msg; int ret; if (logger->log_level < level || level > LOG_DEBUG) return; #ifndef CONFIG_ARCH_CORTEX_M if (logger->option.print_time) { time_t t = time(NULL); ret = strftime(p, left, "%Y-%m-%d %X ", localtime(&t)); left -= ret; p += ret; } #endif if (left <= 1) goto print; if (logger->option.print_level) { ret = snprintf(p, left, "%s ", map_table[level]); left -= ret; p += ret; } if (left <= 1) goto print; if (logger->option.print_prefix && logger->prefix) { ret = snprintf(p, left, "%s ", logger->prefix); left -= ret; p += ret; } if (left <= 1) goto print; va_start(ap, fmt); ret = vsnprintf(p, left, fmt, ap); va_end(ap); print: msg[max_len - 1] = 0x0; lock(&log_lock); logger->output_func(msg); unlock(&log_lock); }在这个函数下面我们发现了logger->output_func(msg);这个函数,这个是直接打印的。这个函数大家应该都能看懂,就是level小于设置level的不打印,CONFIG_ARCH_CORTEX_M这个先忽略,我们只看PC端的处理。
-
default_logger.set_log_level = change_log_level; 还是直接去看change_log_level这个函数
static void change_log_level(struct logger* logger, int level) { if (level < 0 || level > LOG_DEBUG) return; logger->log_level = level; }很简单,就是直接给log_level赋值。
-
default_logger.set_output_func = set_output_func; 还是直接去看set_output_func这个函数
static void set_output_func(struct logger* logger, void (*func)(const char*)) { logger->output_func = func; }该函数用于设置输出函数,默认的输出是stderr,即标准错误流,可以调用set_output_func这个函数去改变输出函数,比如将日志保存在文件中。
最后这个是打印日志的选项:
default_logger.option.print_prefix = 0;
default_logger.option.print_time = 0;
default_logger.option.print_level = 0;
到这里其实整个logger组件已经捋了一遍,在回头看看init_tengine( )这个函数set_log_level(LOG_ERR);设置日志等级为ERROR,继续查看init_tengine中的函数:
-
ret = init_op_name_map(); 跟踪进去看这个函数:
int init_op_name_map(void) { op_map_list = create_vector(sizeof(struct op_map_entry), NULL); if (op_map_list == NULL) return -1; return 0; }进入create_vector函数中:
struct vector* create_vector(int elem_size, void (*free_data)(void*)) { struct vector* v = ( struct vector* )sys_malloc(sizeof(struct vector)); if (v == NULL) return NULL; v->elem_num = 0; v->elem_size = elem_size; v->free_func = free_data; v->entry_size = elem_size + sizeof(struct vector_entry); v->entry_size = (v->entry_size + VECTOR_ALIGN_SIZE) & (~(VECTOR_ALIGN_SIZE - 1)); v->ahead_num = 8; v->space_num = v->ahead_num; v->real_mem = sys_malloc(v->entry_size * v->space_num + VECTOR_ALIGN_SIZE); v->mem = ( void* )((( long )v->real_mem) & (~(VECTOR_ALIGN_SIZE - 1))); for (int i = 0; i < v->space_num; i++) { struct vector_entry* e = get_vector_entry(v, i); e->valid = 0; } return v; }显然这块引入了一种数据结构vector,这是向量,是一种栈(filo):
struct vector { int elem_size; int elem_num; int entry_size; int space_num; int ahead_num; void * real_mem; void* mem; void (*free_func)(void*); };init_op_name_map这个函数实际上就是初始化一个用于装载op名字的向量,这块不用细说,继续往后看。
-
ret = init_op_registry(); 跟踪进去:
int init_op_registry(void) { op_list = create_vector(sizeof(struct op_method), NULL); if (op_list == NULL) return -1; return 0; }struct op_method { int op_type; int op_version; int (*init_op)(struct ir_op* op); void (*release_op)(struct ir_op* op); int (*access_param_entry)(void* param_mem, const char* entry_name, int entry_type, void* buf, int size, int set); };和上面的函数类似,这里也不做介绍,init_op_registry函数实际上就是初始化一个用于注册op的向量,继续向后看:
-
ret = init_nn_dev_registry(); 跟踪进去:
int init_nn_dev_registry(void) { dev_list = create_vector(sizeof(struct nn_device*), NULL); if (dev_list == NULL) { set_tengine_errno(ENOMEM); return -1; } return 0; }和上面一样,这块也是用来初始化一个向量,用于保存nn_device,来具体看看这个结构的内容:
struct nn_device { char* name; int (*init)(struct nn_device* dev); int (*prerun)(struct nn_device* dev, struct subgraph* subgraph, int num_thread, int cpu_affinity, int mode); int (*run)(struct nn_device* dev, struct subgraph* subgraph); int (*postrun)(struct nn_device* dev, struct subgraph* subgraph); int (*async_run)(struct nn_device* dev, struct subgraph* subgraph); int (*async_wait)(struct nn_device* dev, struct subgraph* subgraph, int try_wait); int (*release)(struct nn_device* dev); int (*release_exec_graph)(struct nn_device* dev, void* exec_graph); };这个结构里面基本上都是函数指针,先不具体看这里面,猜测这里面应该是实现了一个计算图的结构(一会再看)。
-
ret = init_serializer_registry(); 根据官方README介绍,serializer是模型解析器。追踪进去看:
int init_serializer_registry(void) { serializer_list = create_vector(sizeof(struct serializer*), NULL); if (serializer_list == NULL) { set_tengine_errno(ENOMEM); return -1; } return 0; }同样是初始化一个向量,serializer的结构如下:
struct serializer { const char* (*get_name)(struct serializer*); /* load graph from file */ int (*load_model)(struct serializer*, struct ir_graph*, const char* fname, va_list ap); /* load graph from memory */ int (*load_mem)(struct serializer*, struct ir_graph*, const void* addr, int size, va_list ap); /* unload graph, free serializer related and device releated resource */ int (*unload_graph)(struct serializer*, struct ir_graph*, void* s_priv, void* dev_priv); /* those interface exposed for operator extension */ int (*register_op_loader)(struct serializer*, int op_type, int op_ver, void* op_load_func, void* op_type_map_func, void* op_ver_map_func); int (*unregister_op_loader)(struct serializer*, int op_type, int op_ver, void* op_load_func); /* interface for regiser and unregister */ int (*init)(struct serializer*); int (*release)(struct serializer*); };还是一堆函数指针,后面要对这个结构详细分析,这里先忽略。
-
ret = exec_module_init(0); 字面意思,执行模块初始化,跟踪进去看:
int exec_module_init(int stop_on_all_error) { for (int i = 0; i < MOD_MAX_LEVEL; i++) { struct vector* v = init_vector[i]; if (v == NULL) continue; int vector_num = get_vector_num(v); for (int j = 0; j < vector_num; j++) { struct module_init_func_entry* e; int ret; e = ( struct module_init_func_entry* )get_vector_data(v, j); ret = e->func(e->arg); // fprintf(stderr, "on executing %s() \n", e->name); if (ret < 0 && (stop_on_all_error || e->critical)) return -1; } } // free the memory used for (int i = 0; i < MOD_MAX_LEVEL; i++) { struct vector* v = init_vector[i]; if (v == NULL) continue; release_vector(v); init_vector[i] = NULL; } return 0; }今天先这样吧,基本上吧这一个初始化tengine的函数看了一下。有空再继续看(x