在嵌入式 MCU 应用程序中,在 for 循环中使用 uint_fast16_t 还是 size_t 更好?
In embedded MCU application is it better to use uint_fast16_t or size_t in for loops?
我想为 运行 在不同 MCU(16 位、32 位或 64 位基础)上的应用程序编写可移植代码。
- MSP-430
- nRF52(32 位)
- PIC(16 位)
- C51(8 位)
让我们考虑一下这个片段:
events = 0;
for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
if (array[i] > threshold)
events++;
}
我的问题是关于循环计数器变量的类型,这里是size_t
。
通常 size_t
应该足够大以寻址我系统的所有内存。所以使用 size_t
可能会影响我的代码在某些体系结构上的性能,因为这个变量的宽度对于我拥有的数组的长度来说太大了。
根据这个假设,我应该更好地使用 uint_fast16_t
,因为我知道我的 array
少于 65k 个元素。
关心这篇文章是否有意义,或者我的编译器是否足够聪明来优化它?
我认为 uint_fast16_t
很少使用,与 size_t
相比几乎是样板文件。
更具体地说明我的问题:
我是通过系统地为我的循环计数器使用正确的类型来提高我的代码的可移植性(uint_fast8_t
、uint_fast16_t
、...)还是我应该更喜欢使用 size_t
因为在大多数情况下,它不会对性能产生影响?
编辑
根据您的评论和评论,很明显大多数时候,编译器将注册循环计数器,因此在size_t
或uint_fast8_t
之间进行选择没关系。
main: # @main
mov rax, -80
mov ecx, dword ptr [rip + threshold]
.LBB0_1: # =>This Inner Loop Header: Depth=1
[....]
.LBB0_5: # in Loop: Header=BB0_1 Depth=1
add rax, 8 # <----------- Kept in a register
jne .LBB0_1
jmp .LBB0_6
.LBB0_2: # in Loop: Header=BB0_1 Depth=1
[....]
.LBB0_6:
xor eax, eax
ret
如果循环长度大于内部 CPU 寄存器,例如在 8 位微控制器上执行 512 次循环。
对于可移植代码,使用 size_t
。
对于快速代码...好吧,这取决于您的编译器和处理器。如果您使用 16 位类型,它可能 运行 在 16 位处理器上最快,但实际上 比在 64 位处理器上 size_t
慢。在衡量性能之前,您不应该假设任何事情。
我会使用 size_t
并且只有在存在可演示的性能问题时才会考虑进一步优化。
对于 MCU,请使用您认为适合阵列大小的最小类型。如果您知道数组可能大于 256 字节,但绝不会大于 65536,那么 uint_fast16_t
确实是最合适的类型。
这里的主要问题是具有扩展闪存 (>64kb) 的 16 位 MCU,提供 24 或 32 位地址宽度。这是许多 16 苦味剂的常态。在这样的系统上,size_t
将是 32 位,因此使用起来会很慢。
如果不考虑可移植到 8 或 16 个苦味,那么我会使用 size_t
。
根据我的经验,许多嵌入式编译器不够智能,无法优化代码以使用比指定类型更小的类型,即使它们可以扣除 compile-time 处的数组大小。
与任何优化一样,首先为可移植的常见情况编写简单代码(使用 size_t)。然后用其他类型看看你平台上的程序集。 如果其中一种类型工作得更快或生成的代码明显更小,您可以为这些类型的访问类型定义一个特殊的索引类型。例如,您可以使用 near、far 和 huge 指针(以及相应的索引)的概念,但为了清楚起见,请使用固定宽度类型。
/* The compiler for my16bitmcu, cannot detect ranges to use appropriate types */
#if defined __MY16BITMCU__ /* replace with architecture's predefined macro */
typedef uint16_t size8t, size16t; /* use uint8_t for size8t on 8bit */
typedef uint32_t size32t;
typedef uint64_t size64t;
typedef int16_t ptrdiff8t, ptrdiff16t; /* use int8_t for ptrdiff8t on 8bit */
typedef int32_t ptrdiff32t;
typedef int64_t ptrdiff64t;
#else
typedef size_t size8t, size16t, size32t, size64t;
typedef ptrdiff_t ptrdiff8t, ptrdiff16t, ptrdiff32t, ptrdiff64t;
#endif
/** example usage: sum the total of an array
** using size8t for count reduces complexity on some 8/16 bit systems
** on other systems, size8t is the same as size_t
**/
int sum_numbers(int *numbers, size8t count){
int total = 0;
while(count--) total += numbers[count];
return total;
}
我想为 运行 在不同 MCU(16 位、32 位或 64 位基础)上的应用程序编写可移植代码。
- MSP-430
- nRF52(32 位)
- PIC(16 位)
- C51(8 位)
让我们考虑一下这个片段:
events = 0;
for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
if (array[i] > threshold)
events++;
}
我的问题是关于循环计数器变量的类型,这里是size_t
。
通常 size_t
应该足够大以寻址我系统的所有内存。所以使用 size_t
可能会影响我的代码在某些体系结构上的性能,因为这个变量的宽度对于我拥有的数组的长度来说太大了。
根据这个假设,我应该更好地使用 uint_fast16_t
,因为我知道我的 array
少于 65k 个元素。
关心这篇文章是否有意义,或者我的编译器是否足够聪明来优化它?
我认为 uint_fast16_t
很少使用,与 size_t
相比几乎是样板文件。
更具体地说明我的问题:
我是通过系统地为我的循环计数器使用正确的类型来提高我的代码的可移植性(uint_fast8_t
、uint_fast16_t
、...)还是我应该更喜欢使用 size_t
因为在大多数情况下,它不会对性能产生影响?
编辑
根据您的评论和评论,很明显大多数时候,编译器将注册循环计数器,因此在size_t
或uint_fast8_t
之间进行选择没关系。
main: # @main
mov rax, -80
mov ecx, dword ptr [rip + threshold]
.LBB0_1: # =>This Inner Loop Header: Depth=1
[....]
.LBB0_5: # in Loop: Header=BB0_1 Depth=1
add rax, 8 # <----------- Kept in a register
jne .LBB0_1
jmp .LBB0_6
.LBB0_2: # in Loop: Header=BB0_1 Depth=1
[....]
.LBB0_6:
xor eax, eax
ret
如果循环长度大于内部 CPU 寄存器,例如在 8 位微控制器上执行 512 次循环。
对于可移植代码,使用 size_t
。
对于快速代码...好吧,这取决于您的编译器和处理器。如果您使用 16 位类型,它可能 运行 在 16 位处理器上最快,但实际上 比在 64 位处理器上 size_t
慢。在衡量性能之前,您不应该假设任何事情。
我会使用 size_t
并且只有在存在可演示的性能问题时才会考虑进一步优化。
对于 MCU,请使用您认为适合阵列大小的最小类型。如果您知道数组可能大于 256 字节,但绝不会大于 65536,那么 uint_fast16_t
确实是最合适的类型。
这里的主要问题是具有扩展闪存 (>64kb) 的 16 位 MCU,提供 24 或 32 位地址宽度。这是许多 16 苦味剂的常态。在这样的系统上,size_t
将是 32 位,因此使用起来会很慢。
如果不考虑可移植到 8 或 16 个苦味,那么我会使用 size_t
。
根据我的经验,许多嵌入式编译器不够智能,无法优化代码以使用比指定类型更小的类型,即使它们可以扣除 compile-time 处的数组大小。
与任何优化一样,首先为可移植的常见情况编写简单代码(使用 size_t)。然后用其他类型看看你平台上的程序集。 如果其中一种类型工作得更快或生成的代码明显更小,您可以为这些类型的访问类型定义一个特殊的索引类型。例如,您可以使用 near、far 和 huge 指针(以及相应的索引)的概念,但为了清楚起见,请使用固定宽度类型。
/* The compiler for my16bitmcu, cannot detect ranges to use appropriate types */
#if defined __MY16BITMCU__ /* replace with architecture's predefined macro */
typedef uint16_t size8t, size16t; /* use uint8_t for size8t on 8bit */
typedef uint32_t size32t;
typedef uint64_t size64t;
typedef int16_t ptrdiff8t, ptrdiff16t; /* use int8_t for ptrdiff8t on 8bit */
typedef int32_t ptrdiff32t;
typedef int64_t ptrdiff64t;
#else
typedef size_t size8t, size16t, size32t, size64t;
typedef ptrdiff_t ptrdiff8t, ptrdiff16t, ptrdiff32t, ptrdiff64t;
#endif
/** example usage: sum the total of an array
** using size8t for count reduces complexity on some 8/16 bit systems
** on other systems, size8t is the same as size_t
**/
int sum_numbers(int *numbers, size8t count){
int total = 0;
while(count--) total += numbers[count];
return total;
}