2. 内存分配策略初步优化
任务目标
在RK3588 NPU推理场景中,频繁调用rknn_init/rknn_run时默认内存分配策略会产生碎片,导致峰值内存上升。本案例通过设置rknn_set_mem_strategy为顺序分配模式,并配合输入/输出张量的显式预分配,减少碎片、提升利用率。
环境依赖
- RK3588开发板,系统为Ubuntu 20.04+
- RKNN Toolkit 1.6.0(PC端模型转换)
- RKNN Runtime 1.6.0(板端推理库,含librknnmrt.so)
- 模型文件:
mobilenet_v2.rknn(由RKNN Toolkit转换得到)
关键实现
核心策略:在rknn_init后立即调用rknn_set_mem_strategy,将分配策略从默认的RKNN_MEM_STRATEGY_DEFAULT切换为RKNN_MEM_STRATEGY_SEQUENTIAL。该模式按推理顺序连续分配内存块,避免反复malloc/free造成的空洞。同时,对输入/输出张量使用rknn_create_mem预分配,使NPU内部与外部张量共享同一内存池,减少拷贝与碎片。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "rknn_api.h"
#define MODEL_PATH "./mobilenet_v2.rknn"
#define IMG_SIZE 3 * 224 * 224
int main() {
// 1. 加载模型
FILE *fp = fopen(MODEL_PATH, "rb");
if (!fp) { printf("open model failed\n"); return -1; }
fseek(fp, 0, SEEK_END);
int model_len = ftell(fp);
fseek(fp, 0, SEEK_SET);
void *model_data = malloc(model_len);
fread(model_data, 1, model_len, fp);
fclose(fp);
// 2. 初始化NPU上下文
rknn_context ctx;
int ret = rknn_init(&ctx, model_data, model_len, 0, NULL);
if (ret < 0) { printf("rknn_init failed: %d\n", ret); return -1; }
// 3. 设置内存分配策略为顺序模式(关键步骤)
ret = rknn_set_mem_strategy(ctx, RKNN_MEM_STRATEGY_SEQUENTIAL);
if (ret < 0) {
printf("set mem strategy failed: %d\n", ret);
rknn_destroy(ctx);
return -1;
}
// 4. 查询输入/输出张量信息
rknn_input_output_num io_num;
rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num));
rknn_tensor_attr input_attr, output_attr;
memset(&input_attr, 0, sizeof(input_attr));
input_attr.index = 0;
rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &input_attr, sizeof(input_attr));
memset(&output_attr, 0, sizeof(output_attr));
output_attr.index = 0;
rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &output_attr, sizeof(output_attr));
// 5. 预分配输入/输出内存(避免推理时临时分配)
rknn_tensor_mem *input_mem = rknn_create_mem(ctx, input_attr.size);
rknn_tensor_mem *output_mem = rknn_create_mem(ctx, output_attr.size);
if (!input_mem || !output_mem) {
printf("create mem failed\n");
rknn_destroy(ctx);
return -1;
}
// 6. 模拟连续推理10次,观察内存稳定性
rknn_input inputs[1];
rknn_output outputs[1];
inputs[0].index = 0;
inputs[0].type = RKNN_TENSOR_UINT8;
inputs[0].size = IMG_SIZE;
inputs[0].fmt = RKNN_TENSOR_NHWC;
inputs[0].buf = malloc(IMG_SIZE);
memset(inputs[0].buf, 0, IMG_SIZE); // 用零填充模拟输入
outputs[0].index = 0;
outputs[0].want_float = 1;
for (int i = 0; i < 10; i++) {
// 使用预分配内存作为输入
memcpy(input_mem->virt_addr, inputs[0].buf, IMG_SIZE);
rknn_set_io_mem(ctx, input_mem, &input_attr);
rknn_set_io_mem(ctx, output_mem, &output_attr);
ret = rknn_run(ctx, NULL);
if (ret < 0) { printf("rknn_run failed at iter %d: %d\n", i, ret); break; }
ret = rknn_outputs_get(ctx, 1, outputs, NULL);
if (ret < 0) { printf("get output failed: %d\n", ret); break; }
rknn_outputs_release(ctx, 1, outputs);
}
// 7. 清理
free(inputs[0].buf);
rknn_free_mem(ctx, input_mem);
rknn_free_mem(ctx, output_mem);
rknn_destroy(ctx);
free(model_data);
printf("inference loop completed\n");
return 0;
}
编译与运行
# 板端交叉编译(RK3588)
aarch64-linux-gnu-gcc mem_strategy_demo.c -o mem_strategy_demo \
-I/usr/include/rknn -L/usr/lib -lrknnmrt
# 将可执行文件与模型拷贝至板端后运行
./mem_strategy_demo
验证方法
在板端运行程序前,先通过cat /proc/meminfo记录可用内存基线。程序运行期间,在另一个终端循环执行cat /proc/meminfo | grep MemFree观察内存波动。判定通过标准:
- 程序输出
inference loop completed,无错误码; - 10次推理过程中,MemFree值下降幅度不超过20MB(对比未设置策略时通常下降50MB以上);
- 连续运行3次程序,内存峰值基本一致,无递增趋势。
若需对比优化效果,可将第3步的rknn_set_mem_strategy调用注释掉重新编译运行,观察内存波动差异。
常见问题
- 策略设置失败(返回非0):确认RKNN Runtime版本≥1.6.0,旧版本不支持该接口。
- 内存预分配失败:检查
input_attr.size是否与实际输入尺寸匹配,模型输入为224x224x3时该值应为150528。 - 性能下降:顺序分配策略在极端动态shape场景下可能增加单次分配耗时,但本案例固定shape场景下影响可忽略。