精华内容
下载资源
问答
  • Cache优化

    千次阅读 2019-05-15 22:49:02
    优异的cache性能很大程度上依赖于cache lines(缓存行)的重复使用,优化的最主要目标也在于此,一般通过恰当的数据和代码内存布置,以及调整CPU的内存访问顺序来达到此目的。由此,应该熟悉cache内存架构,特别是...

    一、cache性能特点

        优异的cache性能很大程度上依赖于cache lines(缓存行)的重复使用,优化的最主要目标也在于此,一般通过恰当的数据和代码内存布置,以及调整CPU的内存访问顺序来达到此目的。由此,应该熟悉cache内存架构,特别是cache内存特点,比如line size, associativity, capacity, replacement scheme,read/write allocation, miss pipelining和write buffer.另外,还需要知道什么条件下CPU STALLS会发生以及产生延时的cycle数。只有清楚了这些,才能清楚如何优化cache。

    二、优化cache

        L1 cache的特点(capacity、associativity、linesize)相对于L2 cache来说更具局限性,优化了L1 cache几乎肯定意味着L2 cache也能得到有效使用。通常,仅优化L2 cache效果并不理想。建议将L2 cache用于一般的类似控制流程等大量内存访问无法预测的部分。L1和L2 SRAM应该用于时间性非常重要的信号处理算法。数据能够用EDMA或IDMA直接导入L1 SRAM,或用EDMA导入L2 SRAM。这样,可使L1 cache的mem访问效率获得优化。

        有两种重要方法来减少cache ovehead:
    1. 通过以下方式减少cache miss数量(L1P,L1D,L2 cache):
        a. cache line reuse最大化
        >访问cached行中的所有mem位置(应该是对多路组相联才有效,直接映射地址是一对一的)。进入cache行中的数据花费了昂贵的stall cycles,应该被使用;
        >cached line中的同一内存位置应该尽可能的重复使用。
        b. 只要一行被使用,将要避免牺牲该行
    2. 利用miss pipelining,减少每次miss的stall cycles数

        cache优化的最好策略是从上到下的方式,从应用层开始,到程序级,再到算法级别的优化。应用层的优化方法通常易于实现,且对整体效果改善明显,然后再配合一些低层次的优化策略。这也是通常的优化顺序。

    应用层级应考虑的几点:
        >用DMA搬进/出数据,DMA buffer最好分配在L1或L2 SRAM,出于以下考虑。首先,L1/L2 SRAM更靠近CPU,可以尽量减少延迟;其次,出于cache一致性的考虑。
        >L1 SRAM的使用。C64x+提供L1D 和L1P SRAM,用于存放对cache性能影像大的代码和数据,比如:
        @ 至关重要的代码或数据;
        @ 许多算法共享的代码或数据;
        @ 访问频繁的代码或数据;
        @ 代码量大的函数或大的数据结构
        @ 访问无规律,严重影像cache效率的数据结构;
        @ 流buffer(例如L2比较小,最好配置成cache)
        因为L1 SRAM有限,决定哪些代码和数据放入L1 SRAM需要仔细考虑。L1 SRAM 分配大,相应L1 cache就会小,这就会削弱放在L2和外部内存中代码和数据的效率。 如果代码和数据能按要求导入L1 SRAM,利用代码和/或数据的重叠,可以将L1 SRAM设小点。IDMA能够非常快的将代码或数据page到L1。如果代码/数据是从外部page进来,则要用EDMA。但是,非常频繁的paging可能会比cache增加更多的overhead。所以,必须在SRAM和cache大小之间寻求一个折中点。

        >区别signal processing 和 general-purpose processing 代码
        后者通常并行性不好,执行过程依赖于许多条件,结果大多无法预测,比如滤波模块,数据内存访问大多随机,程序内存访问因分支条件而异,使得优化相当困难。鉴于此,当L2不足以放下整个代码和数据时,建议将其代码和数据放到片外,并允许L2 能cache访问到。这样腾出更多的L2 SRAM空间存放易于优化,结构清晰的前者代码。由于后者代码的无法预测性,L2 cache应该是设的越大越好(32k~256k). 前者比较有规律的代码和数据放到L2 SRAM或L1 SRAM更为有利。放到L2,可以允许你根据CPU对数据的访问方式来修改算法,或调整数据结构,以获得更好的cache友好性。放到L1 SRAM,无需任何cache操作,并且除非bank冲突,无需做memory 优化。

    procedural(程序)级的优化:
        优化目的是减少cache miss,以及miss带来的stall数。前者可通过减少被cache的内存大小并重复使用已经cached lines来获得。尽量避免牺牲行并尽可能写已经分配的行可以提高重用率。利用miss pipelining可以减少stall数。以下根据三种不同类型的读miss来分析优化的方法。
        >选用合适的数据类型,以减少内存需要
       16位可以表示的数不要定义成32位,这不但可以省一半内存消耗,而且减少compulsory miss。这种优化容易修改,无需改动算法,而且小数据类型容易实现汇编的SIMD。在不同系统平台端口间的数据流动,容易出现这种低效的数据类型。
        >处理链
        前一算法的输出是后一算法的输入。如果输出、输入不是同一级内存地址,后一算法使用前一算法结果时就存在读miss的消耗。这个时候就要考虑两者空间如何布置。如果超过两个数组映射到L1D的同一个set,则会产生conflict miss(L1D cache是2-way set-associative),故应该将这些数组连续分配(详见P55)
        >避免L1P conflict miss
        即使cpu需要的指令全在L1P cache(假定无capacity miss),仍然可能会产生conflict miss。以下解释conflict miss是如何产生的,又如何通过code在内存中的连续存放来消除miss。例如:
        for(i=0; i<N; i++)
        {   function_1();   function_2(); }
        如果func2在L2中的位置正好与func1有部分处于同一set中,而L2 cache是4-way set-associativity,处于同一set的指令在被L1P cache循环读取后,可能会出现conflict miss(如刚读入func1,然后读入func2,可能会驱逐掉func1在L1P中的部分cache lines).这种类型的miss是完全可以消除掉的,通过将这两个函数的代码分配到不冲突的set中,最直接具体的方法是将这两个函数在内存中连续存放,存放的方法有

    二:
    1. 使用编译器选项 -mo,将各C和线性汇编函数放到各自独立的section,其中汇编函数必须被放到以.sect标示的sections中。然后检查map file,获取各函数的段名,比如上例.text:_function1和.text:function_2。则linker命令文件如下:
        ...   
        SECTIONS
        { .cinit   > L2SRAM
            .GROUP   > L2SRAM      (在CCS3.0及以后,.GROUP标示用于强制指定段的link顺序)
            {   .text:_function1 .text:function_2
                .text
            }
            .stack   > L2SRAM
            .bss     > L2SRAM
            ...
        }
        linker会严格按照GROUP申明的顺序来link各段。上例中,先func1,然后是func2,然后是.text section中的其它函数。但要注意,使用-mo后会导致整个code尺寸变大,因为包含code的任何段都要按32-byte边界对齐。

    2. 为避免-mo只能指定section,而不能单独指定函数的不足,如果仅需要函数连续排放,我们可以在定义函数前,通过#pragma CODE_SECTION来为函数指定sections:
        #pragma CODE_SECTION(function_1,".funct1")
        #pragma CODE_SECTION(function_2,".funct2")
         void function_1(){...}
         void function_2(){...}
        这样,linker命令文件如下:
        ...
        SECTIONS
        {
           .cinit > L2SRAM
           .GROUP > L2SRAM
             {
                 .funct1.funct2
                 .text
             }
          .stack > L2SRAM
          ...
        }
        结合上例可见,在同一循环里面或在某些特定时间帧里面反复调用的多个函数,需要考虑重排。如果L1P cache不够大,不足以放下所有循环内函数,则循环必须被拆开来,以保证code无驱逐的重用。但这会增大内存消耗,上函数拆分成如下:
        for (i=0; i<N; i++)
          {    function_1(in[i], tmp[i]);   }      //++很显然需要增大tmp[],以保存func1每
        for (i=0; i<N; i++)                          //++次循环的输出结果,作为func2的输入
          {    function_2(tmp[i], out[i]); }

        >freezing L1P cache
        调用CSL函数: CACHE_freezeL1p()与CACHE_unfreezeL1p()可以控制L1P cache,阻止其分配新行,freezing后,cache内容就不会因conflict而牺牲,但其他所有如dirty比特、LRU更新、snooping等等cache行为仍然是一样的。肯定会被重用的code,如果因为其他仅执行一次的code而被驱逐掉,比如中断程序等,可以采用这个函数来避免。

        >避免L1D conflict miss
        L1P是直接映射型cache,如果cpu访问的地址没有包含在同一cache line内,则会相互evict。然而,L1D是2-way set-associative,对直接映射来说是conflict 的两lines却能够同时保存在cache中,只有当第三个被访问分配的memory地址仍映射到同一set时,早前分配的两个cache lines将根据LRU规则牺牲掉一行。L1D的优化方法与上面L1P类似,区别在于前者是2-way set-associative,而后者是direct-mapped,这意味着对L1D,两个数组能够映射到同一set,并同时保存在L1D。
        @定义数组后,通过编译选项-m生成map file可以查看给该数组分配的地址。
        与L1P类似,如果不连续定义数组,会导致各种miss(具体各数组是如何映射到L1D cache各way各set的,没看明白,P61),为避免读miss,应在内存中连续分配各数组。注意,因为linker的内存分配规则,在程序中连续定义数组,并不表示他们在内存中的地址也是连续的(比如,const数组会放在.const section而非.data section中)!因此,必须将数组指定到用户定义的段:
        #pragma DATA_SECTION(in1, ".mydata")
        #pragma DATA_SECTION(in2, ".mydata")
        #pragma DATA_SECTION(w1, ".mydata")
        #pragma DATA_SECTION(w2, ".mydata')
        #pragma DATA_ALIGN(in1, 32)     //++ 数组按照cache line边界对齐
         short in1 [N];
         short in2 [N];
         short w1 [N];
         short w2 [N];

        @另注意:为避免memory bank冲突,非常有必要将数组按不同memory bank对齐,如:
        #pragma DATA_MEM_BANK(in1, 0)
        #pragma DATA_MEM_BANK(in2, 0)
        #pragma DATA_MEM_BANK(w1, 2)
        #pragma DATA_MEM_BANK(w2, 2)
        
        @利用miss pipelining可以进一步减少miss stalls。利用touch loop来为四个数组在L1D cache中预分配空间,因为数组物理连续,故只需调用一次touch程序:
        touch(in1, 4*N*sizeof(short));
        r1 = dotprod(in1, w1, N);
        r2 = dotprod(in2, w2, N);
        r3 = dotprod(in1, w2, N);
        r4 = dotprod(in2, w1, N);
        ====>touch loop的意义和实现:意义是为了最大限度实现miss piplining。如果连续访问mem,因为一次miss,会搬移一个cacheline,则随后的访问就会hit,miss不能实现overlap。因此,为获得stalls的完全重叠,可以考虑在一个cycle内同时访问两个新的cacheline,即按两个cachelines的间距遍历mem。TI提供的汇编函数“touch”,用于在L1D cache中预先分配长为length的数组buffer,它对每两个连续cache lines 分别并行load一个byte。为避免bank conflict,这两个并行load之间偏移一个word。 (c64x采用基于LSB的mem bank结构,L1D分成8个bank,每个bank宽32-bit,共2K,这些bank均为single port输入,每个cycle允许一个访问,与c621x/c671x的单bank多输入口有区别。这样,对同一bank同时进行读和写访问,总是会造成stall,而同时对同一bank进行读或写,只要满足一定条件,就不会产生stall)。

        >避免L1D thrashing ---具体图示详见two-level-->3-38
        这种Miss情况下,数据集比cache大,连续分配,但数据不需要reused,发生conflict miss,但无capacity miss发生(因为数据不reused)。 对同一set发生两个以上的读miss,这样在该行全部数据被访问前就将该行驱逐掉了,这种情况就是thrashing.假定所有数据在mem中是连续分配的,这样只有当被访问的所有数据集超过L1D cache容量时才会发生thrashing.这种conflict miss是可以完全避免的,通过在mem中连续分配数据集,并嵌入一些多余数组,强制将数据交叉映射到cache sets。比如:
        int w_dotprod(const short *restrict w, const short *restrict x, const short *restrict h, int N)
        { int i, sum = 0;
           _nassert((int)w % 8 == 0);      //++如果w[],x[],h[]三个数组在内存中都映射到
           _nassert((int)x % 8 == 0);      //++同一L1D cache set,则L1D thrashing发生。当前读入
           _nassert((int)h % 8 == 0);      //++的w,x被随后读入的h给替换掉了....
           #pragma MUST_ITERATE(16,,4)
           for (i=0; i<N; i++)
               sum += w[i] * x[i] * h[i];
           return sum;   }
        处理办法是在w,x后填充一个cache行大小的数,使h[0]往下偏移一行,映射到下一set:
           #pragma DATA_SECTION(w, ".mydata")
           #pragma DATA_SECTION(x, ".mydata")
           #pragma DATA_SECTION(pad, ".mydata")
           #pragma DATA_SECTION(h, ".mydata")
           #pragma DATA_ALIGN (w, CACHE_L1D_LINESIZE)
            short w [N];
            short x [N];
            char pad [CACHE_L1D_LINESIZE];
            short h [N];
        对应linker命令文件如下指定:
           ...
           SECTIONS
           { GROUP > L2SRAM
                 {   .mydata:w
                     .mydata:x
                     .mydata:pad
                     .mydata:h   }
              ...
           }

        对应于我们的应用,L1D如果设cache为32k,放在DDR中的重建或插值帧数据应该考虑到以上问题,一帧的重建数据远大于32k,这时必然需要考虑被处理数据在DDR中怎么排放才能避免cache中Line evict。为了能够明确数据在cache的set映射,应该将DDR划分成16k的CE小段,然后数据按小段布置,这样容易做到主观对应也便于操作数据(这估计也是示例中linker划分很多CE external段的原因)。                  原来的理解是错误的,首先LRU策略理解有误,它只针对行,而非全部cache空间;在编译连接重定位后,不管是数据还是指令,都有了确定的地址和大小,以及在memory中的位置,这样它们到cache的映射set也已经确定下来了,故理论上通过地址排放,我们应该能确定的了解到数据和代码在cache中的具体运行情况,知道了这些,就可以针对性的采取措施以尽量避免miss和stall了。

        >避免capacity miss---具体图示见two-level-->3-41
        这种情况下,数据需要重用reused,但是数据集比cache大,造成capacity和conflict miss。通过分裂数据集,一次处理一个数据子集,可以避免这种miss,这种方法叫做blocking或tiling. 下面以例子说明原因和处理方法。点积函数,调用4次,一个参考矢量,四个不同的输入矢量。
        short in1[N];
        short in2[N];
        short in3[N];
        short in4[N];
        short w [N];
        r1 = dotprod(in1, w, N);
        r2 = dotprod(in2, w, N);
        r3 = dotprod(in3, w, N);
        r4 = dotprod(in4, w, N);
        假定每个数组都是L1D cache容量的两倍,对w来说,除了第一次调用需要compulsory miss外,之后应该保存在cache里面重用是最合理的,但分析可知,当处理到N/4的输入数据时,最先进入cache的w就开始被驱逐了,这样w将会被反复多次读入cache,非常浪费。可以考虑加个循环,每次只处理N/4的数据,保证w在读进cache后,直到用完才驱逐,修改如下:
        for (i=0; i<4; i++)             {
            o = i * N/4;
            dotprod(in1+o, w+o, N/4);
            dotprod(in2+o, w+o, N/4);
            dotprod(in3+o, w+o, N/4);
            dotprod(in4+o, w+o, N/4);   }
        可以利用miss pipelining进一步减少read miss stalls.在每次循环开始用touch循环预先在cache分配w[],这样在每次调用点积函数前,需要的输入数组都准备好了:
        for (i=0; i<4; i++)                            {
            o = i * N/4;
            touch(w+o, N/4 * sizeof(short));
            touch(in1+o, N/4 * sizeof(short));
            dotprod(in1+o, w+o, N/4);
                  touch(w+o, N/4 * sizeof(short));   //++每次touch in[]前都要touch w[]是为了保证w[]为MRU,
                  touch(in2+o, N/4 * sizeof(short)); //++以防访问顺序发生改变导致w[]被驱逐掉。
                  dotprod(in2+o, w+o, N/4);
            touch(w+o, N/4 * sizeof(short));
            touch(in3+o, N/4 * sizeof(short));
            dotprod(in3+o, w+o, N/4);
                   touch(w+o, N/4 * sizeof(short));
                   touch(in4+o, N/4 * sizeof(short));
                   dotprod(in4+o, w+o, N/4);              }
        另外,本例中为避免bank conflict,数组w[]和in[]应该对齐到不同的memory banks:
            #pragma DATA_SECTION(in1, ".mydata")
            #pragma DATA_SECTION(in2, ".mydata")
            #pragma DATA_SECTION(in3, ".mydata")
            #pragma DATA_SECTION(in4, ".mydata")
            #pragma DATA_SECTION(w , ".mydata")

            #pragma DATA_ALIGN(w, CACHE_L1D_LINESIZE) //++意味着已#pragma DATA_MEM_BANK(w, 0)
             short w [N];
            #pragma DATA_MEM_BANK(in1, 2)                        //++avoid bank conflicts
             short in1[N];
             short in2[N];
             short in3[N];
             short in4[N];

        这个例子in1~in4的容量为L1D cache的两倍,假如为32k,这样,假定已经从头对齐,按照上面变量的定义顺序,则in1的0~8k将映射到 cache的way0-set0开始,9~16k映射到 cache的way1-set0开始,17~24k映射到 cache的way0-set0开始,25~32k映射到 cache的way1-set0开始,in2的0~8k映射到 cache的way0-set0开始......这样布置后,w刚好从way0-set0开始映射。由此,dotprod(in1,w,N)开始后,in1与w分别进入set0-way0/way1,......到N/4时,超过cache的8k/way容量,9~16k的In1开始进入set0-way0,这是合理可接受的,因为in1的前8k line data已经不再需要了;但同时w的9~16k也开始进入set0-way1,将其前8k的line data替换掉了,这就不合理了,因为后面计算in2的时候还需要用到w的0~8k。这样分析后,可见使用一个简单循环就避免了这个问题。

        这个例子的启示是:数据排放不变(有时需要定义的数组太大,必须连续的空间,排放时不方便灵活处理),通过改变程序,从而改变使用数据的顺序,一样可以达到一line data进入cache后,直到用完才释放的cache使用终极目的。

        >避免write buffer相关的stalls
        WB只有4个入口,且深度有限,如果WB满,而又出现写miss,则CPU就会stall直到有WB有空间为止。同时,read miss会使得write buffer完全停止,因此保证正确的read-after-write顺序非常重要(read miss需要访问的数据很可能仍然在WB中)。通过在L1D cache中分配输出buffer(事先将输出buffer cache进入L1D),可以完全避免WB相关的stalls,这样write操作会在输出buffer中hit,而非由WB写出。事实上,输出buffer是在循环执行过程中逐渐进入L1D的,在此过程中还是会存在read miss的。
         void vecaddc(const short *restrict x, short c, short *restrict r, int nx)
         {       int i;
                 for (i = 0 ; i < nx; i++)
                 r[i] = x[i] + c;
         }
        /     
        {
                      short in[4][N];
                      short out [N];
                      short ref [N];
                      short c, r;
                     for (i=0; i<4; i++)
                     {
                       vecaddc(in[i], c, out, N);
                       r = dotprod(out, ref, N);
                      }
        }

    展开全文
  • Cache 优化

    千次阅读 2009-08-19 21:43:00
    3 基于cache的程序优化  3.1 应用级优化 (application-level optimization)  1) 合理设置cache大小,尽量将DMA用到的buffer开在片内RAM上  2) 将一般性程序代码和数据放到片外RAM,将DSP型代码和数据放到L2RAM...

    对于一般的CPU信号处理加上外围设备的DMA数据读写情况,处理方法是在内存中开辟pingpong缓冲区。inbufa,outbufa,inbufb,outbufb四块内存区,当CPU处理inbufa的数据并将产生的结果放到outbufa,这是外围器件DMA搬移数据到inbufb,另一外围设备将数据从outbufb搬出;同理当CPU处理inbufb并将产生的结果放到outbufb时,外围设备利用DMA分别从inbufa和outbufa搬入数据和搬出数据。一般L2RAM DMA实现代码如下:
           for (i=0; i<(DATASIZE/BUFSIZE)–2; i+=2)
    {
    /* –––––––––––––––––––––––––––––––––––––––––––––––––––– */
    /* InBuffA –> OutBuffA Processing */
    /* –––––––––––––––––––––––––––––––––––––––––––––––––––– */
    <DMA_transfer(peripheral, InBuffB, BUFSIZE)>
    <DMA_transfer(OutBuffB, peripheral, BUFSIZE)>
    process(InBuffA, OutBuffA, BUFSIZE);
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    /* InBuffB –> OutBuffB Processing */
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    <DMA_transfer(peripheral, InBuffA, BUFSIZE)>
    <DMA_transfer(OutBuffA, peripheral, BUFSIZE)>
    process(InBuffB, OutBuffB, BUFSIZE);
    }
          上面CACHE的一致性由CPU自动管理,无须程序员设置,而下面要说的片外RAM开辟双缓冲区时要求程序员掌握L2cache 和片外RAM coherence(一致性)以及L2RAM和L1D的一致性,否则程序会出错。
          对于C64x系列,无论什么时候当片外设备DMA写片外RAM开辟的inbuf时,都要使用CACHE_invL2(InBuffB, BUFSIZE, CACHE_WAIT),使L1D当中的inbuffb无效;当片外设备DMA读片外RAM开辟的outbuf时,都要使用CACHE_wbL2(OutBuffB, BUFSIZE, CACHE_WAIT),使L1D当中的相应数据write back 到片外RAM .
           External Memory DMA Double Buffering Code Example
    for (i=0; i<(DATASIZE/BUFSIZE)–2; i+=2)
    {
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    /* InBuffB –> OutBuffB Processing */
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    CACHE_wbInvL2(InBuffB, BUFSIZE, CACHE_WAIT);
    <DMA_transfer(peripheral, InBuffB, BUFSIZE)>
    CACHE_invL2(OutBuffB, BUFSIZE, CACHE_WAIT);
    <DMA_transfer(OutBuffB, peripheral, BUFSIZE)>
    process(InBuffA, OutBuffA, BUFSIZE);
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    /* InBuffA –> OutBuffA Processing */
    /* ––––––––––––––––––––––––––––––––––––––––––––––––––––– */
    CACHE_wbInvL2(InBuffA, BUFSIZE, CACHE_WAIT);
    <DMA_transfer(peripheral, InBuffA, BUFSIZE)>
    CACHE_invL2(OutBuffA, BUFSIZE, CACHE_WAIT);
    <DMA_transfer(OutBuffA, peripheral, BUFSIZE)>
    process(InBuffB, OutBuffB, BUFSIZE);
    }
          虽然我们可以指定一定大小的buf被write back或者invalidate或者write back+invalidate,但是cache controller是对完整的line操作,这就要求我们在片外RAM开辟内存( 或者数组)作buffer时,尽量使得其大小是CACHE_L2_LINESIZE(128)的整数倍并且CACHE_L2_LINESIZE对齐,为此我们需要:

          #pragma DATA_ALIGN(InBuffA, CACHE_L2_LINESIZE)
          #pragma DATA_ALIGN(InBuffB, CACHE_L2_LINESIZE)
          #pragma DATA_ALIGN(OutBuffA,CACHE_L2_LINESIZE)
          #pragma DATA_ALIGN(OutBuffB,CACHE_L2_LINESIZE)

    来对齐buffer。
            使用宏#define CACHE_ROUND_TO_LINESIZE(cache,elcnt,elsize) /
                                                                                     ((CACHE_#cache#_LINESIZE * /
                                                                                           ((elcnt)*(elsize)/CACHE_#cache#_LINESIZE) + 1) / /
                                                                                                 (elsize))
    来对其内存,使用如下;
    unsigned char InBuffA [CACHE_ROUND_TO_LINESIZE(L2, N, sizeof(unsigned char)];
    unsigned char OutBuffA[CACHE_ROUND_TO_LINESIZE(L2, N, sizeof(unsigned char)];
    unsigned char InBuffB [CACHE_ROUND_TO_LINESIZE(L2, N, sizeof(unsigned char)];
    unsigned char OutBuffB[CACHE_ROUND_TO_LINESIZE(L2, N, sizeof(unsigned char)];
    这样我们得到的数组就是内存对齐并且其大小是CACHE_L2_LINESIZE的整数倍,尽管这样做浪费片外RAM。由于L2CACHE最大256K,所以如果生命的数组大小超过256K,就需要多用几次cache write back和invalidate函数。
           如果程序没有配置L2CACHE,也就是说片内256K都作为RAM,那么当利用片外RAM开辟buffer时要考虑的L1D和L1P与片外设备的一致性问题,也有一组相应的write back和invalidate函数对应。

    3    基于cache的程序优化
        3.1    应用级优化 (application-level optimization)
           1) 合理设置cache大小,尽量将DMA用到的buffer开在片内RAM上
           2) 将一般性程序代码和数据放到片外RAM,将DSP型代码和数据放到L2RAM。所谓一般性代码是指带有很多条件分支转移的指令,程序执行在空间上有随意性,不利于流水线的形成,外在片外可以发挥L2CACHE 4 way的优势。DSP型代码是指算法型的代码,放在L2RAM,CPU stall 时间少,可以充分发挥DSP速度快的优势。
        3.2 程序级优化(procedural-level optimization)
           1)选择合适的数据类型。能用short就不要用int。
           2)将同一个函数要处理的数据尽量在内存中连续存放。
        3.3避免L1P read miss
           这种情况发生在一个循环体中有两个或以上的函数要执行,要利用#pragma DATA_SECTION伪指令将和CMD文件将其在内存中相邻定位,这样不会发生两个程序对应L1P中相同line所造成的冲突缺失。
          若果循环体中的两个函数大小超过L1P容量,将这两个函数分别放到两个循环体中。这样做会造成中间数据变量的加大。
          3.4避免L1D read miss
           利用#pragma DATA_SECTION伪指令将函数要同时处理的数组在内存中相邻存放。最好再用#pragma DATA_MEM_BANK 将数组内存对齐

    展开全文
  • cache 优化基础

    2008-11-07 10:50:33
    简单介绍了dsp中的cache原理,为做Cache优化提供理论基础。
  • 基于TMS320DM642的视频编码Cache优化策略、电子技术,开发板制作交流
  • cache优化文章整理

    2011-07-20 21:48:19
    1.万方数据上的一篇论文,据说进过DMA优化后,硬件仿真软件仿真的编码速率,该方法待验证http://d.wanfangdata.com.cn/Periodical_dqdzjxxb200902019.aspx2.对cache优化的几个方面做了很好的介绍,讲得很详细http://

    1.万方数据上的一篇论文,据说进过DMA优化后,硬件仿真软件仿真的CPU周期数相差很小,该方法亟待测试和验证

    http://d.wanfangdata.com.cn/Periodical_dqdzjxxb200902019.aspx

    2.对cache优化的几个方面做了很好的介绍,讲得很详细

    http://www.cnblogs.com/jinrize/archive/2010/01/29/1659126.html

    3.对DMA的具体操作进行了举例

    http://hi.baidu.com/zcug_071/blog/item/5c5f5dc12a683d38e5dd3b4f.html

    4.讲得比较全,值得参考

    http://hi.baidu.com/shmily_soc/blog/item/2d8f5b83bc0639a70df4d217.html


    几个要点及问题的总结:

    -1.由于图像数据量大,采用片上内存较小的DSP芯片时,大量的运行时间将浪费在片内、外存储器的数据交换上,因此为了提高性能,需专注于存储结构的优化

    0.CPU只访问L1的数据,程序代码和数据必须经过存储器到L2,再由L2到L1的逐级搬移才能被CPU访问。

    1.L1D Cache不命中访问L2 Cache的时钟延迟是8个时钟周期,而L1D Cache不命中访问L2 SRAM的时钟延迟是6个时钟周期。

        为什么访问L2 Cache的时钟延迟要高呢?

    2.Cache优化流程为应用级->程序级->算法级。应用级优化即为合理安排Cache和SRAM的大小,以及采用DMA搬运数据。程序级的优化旨在减少conflict miss。

    3.LIP优化策略:

       在同一循环里或在某些特定时间帧里面反复调用的多个函数,需要考虑重排。

       L1P Cache的Cache line为32byteC64x的指令长度一般为4B,这就表示在L1P Cache读取指令的时候,其实每次是读取8条指令。基于此,保证每个函数在存储器中的起始地   址是32B对齐是有效利用L1P Cache的方式。

    4.简单配置L2 Cache提高Cache性能十分有限,在L2 SRAM上开辟双缓冲实现乒乓操作,可以在很大程度上提高Cache性能?值得验证!

       采用缓冲结构的时候,缓冲区的大小设置是需要慎重考虑的。既要考虑DMA的效率,也好考虑系统运行必须的内存空间。


    展开全文
  • VPP高性能从何而来之一:cache优化

    千次阅读 2019-01-04 17:15:58
    VPP高性能从何而来之一:cache优化cache优化包括如下几个方面1.cache line 对齐2.尽量避免cache一致性问题3.I-cache与D-cache cache优化包括如下几个方面 1.cache line 对齐 2.尽量避免cache一致性问题 3.I-...


    cache的哲学:时间局限性和空间局限性
    时间局限性:程序即将用到的指令/数据可能就是目前正在用到的指令/数据,因此当前用到的指令或数据将会继续放在cache中以备将来继续使用;如循环语句的终止条件满足之前,处理器会反复用到循环语句中的指令;
    这就是双层循环时候为什么尽量把循环次数更大的放在内层的原因
    空间局限性:程序即将用到的指令/数据可能与目前正在用到的指令/数据在空间上相邻或接近。因此处理器在处理当前指令或数据时候,可以从内层中把相邻区域的指令或数据预取到cache中。这就是VPP向量报文的秘密。

    cache优化包括如下几个方面

    1.cache line 对齐

    cache line bytes是指一次性从内存读到cache中的字节数。cache line对齐的目的,可减少CPU访问cache、cache访问内存的次数。因为数据跨越两个cache line,就意味着两次load或者两次store。如果数据结构是cache line对齐的, 就有可能减少一次读写。

    
     /*
     * Allow CFLAGS to override the configured / deduced cache line size
     */
    #ifndef CLIB_LOG2_CACHE_LINE_BYTES
     
    /* Default cache line size of 64 bytes. */
    #ifndef CLIB_LOG2_CACHE_LINE_BYTES
    #define CLIB_LOG2_CACHE_LINE_BYTES 6
    #endif
     
    #endif /* CLIB_LOG2_CACHE_LINE_BYTES defined */
     
    #if (CLIB_LOG2_CACHE_LINE_BYTES >= 9)
    #error Cache line size 512 bytes or greater
    #endif
     
    #define CLIB_CACHE_LINE_BYTES (1 << CLIB_LOG2_CACHE_LINE_BYTES)
    #define CLIB_CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CLIB_CACHE_LINE_BYTES)))
    
    
    

    2.尽量避免cache一致性问题

    cache一致性问题:多核对同一块内存读写,会引起冲突的问题。
    类似于DPDK,VPP为了避免cache一致性问题,对某些数据结构,给每个核都定义一份。
    例如snat session定义成per-thread的

    typedef struct
    {
      /* Main lookup tables */
      clib_bihash_8_8_t out2in;
      clib_bihash_8_8_t in2out;
    
      /* Endpoint dependent sessions lookup tables */
      clib_bihash_16_8_t out2in_ed;
      clib_bihash_16_8_t in2out_ed;
    
      /* Find-a-user => src address lookup */
      clib_bihash_8_8_t user_hash;
    
      /* User pool */
      snat_user_t *users;
    
      /* Session pool */
      snat_session_t *sessions;
    
      /* Pool of doubly-linked list elements */
      dlist_elt_t *list_pool;
    
      /* NAT thread index */
      u32 snat_thread_index;
    } snat_main_per_thread_data_t;
    

    3.I-cache与D-cache

    1)I-cache相关的优化

    (1)精简的code path,vpp的node架构简化了调用关系,减少冗余代码;
    (2)分支预测

    /* Hints to compiler about hot/cold code. */
    #define PREDICT_FALSE(x) __builtin_expect((x),0)
    #define PREDICT_TRUE(x) __builtin_expect((x),1)
    

    2)D-cache相关的优化

    减少D-cache miss的数量,增加有效的数据访问的数量。

    (1)Vector all over ,所有结构都是数组、链表操作也修改成为动态数组;

    (2):利用gcc内置函数 __builtin_prefetch实现的数据手工预取

    #define _CLIB_PREFETCH(n,size,type)				\
      if ((size) > (n)*CLIB_CACHE_LINE_BYTES)			\
        __builtin_prefetch (_addr + (n)*CLIB_CACHE_LINE_BYTES,	\
    			CLIB_PREFETCH_##type,			\
    			/* locality */ 3);
    
    #define CLIB_PREFETCH(addr,size,type)		\
    do {						\
      void * _addr = (addr);			\
    						\
      ASSERT ((size) <= 4*CLIB_CACHE_LINE_BYTES);	\
      _CLIB_PREFETCH (0, size, type);		\
      _CLIB_PREFETCH (1, size, type);		\
      _CLIB_PREFETCH (2, size, type);		\
      _CLIB_PREFETCH (3, size, type);		\
    } while (0)
    

    4.内存池

    VPP的mheap自己管理内存分配。我们知道频繁的调用malloc和free函数是导致性能降低的重要原因,不仅仅是函数调用本身非常耗时,而且会导致大量内存碎片。由于空间比较分散,也进一步增大了cache misses的概率。

    展开全文
  • 本文主要介绍TI C6000系列的C64x+的DSP Cache配置,常见的Cache miss模式以及如何在多核或者多外设DMA系统下维护Cache一致性,当然还有Cache优化的专题,针对指令L1P和数据L1D cache的优化。 C64x+与C64x CACHE的...
  • dsp cache优化(转载)

    千次阅读 2012-07-06 17:07:52
    dsp cache优化(转载) 2010年07月27日 星期二 12:22 转载自 8kone8 最终编辑 zcug_071 以前我自己编写过乒乓结构的程序,后来查资料的时候发现乒乓结构还是很普遍的用法,但是我是初学者,其实编程优化...
  • 背景 监控线上实例时,曾出现可用内存不足,性能发生抖动的情况。研究后发现是日志文件的page cache占用了大量的内存(200G+),导致系统可立即分配的内存不足,影响了系统性能...据此,考虑能否设计一个优化,在page
  • 我百度cache优化的时候看到百度这样说的: 变量申明时cache对齐 只有内存地址被2,4,8或16整除的位置是cache优化的。 因此,申明数组变量时,力求每个数组内成员是cache独立的,不碰撞的。 可以如下申明: int a...
  • 为了提供高速的数据访问,多核处理器常使用Cache划分机制来分配二级Cache资源,但传统的共享Cache划分算法大多是面向多道程序的,忽略了多线程负载中共享和私有数据访问模式的差别,使得共享数据的使用效率降低....
  • 本文主要介绍TI C6000系列的C64x+的DSP Cache配置,常见的Cache miss模式以及如何在多核或者多外设DMA系统下维护Cache一致性,当然还有Cache优化的专题,针对指令L1P和数据L1D cache的优化。 C64x+与C64x CACHE...
  • c/c++性能优化--- cache优化的一点杂谈

    千次阅读 2013-07-16 13:42:38
    c/c++性能优化--- cache优化的一点杂谈 分类: 程序优化2013-07-01 12:53 1747人阅读 评论(8) 收藏 举报 Cache性能优化 之前写了一篇关于c/c++优化的一点建议,被各种拍砖和吐槽,有赞成的有反对...
  • DPDK中的Cache优化

    千次阅读 2018-03-18 23:08:51
    DPDK利用了一系列的Cache软件优化方法(cache预取,cache对齐,hugepage ,NUMA感知,DDIO)高效的减少访存开销以提升性能。本文将讲解这些方法的基本原理以及在DPDK中的应用。1:Cache预取Cache由于时间以及空间上...
  • 矩阵乘法cache优化

    千次阅读 2015-05-05 17:03:24
    好文要转,太棒了~~~~~~~~~~~~~~~~~~~~~~~~~ ...昨晚为了优化这个题目弄到2点多,今天一早就写博,我真是太不蛋定了,哈哈。 做OJ的朋友都知道快速幂,我就不罗嗦了,我说的主要是矩阵乘法实现层面的优化
  • 在这同平台上做性能优化Cache是一重要考虑因素,现在指令运行越来越快,导致内存读写操作成为耗时最多的操作,在这转载一篇文章,相当不错。 0.缘起 受首席启发,补充点 cache 背景。再乱弹一下 Page Coloring ...
  • DSP-Cache优化

    千次阅读 2014-10-31 13:39:48
    3 基于cache的程序优化  3.1 应用级优化 (application-level optimization)  1) 合理设置cache大小,尽量将DMA用到的buffer开在片内RAM上  2) 将一般性程序代码和数据放到片外RAM,将DSP型代码和数据放...
  • CPU CACHE优化 性能优化方法和技巧

    千次阅读 2014-08-22 15:42:40
    这个要比I-cache优化难一些。 下面是一个代码优化技巧列表,需要不断地补充,优化和筛选。 1) Code adjacency (把相关代码放在一起),推荐指数:5颗星 把相关代码放在一起有两个涵义,一是相关的...
  • 由于技术的发展,片上多核处理器上的核数量和片上缓存的大小一直在增长,且缓存占据了芯片的大部分面积,使得片上缓存所消耗的能量成为存储器子系统中功率损耗的主要贡献者,因此对片上缓存进行优化是提高存储器系统...
  • mysql query cache优化

    万次阅读 2012-08-12 16:41:59
    query cache原理 当mysql接收到一条select类型的query时,mysql会对这条query进行hash计算而得到一个hash值,然后通过该hash值到query cache中去匹配,如果没有匹配中,则将这个hash值存放在一个hash链表中,同时将...
  • 文章从C64x+内核DSP的RAM存储器使用效率、Cache工作机制和缓存一致性3个方面出发,分析了数据处在不同级别的存储器对运算性能的影响,指出制约算法效率的因素,给出提高算法效率的方法。H.264的数据类型包括原始帧、...
  • TMS320C6000 DSP-----Cache 优化

    千次阅读 2016-11-28 16:14:36
     优异的cache性能很大程度上依赖于cache lines的重复使用,优化的最主要目标也在于此,一般通过恰当的数据和代码内存布置,以及调整CPU的内存访问顺序来达到此目的。由此,应该熟悉cache内存架构,特别是cache内存...
  • oracle Buffer Cache优化思路

    千次阅读 2011-07-17 22:02:05
    最近看了几页书,做个笔记,拷贝oracle官方文档上的一段话,当然Buffer Cache远不止这些,只是看英文的确速度好慢的,我现在最想说的一句话是:我需要更多的时间。最近杂事太多,没多少自己能够支配的时间。什么时候...
  • 十个先进的Cache优化方案

    千次阅读 2016-09-06 15:02:05
    度量标准 Reducing the hit time(降低命中时间):更小的更简单...Increasing cache bandwidth(提高Cache带宽):Pipelined caches, multibanked caches, and nonblocking caches。这些技术对能耗有着不同的影响。 R
  • 上节主要介绍在资源受限的ARM设备上,在各种类型的操作系统上的...以及编译器对结构体和数组的基本处理方式,下节则主要介绍编译器的使用规则,如何指导编译器进行合理的优化,以及系统级的NEON优化,从cache使用到系统
  • Cache 优化(矩阵乘积为例)

    千次阅读 2013-08-21 03:47:39
    Degrees of Latency ...The latency of data access becomes greater with each cache level. Latency of memory access is best measured in CPU clock cycles. One cycle occupies from 4 to 6 nanoseconds, dep
  • TMS320C64x+ DSP-----Cache 优化

    千次阅读 2014-05-10 16:57:23
     优异的cache性能很大程度上依赖于cache lines的重复使用,优化的最主要目标也在于此,一般通过恰当的数据和代码内存布置,以及调整CPU的内存访问顺序来达到此目的。由此,应该熟悉cache内存架构,特别是cache内存...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 265,141
精华内容 106,056
关键字:

cache优化