| Both sides previous revisionPrevious revisionNext revision | Previous revision |
| en:multiasm:paarm:chapter_5_15 [2025/12/05 00:20] – [Speculative instruction execution] eriks.klavins | en:multiasm:paarm:chapter_5_15 [2025/12/08 12:33] (current) – eriks.klavins |
|---|
| ====== Energy Efficient Coding ====== | ====== Energy Efficient Coding ====== |
| |
| Assembler code is assembled into a single object code. Compilers, instead, take high-level language code and convert it to machine code. And during compilation, the code may be optimised in several ways. For example, there are many ways to implement statements, FOR loops or Do-While loops in the assembler. There are some good hints for optimising the assembler code as well, but these are just hints for the programmer. | Some special instructions are meant to put the processor into sleep modes and wait for an event to occur. The processor can be woken up by an interrupt or by an event. In these modes, the code may be explicitly created to initialise interrupts and events, and to handle them. After that, the processor may be put into sleep mode and remain asleep unless an event or interrupt occurs. The following code example can be used only in bare-metal mode – without an OS. |
| - Take into account the instruction execution time (or cycle). Some instructions take more than one CPU cycle to execute, and there may be other instructions that achieve the desired result. | <codeblock code_label> |
| - Try to use the register as much as possible without storing the temporary data in the memory. | <caption>IDLE loop</caption> |
| - Eliminate unnecessary compare instructions by doing the appropriate conditional jump instruction based on the flags that are already set from a previous arithmetic instruction. Remember that arithmetic instructions can update the status flags if the postfix ''<fc #008000>S</fc>'' is used in the instruction mnemonic. | <code> |
| - It is essential to align both your code and data to get a good speedup. For ARMv8, the data must be aligned on 16-byte boundaries. In general, if alignment is not used on a 16-byte boundary, the CPU will eventually raise an exception. | .global idle_loop |
| - And yet, there are still multiple hints that can help speed up the computation. In small code examples, the speedup will not be noticeable. The processors can execute millions of instructions per second. | idle_loop: |
| | 1: WFI @ Wait For Interrupt, core goes to low-power |
| | B 1b @ After the interrupt, go back and sleep again |
| | </code> |
| | </codeblock> |
| | <note>Note that interrupt handling and initialisation must also be implemented in the code; otherwise, the CPU may encounter an error that may force a reboot. </note> |
| | The example only waits for interrupts to occur. To wait for events and interrupts, the ''<fc #800000>WFI</fc>'' instruction must be replaced with the ''<fc #800000>WFE</fc>'' instruction. Another CPU core may execute an ''<fc #800000>SEV</fc>'' instruction that signals an event to all cores. |
| |
| Processors today can execute many instructions in parallel using pipelining and multiple functional units. These techniques allow the reordering of instructions internally to avoid pipeline stalls (Out-of-Order execution), branch prediction to guess the branching path, and others. Without speculation, each branch in the code would stall the pipeline until the outcome is known. These situations are among the factors that reduce the processor's computational power. | On a Raspberry Pi 5 running Linux, it is not observable whether the CPU enters these modes, because the OS generates many events between CPU cores and also handles many interrupts from communication interfaces and other Raspberry Pi components. |
| | Another way to save more energy while running the OS on the Raspberry Pi is to reduce the CPU clock frequency. There is a scheme called dynamic voltage and frequency scaling (DVFS), the same technique used in laptops, that reduces power consumption and thereby increases battery life. On the internet, there is a paper named “Cooling a Raspberry Pi Device ”. The paper includes one chapter explaining how to reduce the CPU clock frequency. The Linux OS exposes CPU frequency scaling through sysfs, e.g.: |
| | * ”/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor” |
| | * “/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq” |
| |
| ===== Speculative instruction execution ===== | It is possible to use syscalls in assembler to open and write specific values into them. |
| | <codeblock code_label> |
| | <caption>Power saving</caption> |
| | <code> |
| | .global _start |
| | .section .text |
| | _start: |
| | @ openat(AT_FDCWD, path, O_WRONLY, 0) |
| | mov x0, #-100 @ AT_FDCWD |
| | ldr x1, =gov_path @ const char *pathname |
| | mov x2, #1 @ O_WRONLY |
| | mov x3, #0 @ mode (unused) |
| | mov x8, #56 @ sys_openat |
| | svc #0 |
| | mov x19, x0 @ save fd |
| |
| Let's start with an explanation of how speculation works. The pipeline breaks down the whole instruction into small microoperations. The first microoperation (first step) is to fetch the instruction from memory. The second step is to decode the instruction; this is the primary step, during which the hardware is prepared for instruction execution. And of course, the next step is instruction execution, and the last one is to resolve and commit the result. The result of the instruction is temporarily stored in the pipeline buffer and waits to be stored either in the processor’s registers or in memory. \\ | @ write(fd, "powersave\n", 10) |
| ''<fc #800000>CMP </fc> <fc #008000>X0</fc>, <fc #ffa500>#0</fc>''\\ | mov x0, x19 |
| ''<fc #800000>B</fc>.<fc #9400d3>EQ </fc> JumpIfZeroLabel''\\ | ldr x1, =gov_value |
| ''<fc #800000>ADD </fc> <fc #008000>X1</fc>, <fc #008000>X1</fc>, <fc #ffa500>#1</fc> <fc #6495ed>@ This executes speculatively while B.EQ remains unresolved</fc>''\\ | mov x2, #10 @ length of "powersave\n" |
| | mov x8, #64 @ sys_write |
| | svc #0 |
| |
| ===== Barriers(instruction synchronization / data memory / data synchronization / one way BARRIER) ===== | @ close(fd) |
| | mov x0, x19 |
| | mov x8, #57 @ sys_close |
| | svc #0 |
| |
| ===== Conditional instructions ===== | @ exit(0) |
| | mov x0, #0 |
| | mov x8, #93 @ sys_exit |
| | svc #0 |
| | |
| | .section .rodata |
| | gov_path: |
| | .asciz "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor" |
| | gov_value: |
| | .asciz "powersave\n" |
| | </code> |
| | </codeblock> |
| | Similar things can be done with CPU frequencies, or even by turning off a separate core. This is just one example template that can be used to put the processor into a specific power mode. By changing the stored path in //gov_path// variable and //gov_value// value. The main idea is to use the OS's system call functions. The OS will do the rest |
| |
| ===== Power saving ===== | |