linux中移动128位的memcpy

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语句作为内联汇编程序。当使用它时,您必须为汇编代码使用字符串文字(

我正在为PCIe设备编写linux设备驱动程序。此设备驱动程序执行多个读写操作以测试吞吐量。当我使用memcpy时,a的最大有效负载是8字节(在64位体系结构上)。在我看来,获得16字节有效负载的唯一方法是使用SSE指令集。我已经看过了,但是代码没有编译(AT&t/Intel语法问题)

  • 有一种在linux中使用该代码的方法吗
  • 有人知道我在哪里可以找到移动128位的memcpy实现吗

首先,您可能使用GCC作为编译器,它使用
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上也没有利用fast
rep movsb

自P6以来,英特尔CPU至少有相当好的
rep-mov
实现。看

但是,你仍然错误地认为memcpy只在64位块中移动,除非我误读了代码或者你所在的平台决定使用回退循环

无论如何,我认为使用普通的Linux
memcpy
,你不会错过很多性能,除非你真的单步执行了你的代码,看到它做了一些愚蠢的事情

对于大拷贝,您仍然需要设置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