Head First C 笔记

2.储存器和指针

  • 两张存款和储蓄器图

   图一图片 1

   图二                           
二个学JAVA时用的图(来源博瑞思创—翁先生)

    图片 2

  • Int *a =5   

    A 是叁个指针变量,它保存多少个地址,地址中保存三个int类型的数值5

  • 指南针的意义   

   指针可避防止副本的发出和共享数据。

  • 指南针像是多少个遥控器,存款和储蓄器像是一个智能冰箱。

图片 3

   存款和储蓄器和指针的图 

  • sizeof(指针) 

*   在叁十二个人中回到4,在陆拾伍个人中回到8.*

  • 数组名做指针

*   *

1 int a []={1,2,3};
2 int *b =a;
3 c=sizeof(a);
4 d=sizeof(b);

   c = 3          d = 4/8 (32/64)

   &a == a    &b! = b

  • 指南针的向下   

*    
把数组赋给指针变量,指针变量就会只领会数组的地方音信,而不清楚数组的轻重缓急。相当于指针丢失了部分信息,称那种新闻丢失为退化。*

   数组变量不能够指向别的地点

  • 指针类型

   指针也是有档次,char int 
等,首假使在进展指针运算时得以依照指针类型实行转移,比如char类型的指针a
,a+1 存款和储蓄器地址 +1

                                        
  int类型的~b                 ,b+1 ~ +4

  • 指南针输出

    1 int num[]={1,2,3}
    2 printf(“%p”,num);//地址
    3 printf(“%i”,num[1]);//数值
    4 printf(“%i”,(num+1));//数值
    5
    6
    7
    8 char
    msg=”Hello”
    9 puts(msg+1)//字符
    10 printf(“%p”,msg)//地址

  • 指南针的加法交流律(来源:百度)

  • 1 int dose[]={1,2,3};

    2 
    3 dose[2] == *(dose+2)            ==            *(2+dose)  ==2[dose]
    4 
    5 只要一个是指针,一个是整形就行。
    

    用指针输入数据

   

1 fgets(food,sizeof(food),stdin);
2 scanf("%4s",food);
3 gets(food);

   安全从高到低

   scnaf遭受空格就会终止。(亲测有用)

  • card[]   or   *card?
  • 1 card []=”qwe”;//数组

    2 
    3 
    4 
    5 一下这两个等价
    6 void func(char card[])
    7 {
    8 //card 是指针
    9 }
    

    10
    11 void func(*card)
    12 {
    13 //card 是指针
    14 }

    const与字符串常量

   字符串常量都以只读的,不可修改。加了const未来,会在您打算修改const修饰过的变量时编写翻译器报错。

  • 字符串常量和字符数组

    1 char * card=”QWE”//不可修改
    2 char car[]=”BBB”//可修改

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

*  *

   
 回想下代码,全部进度中除去阿尔法参数的臆想涉及到了浮点,别的部分皆以整形的乘法和活动操作,因此能够想象,速度相应非常的慢,而且相当适合于手提式有线电话机端处理。同时注意到_blurrow和_blurcol函数循环鲜明互相之间是单独的,可以动用多线程并行处理,可是这么些代码首假如专注于算法的抒发,并不曾过多的设想更好的作用。

1. 入门

  • 为啥字符从零早先编号?  

   字符的目录数值表示的是两个偏移量,它表示的是当前所引述的字符与第伍个字符之间差多少个字符。

  • 单双引号的区分?

   单引号 一个字符,双~ 字符串

  • 字符串字面值和字符数组的区别?

   前者是常量,一旦赋值,不能够再修改。

  • Break 和continue?

   break是退出循环,continue是脱离当前轮回并且开始展览下二次巡回。

   break不能够用来if语句。

     
 https://github.com/rubencarneiro/NUX/blob/master/NuxGraphics/CairoGraphics.cpp

嗨翻c语言

     
 除了线程外,这么些刻钟是或不是还有创新的半空中吗,大家先来探望列方向的优化。

     
然而对于贰十五个人如何是好吧,他的乘除进度是二个字节重复的,不能够直接利用SIMD的那种优化的主意的,同高斯模糊算法的一视同仁优化进程分享(一) 一文类似,大家也是足以把2二个人的图像补二个Alpha通道然后再更换来int类型的缓冲区中,所以难题一挥而就。

   
 本文不享受最后优化的代码,请各位参考本文有关思路自行完成。

  原来的三四行代码一下子改为了几十行的代码,会不会变慢呢,其实不用担心,SIMD真的很强劲,测试的结果是三千*贰仟的图耗时跌落到42ms左右,而且垂直方向的耗时占比有原来的五分三下跌到了35%左右,未来的主导正是水平方向的耗费时间了。

  在盘算实现后结果也会在这一个int类型的缓冲区中,之后再用SSE函数转换为int类型的。

     D1 D2 D3 D4 D5 D6 D7……

  
假设一直行使普通C语言混搭,那一个历程如故相当耗费时间的,当然也无法不的用SSE达成,我们只要条分缕析看过作者图像转置的SSE优化(辅助五人、2多少人、三16人),提速4-6倍一文的代码,这么些进度完结也很简单。

  垂直方向的处理:

     A1 A2 A3 A4 A5 A6 A7……

int *Buffer = (int *)malloc(Width * sizeof(int));
for (int X = 0; X < Width; X++)        Buffer[X] = Src[X] << Zprec;
for (int Y = 0; Y < Height; Y++)
{
    byte *LinePS = Src + Y * Stride;
    byte *LinePD = Dest + Y * Stride;
    for (int X = 0; X < Width; X++)        //  从上到下
    {
        Buffer[X] += (Alpha * ((LinePS[X] << Zprec) - Buffer[X])) >> Aprec;
        LinePD[X] = Buffer[X] >> Zprec;
    }
}
for (int Y = Height - 1; Y >= 0; Y--)      //  从下到上
{
    byte *LinePD = Dest + Y * Stride;
    for (int X = 0; X < Width; X++)
    {
        Buffer[X] += (Alpha * ((LinePD[X] << Zprec) - Buffer[X])) >> Aprec;
        LinePD[X] = Buffer[X] >> Zprec;
    }
}
free(Buffer);

  大家稍事对上述代码做个简化处理,对于灰度图,水平方向的代码能够表明如下:

   static inline void _blurinner(guchar* pixel, gint* zR,gint* zG,gint* zB, gint* zA, gint alpha,gint aprec,gint zprec)
  {
    gint R, G, B, A;
    R = *pixel;
    G = *(pixel + 1);
    B = *(pixel + 2);
    A = *(pixel + 3);

    *zR += (alpha * ((R << zprec) - *zR)) >> aprec;
    *zG += (alpha * ((G << zprec) - *zG)) >> aprec;
    *zB += (alpha * ((B << zprec) - *zB)) >> aprec;
    *zA += (alpha * ((A << zprec) - *zA)) >> aprec;

    *pixel       = *zR >> zprec;
    *(pixel + 1) = *zG >> zprec;
    *(pixel + 2) = *zB >> zprec;
    *(pixel + 3) = *zA >> zprec;
  }

  在源代码中用以下四个函数达成以下进度:

     最终:3000*两千的灰度图的进行时间为
7-8ms,升高了7倍左右。

 图片 4

     水平反向的处理:

     1个测试相比工程:

     有的时候思路真的很重点。

  最后的模糊算法如下所示:

   
 其余一些,很鲜明,算法的耗费时间是和Radius参数没有其他涉及的,也便是说那也是个O(1)算法。

     
 提炼出这几个模糊的为主部分算法首假诺底下那些函数:

int X = 0;
for (X = 0; X < Width - 8; X += 8)            
{
    //    将8个字节数存入到2个XMM寄存器中
    //    方案1:使用SSE4新增的_mm_cvtepu8_epi32的函数,优点是两行是独立的
    __m128i Dst1 = _mm_cvtepu8_epi32(_mm_cvtsi32_si128((*(int *)(LinePD + X + 0))));    //    _mm_cvtsi32_si128把参数中的32位整形数据移到XMM寄存器的最低32位,其他为清0。
    __m128i Dst2 = _mm_cvtepu8_epi32(_mm_cvtsi32_si128((*(int *)(LinePD + X + 4))));    //    _mm_cvtepu8_epi32将低32位的整形数的4个字节直接扩展到XMM的4个32位中去。
    __m128i Buf1 = _mm_loadu_si128((__m128i *)(Buffer + X + 0));
    __m128i Buf2 = _mm_loadu_si128((__m128i *)(Buffer + X + 4));
    Buf1 = _mm_add_epi32(_mm_srai_epi32(_mm_mullo_epi32(_mm_sub_epi32(_mm_slli_epi32(Dst1, Zprec), Buf1), Alpha128), Aprec), Buf1);
    Buf2 = _mm_add_epi32(_mm_srai_epi32(_mm_mullo_epi32(_mm_sub_epi32(_mm_slli_epi32(Dst2, Zprec), Buf2), Alpha128), Aprec), Buf2);
    _mm_storeu_si128((__m128i *)(Buffer + X + 0), Buf1);
    _mm_storeu_si128((__m128i *)(Buffer + X + 4), Buf2);
    _mm_storel_epi64((__m128i *)(LinePD + X), _mm_packus_epi16(_mm_packs_epi32(_mm_srai_epi32(Buf1, Zprec), _mm_srai_epi32(Buf2, Zprec)), Zero));
}
for (; X < Width; X++)        
{
    Buffer[X] += (Alpha * ((LinePD[X] << Zprec) - Buffer[X])) >> Aprec;
    LinePD[X] = Buffer[X] >> Zprec;
}

       (Alpha * ((LinePD[X] <<
Zprec) – Buffer[X])) >> Aprec

   修改为上述后,测试二个三千*3000的6位灰度图,耗费时间大约52ms(未利用二十三十六线程的),和平凡的C语言完成的Boxblur时间大致。

   在列方向的  for (int X = 0; X <
Width; X++)
循环内,我们注意到针对Buffer的各样元素的拍卖都是单独和同样的,很肯定那样的进程是很简单选取SIMD指令优化的,但是循环体内部有一对是unsigned
char类型的数额,为运用SIMD指令,须求转移为int类型较为有利,而结尾保存时又须要重新处理为unsigned
char类型的,那种往返转换的耗费时间和其他计量的涨潮能无法来拉动意义呢,我们实行了代码的编写制定,比如:

void ExpFromLeftToRight_OneLine_SSE(int *Data, int Length, int Radius, int Aprec, int Zprec, int Alpha)
{
    int *LinePD = Data;
    __m128i A = _mm_set1_epi32(Alpha);
    __m128i S1 = _mm_slli_epi32(_mm_load_si128((__m128i *)(LinePD)), Zprec);
    for (int X = 0; X < Length; X++, LinePD += 4)
    {
        S1 = _mm_add_epi32(S1, _mm_srai_epi32(_mm_mullo_epi32(_mm_sub_epi32(_mm_slli_epi32(_mm_load_si128((__m128i *)(LinePD)), Zprec), S1), A), Aprec));
        _mm_store_si128((__m128i *)(LinePD), _mm_srai_epi32(S1, Zprec));
    }
}

  
 当中Pixel正是大家要处理的像素,zLX570,zG,zB,zA是表面传入的八个累加值,阿尔法、aprec、zprec是由模糊半径radius生成的一部分一定的全面。

     
今日大家来花点时间另行谈谈一个歪曲算法,二个特级简单然则又一流牛逼的算法,无论在作用上恐怕速度上都足以和Boxblur,
stackblur或许是Gaussblur想比美,效果上,比Boxblur来的更平整,和Gaussblur相似,速度上,经过小编的优化,在PC端比他们多少个都要快一大截,而且基本不需占用额外的内部存款和储蓄器,实在是三个绝好的算法。

  在 高斯模糊算法的应有尽有优化进程分享(一) 中我们追究过垂直方向处清理计算法一般不宜直接写,而应该用三个一时的行缓存举行拍卖,那样列方向的灰度图的处理代码类似上边包车型大巴:

   
 值得注意的是改为十几个人后,无论是33人、2三位依旧灰度的,写入到缓冲区的多寡格式都会有连锁的更改(其实依旧有很多居多技术小编那边没有发挥的)。

   
 
由于_mm_mulhi_epi16三遍性能够处理九个short类型,其余相应的SSE指令也同时改变为十五人的话,理论上又会比用三十六人的SSE指令快一倍,更为主要的是,大家最初的int缓冲区也应当改为short类型的缓冲区,对于那种自个儿耗费时间就不太多的算法,LOAD和Store指令的耗费时间是十三分值得注意,使用short类型时那些和内部存款和储蓄器打交道的频率又联合提升了。

     
前后三回那种类型的变换的SSE完成速度特别快,达成之后的涨潮也特别显眼,对三千*2000的叁九人图像耗费时间大致由150ms降低到50ms,提速很明显。

     
在实行了上边的优化后,小编曾本身满意过一段时间,因为他的小时已经在肯定程度上当先了SSE优化版本的Boxblur,不过俗话说,四处留心皆学问、开卷有益。当某一天本人留意到aprec的值为16丰裕>>aprec这一个操作时,大家脑海中就崩出了2个很好的SSE指令:_mm_mulhi_epi16,你们看,3个int类型右移13人不就是取int类型的高16个人呢,而在移十四个人的前头就是个乘法,也正是要开始展览(a*b)>>16,这个和_mm_mulhi_epi16命令的趣味完全一致。 

  static inline void _blurrow(guchar* pixels,
                               gint    width,
                               gint    /* height */,  // TODO: This seems very strange. Why is height not used as it is in _blurcol() ?
                               gint    channels,
                               gint    line,
                               gint    alpha,
                               gint    aprec,
                               gint    zprec)
  {
    gint    zR;
    gint    zG;
    gint    zB;
    gint    zA;
    gint    index;
    guchar* scanline;

    scanline = &(pixels[line * width * channels]);

    zR = *scanline << zprec;
    zG = *(scanline + 1) << zprec;
    zB = *(scanline + 2) << zprec;
    zA = *(scanline + 3) << zprec;

    for (index = 0; index < width; index ++)
      _blurinner(&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
      zprec);

    for (index = width - 2; index >= 0; index--)
      _blurinner(&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
      zprec);
  }

  不过利用_mm_mulhi_epi16下令前,大家应当肯定下本场景能或不可能满意数量范围的需求,大家看看必要优化的那句代码

for (int Y = 0; Y < Height; Y++)
{
    byte *LinePS = Src + Y * Stride;
    byte *LinePD = Dest + Y * Stride;
    int Sum = LinePS[0] << Zprec;
    for (int X = 0; X < Width; X++)      //  从左往右
    {
        Sum += (Alpha * ((LinePS[X] << Zprec) - Sum)) >> Aprec;
        LinePD[X] = Sum >> Zprec;
    }
    for (int X = Width - 1; X >= 0; X--)   //  从右到左
    {
        Sum += (Alpha * ((LinePD[X] << Zprec) - Sum)) >> Aprec;
        LinePD[X] = Sum >> Zprec;
    }
}

  混搭为:

   图片 5

   
 大家再来看看水平方向的优化,当图像是ALANDGB情势时,也正是原文者的代码,总结进度每隔八个字节就会再也,那种天性当然也顺应SIMD指令,然而为了便于,必须得先将字节数据先转移为int类型的一个缓冲区中,之后从左到右的持筹握算能够用如下的代码实现:

     
算法的主导并不是自家想开依然发明的,是3个敌人在github上挖掘到的,率属于Cairo那个2D图形库的开源代码,详见:

static inline void _blurcol(guchar* pixels,
                               gint    width,
                               gint    height,
                               gint    channels,
                               gint    x,
                               gint    alpha,
                               gint    aprec,
                               gint    zprec)
  {
    gint zR;
    gint zG;
    gint zB;
    gint zA;
    gint index;
    guchar* ptr;

    ptr = pixels;

    ptr += x * channels;

    zR = *((guchar*) ptr    ) << zprec;
    zG = *((guchar*) ptr + 1) << zprec;
    zB = *((guchar*) ptr + 2) << zprec;
    zA = *((guchar*) ptr + 3) << zprec;

    for (index = width; index < (height - 1) * width; index += width)
      _blurinner((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
      aprec, zprec);

    for (index = (height - 2) * width; index >= 0; index -= width)
      _blurinner((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
      aprec, zprec);
  }

     比例来说,四行灰度数据如下

 

  那段代码能够用如下的SIMD指令代替:

     B1 B2 B3 B4 B5 B6 B7……

     上述界面里的算法都以经过了SSE优化的,方今平昔在切磋那上头的事物,又心得就会到此处来记录一下。

     
http://files.cnblogs.com/files/Imageshop/SSE_Optimization_Demo.rar

  // pixels   image-data
  // width    image-width
  // height   image-height
  // channels image-channels
  // in-place blur of image 'img' with kernel of approximate radius 'radius'
  // blurs with two sided exponential impulse response
  // aprec = precision of alpha parameter in fixed-point format 0.aprec
  // zprec = precision of state parameters zR,zG,zB and zA in fp format 8.zprecb

  void _expblur(guchar* pixels,
                 gint    width,
                 gint    height,
                 gint    channels,
                 gint    radius,
                 gint    aprec,
                 gint    zprec)
  {
    gint alpha;
    gint row = 0;
    gint col = 0;

    if (radius < 1)
      return;

    // calculate the alpha such that 90% of 
    // the kernel is within the radius.
    // (Kernel extends to infinity)
    alpha = (gint) ((1 << aprec) * (1.0f - expf(-2.3f / (radius + 1.f))));

    for (; row < height; row++)
      _blurrow(pixels, width, height, channels, row, alpha, aprec, zprec);

    for (; col < width; col++)
      _blurcol(pixels, width, height, channels, col, alpha, aprec, zprec);

    return;
  }

  作为三个第一名的选择,大概说尽量缩短参数,常用的aprec取值为16,Zprec
取值为7。

     C1 C2 C3 C4 C5 C6 C7……

     
最难的是灰度图,因为灰度图的计量进程是单字节重复的,正如上述代码所示,2二位补1位的代价是多二个因素的一个钱打二17个结,不过SIMD能2次性总括6个整形的算法,由此照旧很合算的,假若灰度也如此玩,SIMD的涨潮和浪费的盘算句完全抵消了,而且还扩展了转移时间,肯定是不相宜的,不过我们可以变动思路,一行内种种要素之间的测算是连接的,然而假如小编把延续4行的数量混搭为一行,混搭成类似叁十二位那种数据格式,不正是能一贯动用叁12个人的算法了吧,最终再拆除回去就OK了。

       大家称之为Exponential
blur(指数模糊)。

  类似于高斯模糊恐怕StackBlur,算法也是属于行列分离的,行方向上,先用上述措施从左向右计算,然后在从右向左,接着举行列方向处理,先从上向下,然后在从下向上。当然,行列的乘除也能够反过来。供给小心的是,每一步都是用事先处理过的数据开始展览的。

   
 当图像不是灰度形式时,对于垂直方向的处理和灰度不会有分别,那是因为,只要求增添循环的长度就能够了。

    for (int X = 0; X < Width; X++)        //  从上到下
    {
        Buffer[X] += (Alpha * ((LinePS[X] << Zprec) - Buffer[X])) >> Aprec;
        LinePD[X] = Buffer[X] >> Zprec;
    }

     A1 B1 C1 D1 A2 B2 C2 D2 A3 B3 C3 D3
A4 B4 C4 D4 A5 B5 C5 D5 A6 B6 C6 D6 A7 B7 C7 D7………

   
 经过测试,唯有radius小于2时,那一个阿尔法会超过short能表明的上限,而(LinePD[X]
<< Zprec) –
Buffer[X])这句中LinePD[X]范围是[0,255],Zprec为7,两者相乘的限量不会当先32767,而Buffer[X]是个递归的量,只要第②次不超越32767,后面就不会超越,因而双方的差也不会低于short能表明的下限。所以说要是radius大于2,那么些算式完全符合_mm_mulhi_epi16命令的需要。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图