【问题标题】:Efficient copy on ARM, two 16-bit fetches or 1 32-bit?ARM 上的高效复制,两个 16 位提取还是 1 个 32 位?
【发布时间】:2013-09-17 19:57:16
【问题描述】:

我正在使用 ARM7TDMI 处理器开发嵌入式系统。

在时间紧迫的 ISR 中,我需要将 24 个 16 位值从硬件寄存器快照(复制)到 SRAM 中。这些值是连续的,可以视为一个数组。

数据总线(到 SRAM 和硬件寄存器)是 16 位的,我们在 ARM 模式 (8/32) 下运行。

在商店中,我们正在讨论复制数据的最佳方法:作为 16 位数量或作为 32 位数量。

我的论点是 ARM 处于 32 位模式,因此使用一条指令进行 2 次 16 位取指比使用两条 16 位指令每次取指一次要快。
此外,要获取的指令数量是原来的一半,这应该会减少 1/2 的时间。

有人有任何数据支持这两种方法吗? (我的 O'scopes 都已分配,所以我无法在嵌入式系统上进行测量。由于 ISR 每毫秒中断一次,也无法运行大量时间。) *(分析很困难,因为我们的 JTAG Jet 探头不提供准确分析的方法)。*

示例代码 - 16 它复制:

#define MAX_16_BIT_VALUES 24U
uint16_t volatile * p_hardware;
uint16_t data_from_hardware[MAX_16_BIT_VALUES];
data_from_hardware[0] = p_hardware[0];
data_from_hardware[1] = p_hardware[1];
data_from_hardware[2] = p_hardware[2];
data_from_hardware[3] = p_hardware[3];
//...
data_from_hardware[20] = p_hardware[20];
data_from_hardware[21] = p_hardware[21];
data_from_hardware[22] = p_hardware[22];
data_from_hardware[23] = p_hardware[23];

示例代码,32 位副本:

uint32_t * p_data_from_hardware = (uint32_t *)&data_from_hardware[0];
uint32_t volatile * p_hardware_32_ptr = (uint32_t volatile *) p_hardware;
p_data_from_hardware[0] = p_hardware_32_ptr[0];
p_data_from_hardware[1] = p_hardware_32_ptr[1];
p_data_from_hardware[2] = p_hardware_32_ptr[2];
p_data_from_hardware[3] = p_hardware_32_ptr[3];
//...
p_data_from_hardware[ 8] = p_hardware_32_ptr[ 8];
p_data_from_hardware[ 9] = p_hardware_32_ptr[ 9];
p_data_from_hardware[10] = p_hardware_32_ptr[10];
p_data_from_hardware[11] = p_hardware_32_ptr[11];

详情:ARM7TDMI 处理器运行于 8/32 位模式,IAR EW 编译器。

注意:展开代码是为了防止指令缓存重新加载。
注意:汇编语言清单表明,使用常量索引访问内存比使用递增指针更有效。 em>

编辑 1:测试

根据 Chris Stratton 的评论,我们在 16 位 FPGA 寄存器上进行 32 位提取时遇到问题,因此无法进行 32 位优化。

也就是说,我使用 DMA 进行了分析。使用 DMA 控制器的性能提升为 30 us(微秒)。在我们的项目中,我们希望节省更多的时间,所以这种优化是不值得的。这个实验表明,如果我们有更多的数据要传输,DMA 将非常有用,或者传输可以是并行的。

有趣的是,DMA 需要 17 条指令来设置。

【问题讨论】:

  • 关闭中断,并测量主代码的性能。不过,您可能应该考虑使用 DMA……这是最快的。
  • 您知道寄存器在两种模式下都可以访问吗?在我使用的某些 ARM 部件上,某些 SFR 需要特定的访问宽度。此外,您可能需要考虑基于 DMA 的 memcpy 实现在多大的块传输时变得值得(可能比这更大,但是?)
  • @MarkLakata - 似乎有问题的代码在中断处理程序中,而不是主程序中。应该可以编写在主循环中不必要地执行大量时间的测试代码,但可能有原因导致结果不一样。也许有一个硬件周期计数器可以用于稀疏试验基准测试?
  • @ChrisStratton:寄存器到内存映射的 FPGA,32 位取指将读取 2 个连续的寄存器。此外,使用 DMA 控制器复制 48 个字节是值得的(设置至少需要 6 条指令)。
  • 克里斯说得有道理。某些硬件需要以特定宽度读取。但是,我无法相信代码在 ISR 中的运行与在主循环中的运行有任何不同。代码就是代码。如果硬件阻塞了读取,那么尝试优化它就没有意义了——改用 DMA 会更好。

标签: c performance assembly embedded arm


【解决方案1】:

如果速度是最重要的,如果硬件可以支持它,最好的选择是汇编语言例程,例如:

; Assume R0 holds source base and R1 holds destination base
PUSH   {R4-R7}
LDMIA R0,{R2-R7}
STMIA R1,{R2-R7}
LDMIA R0,{R2-R7}
STMIA R1,{R2-R7}
POP    {R4-R7}

我相信在 ARM7TDMI 上,当使用 32 位总线时,LDR 需要三个周期,STR 需要两个;使用 LDRMIA/STRMIA 加载或存储 n 个字需要 3+n 个周期。因此,12 个 LDR 和 12 个 STR 将需要 60 个周期,但上面的序列应该需要 50 个(包括寄存器保存/恢复)。我希望使用 16 位总线会为每个 32 位加载或存储增加额外的周期损失,但如果 LDM* 和 STM* 指令将每个 32 位操作拆分为两个 16 位操作,它们仍然应该比离散加载和存储快得多,尤其是在必须从 16 位内存中获取代码的情况下。

【讨论】:

  • 您能提供一个使用内联汇编的 C 语言示例吗?我们的车间编码标准更喜欢内联汇编而不是创建新的汇编文件。您可以使用 GNU 语法,因为 IAR 编译器使用 GNU 语法。
  • 只需查看并使用 memcpy(),它已针对 axi/amba 总线上的最佳传输大小进行了优化。连接到 axi/amba 总线的供应商逻辑将处理相同/更大的传输。并且总线将等待(必须将较小总线上的延迟和多个周期添加到 supercat 的数量)已指示。通常,四寄存器 ldmia/stmia 对用于快速复制循环。
  • gameboy Advance 主要使用 16 位总线,并清楚地表明 thumb 运行得更快,但您的代码是数据周期而不是指令周期,因此您会获得 thumb 的好处,但您的主要问题是数据周期。使用 128 位传输(4 个字)或更大应该会将您推向数据与指令的边缘。如果您在编写代码时使用代码并且编译器没有优化,那么您的指令繁重,应该使用拇指。您将获得一些性能提升,但不如您使副本更高效(只需使用 memcpy)。
猜你喜欢
  • 1970-01-01
  • 2011-12-22
  • 2018-03-31
  • 1970-01-01
  • 2023-03-06
  • 2019-12-09
  • 1970-01-01
  • 1970-01-01
  • 2017-04-19
相关资源
最近更新 更多