为什么 LDR 有时需要 20 CPU 个周期?

Why does LDR sometimes take 20 CPU cycles?

我在 ARM Cortex M4 程序集 中遇到并发布了 LDR 和 STR 指令。出于某种原因,它们 write/read 内存中的某些部分比其他部分花费的时间更长。

为了说明这一点,我设置了这个简单的例子:

我创建了一个项目,其中包含一个主 C 文件和一个包含汇编代码的相邻“.S”文件。我已使用“extern”对象将汇编函数包含到我的 C 文件中。

//Add the asm functions to our C code
extern "C" void LoadTest(uint32_t *memory_adress);
extern "C" void LoadTestLoop(uint32_t *memory_adress);

程序的作用如下:

void perform_test()
{
  //Time
  register uint32_t register_before_time=before_time;
  register uint32_t register_after_time=after_time;

  register uint32_t* input_address=0x400E9000;
  
  register_before_time=ARM_DWT_CYCCNT; 

  //Time measurment occurs in here!
  LoadTestLoop(input_address);
  
  register_after_time=ARM_DWT_CYCCNT;

  Serial.print(" Time: ");
  Serial.println(register_after_time-register_before_time-time_error);
}

它向我们展示了执行某项操作所花费的时间 “register_before_time=ARM_DWT_CYCCNT;”“register_after_time=ARM_DWT_CYCCNT;” 行.

以下是我们将测试其速度的汇编子例程:

.global LoadTest
LoadTest:
    ldr r1, [r0]                        /*Load value into r1 from memory_address*/
    orr r1, #0xC0                       /*OR bits 7,6 to be on.*/
    str r1, [r0]                        /*Store the changed value back into memory_address*/
    bx lr
.global LoadTestLoop
LoadTestLoop:
    mov r2, #255                        /* Set r2 to be 255 for the loop*/
    
    TestLoop:                           /*Same code as before*/
        ldr r1, [r0]                        
        orr r1, #0xC0                   
        str r1, [r0]
        
        subs r2, r2, #1                 /*Decrement r2 + set Z flag if it's zero*/
        bne TestLoop                    /*Repeat until r2==0*/
    bx lr

LoadTest – 从我们给它的地址加载一个值。将该值与 0xC0 进行或运算,然后将其存储回同一地址。

LoadTestLoop – 做同样的事情,但是,在一个循环中执行 255 次,这样我们可以得到一个循环迭代需要多长时间的平均值,并最小化来自 b运行ching 指令进出函数的时间测量错误。

注意: 为了尽量减少测量误差,要处理的地址在 input_address 指针中提供给时间测量区外的两个函数。

register uint32_t* input_address=0x400E9000;

测试结果及问题:

我运行这两个测试都是普通C变量

uint32_t test_value=255;
register uint32_t* input_address=&test_value;

以及微控制器内部的配置寄存器。请注意,在数据表中,它们仅显示为内存。

register uint32_t* input_address=0x400E9000;

平均而言,标准变量的 LoadTest 需要 9 个周期来执行,但控制寄存器的 27 个周期要长得多。 LoadTestLoop 测试用平均 1541 的标准变量加强了这一点周期(每次迭代 6 个周期)并且控制记录了惊人的 12227 个周期,每次迭代达到疯狂的 47 个周期!

为什么会这样?

为什么 LDR 和 STR 有时需要更长的时间来执行?它与 this instruction set website 上循环计数旁边写的小“b”有什么关系吗?单击它会使您返回同一页面。

有人知道为什么会这样吗?被这个问题困扰很久了,很想知道。

感谢您的帮助

这是完全正常的。

一般来说,从内存中加载需要多少时间。时间不受 CPU 本身的控制,因此引用的周期计数只能代表“最佳情况”。如果 CPU 无法从其自身的内部结构(例如存储缓冲区或 L1 缓存)完成负载,那么它只需要将请求放到内存总线上并停止,直到内存子系统响应。 (或者继续执行后面的指令out-of-order,如果这样配备,如果它能找到一些不依赖于加载结果的指令。)

实际花费的时间可能会有很大差异,例如取决于负载是命中还是未命中 L2 或 L3 缓存,另一个核心或外部设备是否持有总线锁等。如果机器没有缓存并且所有内存是快速SRAM,那么时间可以很稳定。

但在您的情况下,您正在加载的地址实际上映射到硬件设备。所以你根本不是在读 RAM,你在读 I/O。在这种情况下,响应必须来自设备本身,并且设备基本上可以根据需要进行响应。如果您需要能够预测时间,那么您需要查看该设备的文档(以及其间的任何接口硬件),而不是 CPU 手册中的周期计数。