当前位置:编程学习 > 网站相关 >>

linux编程的108种奇易做图巧计-15(减少复制)

计算机的存储结构是层次性的,从快到慢,代价从高到低,容量从小到大,寄存器,L1 cache,L2 cache,直到磁盘,甚至比磁盘更慢速的磁带机,因此在程序运行时,不可避免的会有复制,这一点很重要,从优化的角度看,很多时候都是为了减少复制的代价,比如如果已知磁盘的读写是顺序的,这样采用DIRECTIO是较好的,直接读到用户内存上,而不需要先读到内核内存上,然后再复制到用户内存,这个例子我打算在未来的试验中给出,当然DirectIO相当于程序员自己实现缓存,在小规模数据读写上并没有优势,因为节省的这些复制开销微乎其微。

      本文通过减少cache line回填(也是一种复制),来说明减少复制获得的收益。首先了解一些背景知识:

      CPU通过芯片缓存(L1 Cache)和内存进行数据交互。而交互的单位叫做cache line,大小为2的幂,32或64字节,虚拟内存页面大小为4KB。cache line的交互分为两种回填(refill)和回写(write-back)两种。假定CPU需要从虚拟内存读取一个字节的操作数,其地址为0xFFFFFFA3,cache line的大小为32字节,则CPU需要将地址0xFFFFFFA0地址开始的32个字节全部读入,填充出一个完整的cache line后,然后从该cache line的第4个字节处取得该字节的内容。如果后继的指令也需要同样从cache行中读,那么填充cache line是值得的;否则,额外的填充cache line的时间就是浪费。因此指令和数据的局部性越好,越符合cache line的设计要求。
      我们都非常熟悉的memset函数,如果我们手写一个memset可能不如库函数memset的实现性能高,为什么呢?其中一个主要优化技术称之为non-temporal,其基本思想是,如果要写入的内存数据无用时,直接写入,而不需要回填cache line。涉及的指令包括movnti,movntdq,sfence等。通俗点说,我们要将一个字符(char),memset在一片内存上,而这片内存上原有的数据显然没有用了,即不需要将这片数据的内容先refill进cacheline,在cacheline中改写了然后再写回内存,只需要直接将数据写入内存即可。库函数的memset使用的是通用寄存器,而不是SSE寄存器,使用了movnti指令,通过objdump指令可以将库函数的memset代码导出,参见在本文的最后。

      为什么一定要refill呢,不refill不可以吗?假定我们对一片内存写入1个字节(假定一条cache line是32字节),数据交互的单位是cache Line,系统怎么知道另外的31个字节是什么呢?这一写回,这1个字节是对的,另外的31个字节就未定义了。因此小数据的读写refill是有必要的,但是,对于易做图内存的写入,显然refill是可以避免的,intel提供的non-temperal方法也就是为这个目的服务的,即本文的减少复制的优化思想。

       代码中还有一些技巧不再详述,如果有反馈,我再写个续篇解答,详细请参见下列代码。

view plaincopy to clipboardprint?
#include <stdlib.h>  
#include <string.h>  
#include <stdio.h>  
#include <iostream>  
typedef unsigned char __attribute__((aligned(16))) fill_t[16];  
using namespace std;  
void 易做图_memset(void *page,unsigned char fill, size_t count)  
{  
        unsigned char *dst = (unsigned char*)page;  
        unsigned char *end = dst + count;  
        for(;dst<end;)  
        {  
                *dst++ = fill;  
        };  
};  
   
void my_memset(void *page, unsigned char fill, size_t count)  
{  
        unsigned char *dst = (unsigned char*)page;  
        fill_t dfill;  
        for(size_t i = 0;i<16;)  
        {  
                dfill[i++]=fill;  
        }  
         __asm__ __volatile__ (  
                " movdqa (%0),%%xmm0 " 
                " movdqa %%xmm0,%%xmm1 " 
                " movdqa %%xmm0,%%xmm2 " 
                " movdqa %%xmm0,%%xmm3 " 
                " movdqa %%xmm0,%%xmm4 " 
                " movdqa %%xmm0,%%xmm5 " 
                " movdqa %%xmm0,%%xmm6 " 
                " movdqa %%xmm0,%%xmm7 " 
                :: "r"(dfill)  
        );  
        while (((long)dst & 0xF) && (count > 0)) {  
                *dst++ = fill;  
                count--;  
        }  
        size_t m_loop = count/128;  
        size_t r = count%128;  
        for(size_t i=0;i<m_loop;++i)  
        {  
                __asm__ (  
                "  movntdq %%xmm0, (%0) " 
                "  movntdq %%xmm1, 16(%0) " 
                "  movntdq %%xmm2, 32(%0) " 
                "  movntdq %%xmm3, 48(%0) " 
                "  movntdq %%xmm4, 64(%0) " 
                "  movntdq %%xmm5, 80(%0) " 
                "  movntdq %%xmm6, 96(%0) " 
                "  movntdq %%xmm7, 112(%0) " 
                ::"r" (dst) :"memory" );  
                dst+=128;  
        }  
        for(int i=0;i<r;++i)  
        {  
                *dst++ = fill;  
        }  
        __asm__ __volatile__ (  
               

补充:综合编程 , 其他综合 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,