为什么 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 手册中的周期计数。
我在 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 手册中的周期计数。