linux中移动128位的memcpy
我正在为PCIe设备编写linux设备驱动程序。此设备驱动程序执行多个读写操作以测试吞吐量。当我使用memcpy时,a的最大有效负载是8字节(在64位体系结构上)。在我看来,获得16字节有效负载的唯一方法是使用SSE指令集。我已经看过了,但是代码没有编译(AT&t/Intel语法问题)linux中移动128位的memcpy,c,linux,assembly,sse,simd,C,Linux,Assembly,Sse,Simd,我正在为PCIe设备编写linux设备驱动程序。此设备驱动程序执行多个读写操作以测试吞吐量。当我使用memcpy时,a的最大有效负载是8字节(在64位体系结构上)。在我看来,获得16字节有效负载的唯一方法是使用SSE指令集。我已经看过了,但是代码没有编译(AT&t/Intel语法问题) 有一种在linux中使用该代码的方法吗 有人知道我在哪里可以找到移动128位的memcpy实现吗 首先,您可能使用GCC作为编译器,它使用asm语句作为内联汇编程序。当使用它时,您必须为汇编代码使用字符串文字(
- 有一种在linux中使用该代码的方法吗李>
- 有人知道我在哪里可以找到移动128位的memcpy实现吗
asm
语句作为内联汇编程序。当使用它时,您必须为汇编代码使用字符串文字(在发送到汇编程序之前将其复制到汇编代码中-这意味着字符串应包含换行符)
其次,您可能必须为汇编程序使用AT&T语法
第三个GCC用于在汇编程序和C之间传递变量
第四,在可能的情况下,无论如何都应该避免内联汇编程序,因为编译器不可能通过asm
语句调度指令(至少这是正确的)。相反,您可以使用GCC扩展,如vector\u size
属性:
typedef float v4sf __attribute__((vector_size(16)));
void fubar( v4sf *p, v4sf* q )
{
v4sf p0 = *p++;
v4sf p1 = *p++;
v4sf p2 = *p++;
v4sf p3 = *p++;
*q++ = p0;
*q++ = p1;
*q++ = p2;
*q++ = p3;
}
它的优点是,即使编译的处理器没有mmx
寄存器,但可能有其他128位寄存器(或者根本没有向量寄存器),编译器也会生成代码
第五,您应该调查所提供的memcpy
是否不够快。通常,memcpy
是真正优化的
第六,如果在Linux内核中使用特殊寄存器,那么应该采取预防措施,在上下文切换期间,有些寄存器不会保存。SSE寄存器是其中的一部分
第七,当你使用这个来测试吞吐量时,你应该考虑处理器是否是方程中的一个重要瓶颈。将代码的实际执行情况与从RAM读取/写入RAM(是否命中缓存?)或从外围设备读取/写入RAM进行比较
< P >第八移动数据时,应避免将大数据块从RAM移动到RAM,如果是从一个具有有限带宽的外围设备,则应该明确地考虑使用DMA来实现这一点。请记住,如果访问时间限制了性能,CPU仍将被视为繁忙(尽管它不能以100%的速度运行)。您提到的是使用非临时存储。例如,我已经讨论过好几次了。我建议您在继续之前阅读这些内容 但是,如果您真的想在这里提到的链接中生成内联汇编代码,请使用intrinsic 不能用GCC编译代码这一事实正是创建intrinsic的原因之一。对于32位和64位代码,内联程序集必须以不同的方式编写,并且对于每个编译器通常具有不同的语法。内在论解决了所有这些问题 以下代码应在32位和64位模式下使用GCC、Clang、ICC和MSVC编译#include "xmmintrin.h"
void X_aligned_memcpy_sse2(char* dest, const char* src, const unsigned long size)
{
for(int i=size/128; i>0; i--) {
__m128i xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7;
_mm_prefetch(src + 128, _MM_HINT_NTA);
_mm_prefetch(src + 160, _MM_HINT_NTA);
_mm_prefetch(src + 194, _MM_HINT_NTA);
_mm_prefetch(src + 224, _MM_HINT_NTA);
xmm0 = _mm_load_si128((__m128i*)&src[ 0]);
xmm1 = _mm_load_si128((__m128i*)&src[ 16]);
xmm2 = _mm_load_si128((__m128i*)&src[ 32]);
xmm3 = _mm_load_si128((__m128i*)&src[ 48]);
xmm4 = _mm_load_si128((__m128i*)&src[ 64]);
xmm5 = _mm_load_si128((__m128i*)&src[ 80]);
xmm6 = _mm_load_si128((__m128i*)&src[ 96]);
xmm7 = _mm_load_si128((__m128i*)&src[ 112]);
_mm_stream_si128((__m128i*)&dest[ 0], xmm0);
_mm_stream_si128((__m128i*)&dest[ 16], xmm1);
_mm_stream_si128((__m128i*)&dest[ 32], xmm2);
_mm_stream_si128((__m128i*)&dest[ 48], xmm3);
_mm_stream_si128((__m128i*)&dest[ 64], xmm4);
_mm_stream_si128((__m128i*)&dest[ 80], xmm5);
_mm_stream_si128((__m128i*)&dest[ 96], xmm6);
_mm_stream_si128((__m128i*)&dest[ 112], xmm7);
src += 128;
dest += 128;
}
}
请注意,src
和dest
需要16字节对齐,size
需要是128的倍数
但是,我不建议您使用此代码。在非时态存储有用的情况下,循环展开是无用的,显式预取很少有用。你可以简单地做
void copy(char *x, char *y, int n)
{
#pragma omp parallel for schedule(static)
for(int i=0; i<n/16; i++) {
_mm_stream_ps((float*)&y[16*i], _mm_load_ps((float*)&x[16*i]));
}
}
现在把这个答案留在这里,尽管现在很清楚OP只需要一次16B的传输。在Linux上,他的代码导致通过PCIe总线进行两次8B传输 对于写入MMIO空间,值得尝试
moventi
write组合存储指令。moventi
的源操作数是GP寄存器,而不是向量寄存器
如果在驱动程序代码中包含,则可以使用内部函数生成。这在内核中应该是很好的,只要您小心使用什么内部函数。它没有定义任何全局变量
所以这一节的大部分内容都不是很相关 在大多数CPU上(其中
rep-mov
很好)。它只对CPU使用显式循环的回退,而rep movsq
或rep movsb
不是好的选择
当大小是编译时常量时,使用rep-movsl
(AT&T语法用于rep-movsd
),然后进行清理:非rep
movsw
和movsb
(如果需要)。(在我看来,实际上有点笨重,因为大小是一个编译时常数。在拥有它的CPU上也没有利用fastrep movsb
)
自P6以来,英特尔CPU至少有相当好的rep-mov
实现。看
但是,你仍然错误地认为memcpy只在64位块中移动,除非我误读了代码或者你所在的平台决定使用回退循环
无论如何,我认为使用普通的Linuxmemcpy
,你不会错过很多性能,除非你真的单步执行了你的代码,看到它做了一些愚蠢的事情
对于大拷贝,您仍然需要设置DMA。驱动程序的CPU使用率很重要,而不仅仅是在其他空闲系统上可以获得的最大吞吐量。(小心过度信任微基准点。)
在内核中使用SSE意味着保存/恢复向量寄存器。这对于RAID5/RAID6代码来说是值得的。该代码可能只能从专用线程运行,而不是从vector/FPU寄存器仍有另一个进程数据的上下文运行 Linux的memcpy可以在任何上下文中使用,因此它避免使用除通常的整数寄存器之外的任何东西。我确实找到了,Andi Kleen和Ingo Molnar都说在memcpy中总是使用SSE是不好的。
shr rdx, 7
test edx, edx
mov eax, edx
jle .L1
.L5:
sub rsi, -128
movdqa xmm6, XMMWORD PTR [rsi-112]
prefetchnta [rsi]
prefetchnta [rsi+32]
prefetchnta [rsi+66]
movdqa xmm5, XMMWORD PTR [rsi-96]
prefetchnta [rsi+96]
sub rdi, -128
movdqa xmm4, XMMWORD PTR [rsi-80]
movdqa xmm3, XMMWORD PTR [rsi-64]
movdqa xmm2, XMMWORD PTR [rsi-48]
movdqa xmm1, XMMWORD PTR [rsi-32]
movdqa xmm0, XMMWORD PTR [rsi-16]
movdqa xmm7, XMMWORD PTR [rsi-128]
movntdq XMMWORD PTR [rdi-112], xmm6
movntdq XMMWORD PTR [rdi-96], xmm5
movntdq XMMWORD PTR [rdi-80], xmm4
movntdq XMMWORD PTR [rdi-64], xmm3
movntdq XMMWORD PTR [rdi-48], xmm2
movntdq XMMWORD PTR [rdi-128], xmm7
movntdq XMMWORD PTR [rdi-32], xmm1
movntdq XMMWORD PTR [rdi-16], xmm0
sub eax, 1
jne .L5
.L1:
rep ret