最近又从新翻看了《编程珠玑》这本书,外面着重对工程化解决进行了解说,写得很好。印象最深的一个例子,作者拿布鲁克林大桥举例,20 世纪 40 年代前,有很多悬索桥会因为风暴断裂。而要对这种气动回升景象做正确的数学建模和工程计算,要等到 10 年后。而同一期间的布鲁克林大桥却没有断裂,工程师 John Roebling 说,他晓得气动回升景象的存在,然而又无奈从实质下来正确处理它(建模),于是他依照所需强度的 6 倍设计了大桥。最初作者倡议咱们,以 2、4 或 6 的系数来升高对性能的预计。其实就是要预留足够的性能。作者最初说:“咱们是和 John Roebling 一样的工程师吗?我很狐疑”。
言归正传,书中提到了时空开销模型,用于对代码中时空开销做计算,能够不便的用于代码性能预计和代码优化。本文对此代码做了整顿,能够退出到 utils 库中,并且可在 ESP32 和 STM32F103C8 上运行。
根本类型的空间占用
#define SIZE_MEASURE(T) \
do { \
printf("sizeof(%s)=%d\n", #T, sizeof(T)); \
} while (0)
void platform_base_type_size_measure(void)
{printf("\nplatform_base_type_size_measure\n");
SIZE_MEASURE(char);
SIZE_MEASURE(short);
SIZE_MEASURE(int);
SIZE_MEASURE(long);
SIZE_MEASURE(long long);
SIZE_MEASURE(float);
SIZE_MEASURE(double);
SIZE_MEASURE(int*);
}
这里用到了后面的知识点,宏定义参数前加 #,能够转换为字符串。
后果:
platform_base_type_size_measure
sizeof(char)=1
sizeof(short)=2
sizeof(int)=4
sizeof(long)=4
sizeof(long long)=8
sizeof(float)=4
sizeof(double)=8
sizeof(int*)=4
因为 esp32 和 stm32 都是 32 位平台,所以 int 是 4 字节,指针也是 4 字节。
构造体对齐和 malloc 占用空间
typedef struct{char c;} structc;
typedef struct {int i; char c;} structic;
typedef struct {int i; struct structip *p;} structip;
typedef struct {double d; char c;} structdc;
typedef struct {char c; double d;} structcd;
typedef struct {char c; double d; char cc;} structcdc;
typedef struct {int i; int ii; int iii;} structiii;
typedef struct {int i; int ii; char c;} structiic;
typedef struct {char c[12];} struct12;
typedef struct {char c[13];} struct13;
typedef struct {char c[28];} struct28;
typedef struct {char c[29];} struct29;
#define SIZE_MALLOC_MEASURE(T) \
do { \
printf("%s%c\t", #T, strlen(#T)<8?'\t':' '); \
printf("%d\t", sizeof(T)); \
int lastp = 0; \
for(int i=0; i<11; i++) { \
T* p = (T*)malloc(sizeof(T)); \
uint32_t thisp = (uint32_t)p; \
if(lastp != 0) \
printf("%d", thisp - lastp); \
lastp = thisp; \
} \
printf("\n"); \
} while (0)
void platform_struct_size_measure(void)
{printf("\nstruct\t\tsizeof\t malloc size\n");
SIZE_MALLOC_MEASURE(int);
SIZE_MALLOC_MEASURE(structc);
SIZE_MALLOC_MEASURE(structic);
SIZE_MALLOC_MEASURE(structip);
SIZE_MALLOC_MEASURE(structdc);
SIZE_MALLOC_MEASURE(structcd);
SIZE_MALLOC_MEASURE(structcdc);
SIZE_MALLOC_MEASURE(structiii);
SIZE_MALLOC_MEASURE(structiic);
SIZE_MALLOC_MEASURE(struct12);
SIZE_MALLOC_MEASURE(struct13);
SIZE_MALLOC_MEASURE(struct28);
SIZE_MALLOC_MEASURE(struct29);
}
命名规定为:c:chart i:int d:double,并且相互拼接。
后果:
struct sizeof malloc size
int 4 16 267900 16 16 16 16 16 16 16 16
structc 1 16 16 16 16 16 16 16 16 16 16
structic 8 16 16 16 16 16 16 16 16 16 16
structip 8 16 16 16 16 16 16 16 16 16 16
structdc 16 20 20 20 20 20 524 20 20 20 20
structcd 16 20 20 20 20 20 20 20 20 20 20
structcdc 24 28 28 28 28 28 28 28 28 28 28
structiii 12 16 16 16 16 16 16 16 16 16 16
structiic 12 16 16 16 16 16 16 16 16 16 16
struct12 12 16 16 16 16 16 16 16 16 16 16
struct13 13 20 20 20 20 20 20 20 20 20 20
struct28 28 32 32 32 32 32 32 32 32 32 32
struct29 29 36 36 36 36 36 36 36 36 36 36
能够看到,构造领会进行对齐补全,有 double 会补齐 8 字节。所以想在通信代码中通过构造体转换协定,记得应用 #pragma pack(1) 按 1 字节对齐。
malloc 在申请内存时,也会有多余的空间占用,并且不肯定是线性增长,这与 malloc 的实现形式无关。
整形运算符工夫耗费
#define INT_TIME_MEASURE(OP, n) \
do { \
printf("%s%c\t", #OP, strlen(#OP)<8?'\t':' '); \
int64_t timesum = 0; \
for(int ttt=0; ttt<5; ttt++) { \
int64_t start = esp_timer_get_time(); \
for(int i=1; i<=n; i++) { \
for(int j=1; j<=n; j++) { \
OP; \
} \
} \
int64_t t = esp_timer_get_time() - start; \
printf("%lld", t); \
timesum += t; \
} \
printf("\t%lld\n", 1000*timesum / (n*n*5)); \
} while (0)
void platform_int_time_measure(void)
{
int loop_n = 500;
printf("\nplatform_int_time_measure (n=%d)\n", loop_n);
printf("oprate\t\ttime(us)\t\ttime avg(ns)\n");
volatile int k = 0;
INT_TIME_MEASURE({}, loop_n);
INT_TIME_MEASURE(k++, loop_n);
INT_TIME_MEASURE(k=i+j, loop_n);
INT_TIME_MEASURE(k=i-j, loop_n);
INT_TIME_MEASURE(k=i*j, loop_n);
INT_TIME_MEASURE(k=i/j, loop_n);
INT_TIME_MEASURE(k=i%j, loop_n);
INT_TIME_MEASURE(k=i&j, loop_n);
INT_TIME_MEASURE(k=i|j, loop_n);
}
因为波及到工夫计算,得应用计时函数,这里是 ESP32 的实现形式。因为运行速度很快,定时器分辨率不高,所以采纳屡次运行取平均值的形式。
后果:
//ESP32
platform_int_time_measure (n=500)
oprate time(us) time avg(ns)
{} 1 1 1 1 1 0
k++ 11473 11475 11471 11471 11471 45
k=i+j 7306 7305 7301 7305 7305 29
k=i-j 7302 7305 7304 7305 7301 29
k=i*j 9390 9390 9390 9390 9390 37
k=i/j 9621 9620 9620 9621 9621 38
k=i%j 10664 10663 10662 10662 10662 42
k=i&j 8347 8346 8346 8346 8343 33
k=i|j 8347 8346 8347 8346 8346 33
//STM32F103C8
platform_int_time_measure (n=500)
oprate time(us) time avg(ns)
{} 27290 27290 27280 27280 27290 109
k++ 39150 39150 39150 39150 39150 156
k=i+j 39060 39060 39070 39070 39070 156
k=i-j 35110 35110 35110 35110 35100 140
k=i*j 35180 35180 35180 35180 35180 140
k=i/j 44410 44410 44410 44410 44410 177
k=i%j 63790 63790 63790 63790 63790 255
k=i&j 39060 39070 39070 39060 39060 156
k=i|j 46820 46820 46820 46820 46830 187
ESP32 的自增运算比拟耗时,有点奇怪。STM32 上自增和加法是一样的。
ESP32 的除法没有更耗时,STM32 的除法是更耗时的。
平时没留神到的是,求余运算十分耗时。与运算也不能说省工夫。
浮点运算符工夫耗费
#define FLOAT_TIME_MEASURE(OP, n) \
do { \
printf("%s%c\t", #OP, strlen(#OP)<8?'\t':' '); \
int64_t timesum = 0; \
volatile float fi,fj; \
for(int ttt=0; ttt<5; ttt++) { \
int64_t start = esp_timer_get_time(); \
for(int i=1; i<=n; i++) { \
fi = i; \
for(int j=1; j<=n; j++) { \
fj = j; \
OP; \
} \
} \
int64_t t = esp_timer_get_time() - start; \
printf("%lld", t); \
timesum += t; \
} \
printf("\t%lld\n", 1000*timesum / (n*n*5)); \
} while (0)
void platform_float_time_measure(void)
{
int loop_n = 100;
printf("\nplatform_float_time_measure (n=%d)\n", loop_n);
printf("oprate\t\ttime(us)\t\ttime avg(ns)\n");
volatile float fk = 0;
FLOAT_TIME_MEASURE({}, loop_n);
FLOAT_TIME_MEASURE(fk=fi+fj, loop_n);
FLOAT_TIME_MEASURE(fk=fi-fj, loop_n);
FLOAT_TIME_MEASURE(fk=fi*fj, loop_n);
FLOAT_TIME_MEASURE(fk=fi/fj, loop_n);
}
后果:
//ESP32
platform_float_time_measure (n=100)
oprate time(us) time avg(ns)
{} 1685 1683 1683 1683 1683 168
fk=fi+fj 3941 3935 3939 3936 3935 393
fk=fi-fj 4516 4511 4514 4510 4514 451
fk=fi*fj 4293 4291 4295 4291 4295 429
fk=fi/fj 14036 14037 14034 14033 14037 1403
//STM32
platform_float_time_measure (n=100)
oprate time(us) time avg(ns)
{} 6000 6010 6000 6000 6010 600
fk=fi+fj 15670 15680 15680 15680 15680 1567
fk=fi-fj 14760 14760 14760 14760 14760 1476
fk=fi*fj 13920 13920 13920 13920 13920 1392
fk=fi/fj 20790 20790 20790 20790 20790 2079
过程中须要先将整数赋值给浮点,耗费了不少工夫。所以须要减去空运行的工夫。即便是带 FPU 的 ESP32,仍然耗费不少工夫。
数组运算符工夫耗费
void platform_array_time_measure(void)
{
int loop_n = 500;
printf("\nplatform_array_time_measure (n=%d)\n", loop_n);
printf("oprate\t\ttime(us)\t\ttime avg(ns)\n");
volatile int k = 0;
int x[loop_n+1];
INT_TIME_MEASURE(k=i+j, loop_n);
INT_TIME_MEASURE(k=x[i]+j, loop_n);
INT_TIME_MEASURE(k=i+x[j], loop_n);
INT_TIME_MEASURE(k=x[i]+x[j], loop_n);
}
间接应用整数的宏,后果和整数操作差别不大
platform_array_time_measure (n=500)
oprate time(us) time avg(ns)
k=i+j 7302 7304 7304 7301 7304 29
k=x[i]+j 7313 7313 7309 7312 7313 29
k=i+x[j] 10431 10429 10430 10430 10430 41
k=x[i]+x[j] 10432 10432 10432 10432 10432 41
操作符和数学库工夫耗费
void platform_function_time_measure(void)
{
int loop_n = 100;
printf("\nplatform_function_time_measure (n=%d)\n", loop_n);
printf("oprate\t\ttime(us)\t\ttime avg(ns)\n");
volatile int k = 0;
volatile float fk = 0;
INT_TIME_MEASURE(k=(i>j)?i:j, loop_n);
INT_TIME_MEASURE(rand(), loop_n);
FLOAT_TIME_MEASURE(fk=sqrt(j+fi), loop_n);
FLOAT_TIME_MEASURE(fk=sin(j+fi), loop_n);
// FLOAT_TIME_MEASURE(fk=sinh(j+fi), loop_n);
// FLOAT_TIME_MEASURE(fk=asin(j+fi), loop_n);
// FLOAT_TIME_MEASURE(fk=cos(j+fi), loop_n);
// FLOAT_TIME_MEASURE(fk=tan(j+fi), loop_n);
}
后果:
platform_function_time_measure (n=100)
oprate time(us) time avg(ns)
k=(i>j)?i:j 337 336 336 336 336 33
rand() 3763 3753 3756 3752 3756 375
fk=sqrt(j+fi) 58442 58416 58417 58419 58416 5842
fk=sin(j+fi) 111011 110967 110970 110967 110971 11097
开方没有设想中的耗时,三角函数耗时真的大。如果对精度要求不高,如呼吸灯什么的,强烈建议应用疾速三角函数。
malloc 工夫耗费
#define ALLOC_TIME_MEASURE(OP, n) \
do { \
printf("%s%c\t", #OP, strlen(#OP)<8?'\t':' '); \
int64_t timesum = 0; \
int64_t start = esp_timer_get_time(); \
for(int i=1; i<=n; i++) { \
OP; \
} \
int64_t t = esp_timer_get_time() - start; \
printf("%lld", t); \
timesum += t; \
printf("\t\t%lld\n", 1000*timesum / (n)); \
} while (0)
#define ALLOC_FREE_MEASURE(OP, n) \
do { \
printf("%s%c\t\t", #OP, strlen(#OP)<8?'\t':' '); \
int64_t timesum = 0; \
int64_t start = esp_timer_get_time(); \
for(int i=1; i<=n; i++) { \
OP; \
} \
int64_t t = esp_timer_get_time() - start; \
printf("%lld", t); \
timesum += t; \
printf("\t\t%lld\n", 1000*timesum / (n)); \
} while (0)
void platform_alloc_time_measure(void)
{
int loop_n = 100;
printf("\nplatform_alloc_time_measure (n=%d)\n", loop_n);
printf("oprate\t\t\ttime(us)\ttime avg(ns)\n");
volatile int* k[loop_n+1];
ALLOC_TIME_MEASURE(k[i]=malloc(16), loop_n);
ALLOC_FREE_MEASURE(free(k[i]), loop_n);
ALLOC_TIME_MEASURE(k[i]=malloc(100), loop_n);
ALLOC_FREE_MEASURE(free(k[i]), loop_n);
ALLOC_TIME_MEASURE(k[i]=malloc(2000), loop_n);
ALLOC_FREE_MEASURE(free(k[i]), loop_n);
}
后果:
platform_alloc_time_measure (n=100)
oprate time(us) time avg(ns)
k[i]=malloc(16) 317 3170
free(k[i]) 195 1950
k[i]=malloc(100) 317 3170
free(k[i]) 194 1940
k[i]=malloc(2000) 613 6130
free(k[i]) 189 1890
free 耗时比较稳定。malloc 耗时会随着空间增大而增大,但也不是线性关系。并且这两个的耗时也不能忽略不计。
End
通过这些比照,以前漠视的 malloc 和 free,其实也是工夫空间耗费小户。求余运算与运算也值得注意。
有了这些空间和工夫耗费的比照,置信大家就能够对本人应用的平台有了更进一步的理解。同时在当前须要优化代码的时候,也能有一个明确的方向。