我需要在c中优化我的混合代码以获得更快的响应时间,因此我决定使用内联汇编将两个缓冲区混合到一个新的更大的缓冲区中。基本上我有左右声道分开,我想把它们放在一个缓冲区。所以我需要从左声道输入2个字节,然后从右声道输入两个字节,依此类推。 为此,我决定将我的3个指针发送到我的汇编代码,我打算将左通道指针指向的内存复制到R0寄存器和右通道指针指向R1的内存之后我打算将R0和R1混合到R3和R4中之后将这些寄存器保存到内存中。(我打算使用其他免费寄存器来执行相同的操作,并通过流水线操作减少处理时间)
所以我有两个寄存器R0和R1带有数据,需要将它们混合到R3和R4中,我需要最终得到R3 = R0HI(高部分)+ R1HI(高部分)和R4 = R0LO (低部分)+ R1LO(低部分)
我可以考虑使用按位移位,但我的问题是,如果有一种更简单的方法可以像在intel x86架构中那样将数据传输到ax寄存器然后我们将其作为高分和低分一部分?
我的想法是对的吗?有更快的方法吗?
我在ndk中的实际(不工作)代码
void mux(short *pLeftBuf, short *pRightBuf, short *pOutBuf, int vecsamps_stereo) {
int iterations = vecsamps_stereo / 4;
asm volatile(
"ldr r0, %[outbuf];"
"ldr r1, %[leftbuf];"
"ldr r2, %[rightbuf];"
"ldr r3, %[iter];"
"ldr r4, [r3];"
"mov r8, r4;"
"mov r9, r0;"
"mov r4, #0;"
"mov r10, r4;"
"loop:; "
"ldr r2, [r1];"
"ldr r3, [r2];"
"ldr r7, =0xffff;"
"mov r4, r2;"
"and r4, r4, r7;"
"mov r5, r3;"
"and r5, r5, r7;"
"lsl r5, r5, #16;"
"orr r4, r4, r5;"
"lsl r7, r7, #16;"
"mov r5, r2;"
"and r5, r5, r7;"
"mov r6, r3;"
"and r6, r6, r7;"
"lsr r6, r6, #16;"
"orr r5, r5, r6;"
"mov r6, r9;"
"str r4, [r6];"
"add r6, r6, #1;"
"str r5, [r6];"
"add r6, r6, #1;"
"mov r9, r6;"
"mov r4, r10;"
"add r4, r4, #1;"
"mov r10, r4;"
"cmp r4, r8;"
"blt loop"
:[outbuf] "=m" (pOutBuf)
:[leftbuf] "m" (pLeftBuf) ,[rightbuf] "m" (pRightBuf),[iter] "m" (pIter)
:"r0","r1","r2","r3","memory"
);
}
答案 0 :(得分:1)
ARM寄存器严格来说是32位的,但如果您使用的是最近的核心(v6 +,但不是Thumb-only v7-M),则有许多合适的指令可用于处理半字(PKHBT
, PKHTB
),或者任意的寄存器片段(BFI
,UBFX
),更不用说疯狂的并行加/减指令吓到我了(可用于音频的饱和算术也可用)..
但是,如果您的机器实现了NEON指令,那么它们将成为实现最佳实现的途径,因为完全是他们设计的那种东西。另外,它们应该可以通过编译器内在函数访问,因此您可以直接在C代码中使用它们。
答案 1 :(得分:1)
我可能不会100%清楚你想要做什么,但看起来你想要:
R3[31:16] = R0[31:16], R3[15:0] = R1[31:16];
R4[31:16] = R0[15:0], R4[15:0] = R1[15:0];
而不是实际的总和。
在这种情况下,您应该能够使用16位掩码的备用寄存器来相对有效地完成此操作。 ARM程序集提供第二个操作数的移位,作为大多数算术或逻辑指令的一部分。
MOV R2, 0xffff ; load 16-bit mask into lower half of R2
AND R3, R2, R1, LSR #16 ; R3 = R2 & (R1 >> 16), or R3[15:0] = R1[31:16]
ORR R3, R3, R0, LSR #16 ; R3 = R3 | (R0 >> 16), or R3[31:16] = R0[31:16]
AND R4, R2, R1 ; R4 = R2 & R1, or R4[15:0] = R1[15:0]
ORR R4, R4, R0, LSL #16 ; R4 = R4 | (R1 << 16), or R4[31:16] = R0[15:0]
; repeat to taste
另一种选择是一次只加载16位,但如果缓冲区在慢速内存中,这可能会降低性能,如果它不支持小于32位的访问,它可能根本不起作用。我不确定核心是否会请求32位并屏蔽掉不需要的内容,或者它是否依赖内存来处理字节通道。
; assume R2 contains CH1 pointer, R3 contains CH2 pointer,
; and R1 contains output ptr
LDRH R0, [R2] ; load first 16 bits pointed to by CH1 into R0
STRH R0, [R1] ; store those 16 bites back into *output
LDRH R0, [R3] ; load first 16 bits pointed to by CH2 into R0
STRH R0, [R1, #2]! ; store those 16 bites back into *(output+2),
; write output+2 to R1
; after "priming" we can now run the following for
; auto increment of pointers.
LDRH R0, [R2, #2]! ; R0 = *(CH1+2), CH1 += 2
STRH R0, [R1, #2]! ; *(Out+2) = R0, Out += 2
LDRH R0, [R3, #2]! ; R0 = *(CH2+2), CH1 += 2
STRH R0, [R1, #2]! ; *(Out+2) = R0, Out += 2
; Lather, rinse, repeat.
这两个示例使用了ARM程序集的一些便利功能。第一个示例使用大多数指令可用的内置移位,而第二个示例使用大小的加载/存储指令以及这些指令的回写。这些都应该与Cortex-M内核兼容。如果你有更高级的ARM,@ Notlikethat的答案更合适。
就代码大小而言,当您将加载和存储添加到第一个示例时,最终会执行两个加载指令,四个逻辑指令和两个存储,总共八个指令用于混合两个样本。第二个示例在混合一个样本时使用两个加载和两个存储,总共四个指令,或者,八个指令用于混合两个。
您可能会发现第一个示例工作得更快,因为它具有更少的内存访问,并且可以通过使用STM
存储多指令(即STMIA OutputRegister!, {R3, R4}
)来减少存储的数量。实际上,第一个例子可以通过使用8个寄存器来流水线化。 LDMIA
可用于在一条指令中从通道加载四个16位样本,执行两组四条混合指令,然后将四个输出寄存器存储在一条STMIA
指令中。这可能不会在性能上提供太多好处,因为它可能以相同的方式与内存交互(STM
和LDM
只执行多个LDR
和STR
s ),但如果你正在优化最小指令,这将导致11条指令混合四个样本(与16相比)。