PS:为了面试准备的,总结的有点粗糙。
ARM:Advanced RISC Machines,ARM架构是面向低预算市场设计的第一款RISC微处理器,基本是32位单片机的行业标准,它提供一系列内核、体系扩展、微处理器和系统芯片方案,四个功能模块可供生产厂商根据不同用户的要求来配置生产。由于所有产品均采用一个通用的软件体系,所以相同的软件可在所有产品中运行。
DSP:Digital Signal Processor,内部有专用的硬件乘法器和哈佛总线结构对大量的数字信号处理的速度快(即数据总线和地址总线分开,使程序和数据分别存储在两个分开的空间,允许取指令和执行指令完全重叠。也就是说在执行上一条指令的同时就可取出下一条指令,并进行译码,这大大的提高了微处理器的速度。)
特点:
1.哈佛结构,程序和数据存储在不同的存储空间,因此取指和执行能完全重叠
2.流水线操作,以减少指令执行时间
3.占用硬件乘法器,乘法可在一个指令周期内完成
4.特殊的dsp指令(SIMD),达到并行执行
当然,与通用微处理器相比,DSP芯片的其他通用功能相对较弱些。
哈佛结构
并行体系结构,将程序和数据存储在不同的存储空间
三级流水线
在每个周期内,三个不同的指令处于激活状态,每个指令处于不同的阶段。
FPGA:Field ProgrammableGate Array,采用了逻辑单元阵列LCA(Logic Cell Array)这样一个概念,内部包括可配置逻辑模块CLB(Configurable Logic Block)、输出输入模块IOB(Input Output Block)和内部连线(Interconnect)三个部分。用户可对FPGA内部的逻辑模块和I/O模块重新配置,以实现用户的逻辑。
加电时,FPGA芯片将EPROM中数据读入片内编程RAM中,配置完成后,FPGA进入工作状态。掉电后,FPGA恢复成白片,内部逻辑关系消失,因此,FPGA能够反复使用。FPGA的编程无须专用的FPGA 编程器,只须用通用的EPROM、PROM编程器即可。当需要修改FPGA功能时,只需换一片EPROM即可。这样,同一片FPGA,不同的编程数据,可以产生不同的电路功能。因此,FPGA的使用非常灵活。
三者比较:
ARM具有比较强的事务管理功能,可以用来跑界面以及应用程序等,其优势主要体现在控制方面;ARM是32位的单片机,其内部硬件资源的性能较高,可以加载操作系统,有了操作系统,就可以像pc机那样多任务实时处理,就是同一时间内能完成多个任务,而且不会互相影响。
DSP主要是用来计算的,比如进行加密解密、调制解调等,优势是强大的数据处理能力和较高的运行速度;
FPGA可以用VHDL或verilog HDL来编程,灵活性强,由于能够进行编程、除错、再编程和重复操作,因此可以充分地进行设计开发和验证。当电路有少量改动时,更能显示出FPGA的优势,其现场编程能力可以延长产品在市场上的寿命,而这种能力可以用来进行系统升级或除错。
FPGA适合于控制功能算法简单且含有大量重复计算的工程使用,DSP适合于控制功能复杂且含有大量计算任务的工程应用。
DSP是软件实现算法,FPGA是硬件实现算法,所以FPGA的处理速度会更高;FPGA比DSP快的一个重要原因是FPGA可以实现并行运算,而DSP由于硬件结构条件限制,主要还是依靠软件来提取指令执行,可以理解为还是串行执行的【DSP所谓的并行执行主要还是得自己开通软件流水,或者说是自己写并行代码,而非硬件结构本身】。
软流水(software pipelining):
一种循环优化技术,通过对循环进行重新建构,将循环体内不同指令进行迭代运行来提高程序的运行效率。
定点DSP 浮点DSP
从宏观上讲,浮点dsp比定点dsp的动态范围大得多。定点运算中,程序员必须时刻关注溢出的发生,为了防止溢出,要么不断进行移位定标,要么做截尾。前者耗费大量时间和空间,后者则带来精度的损失。相反,浮点运算dsp扩大了动态范围,提高了精度,节省了运算时间和存储空间,因为大大减少了定标,移位和溢出检查。
硬件上,浮点dsp处理器具有浮点/整数乘法器,整数/浮点算术逻辑运算单元ALU,适合存放扩展精度的浮点结果的寄存器等。
软件开发上,主要有浮点dsp编程的特点以及注意事项;定点dsp进行浮点运算时的定标,移位,检测溢出操作。比较两个浮点数时,永远不要使用操作符==来判断是否相等。即使比较两个相同的数,还是可能有微小的舍入差别。甚至定义精确的0,也不是很安全,尽管C语言中有0的表示,永远不要写这样的代码(x==0),而应该写成(fabs(x) < TINY),其中TINY定义为一个很小的值,也就是处理器的浮点格式舍入误差。
浮点 DSP 提供的计算能力更高,这也是其区别于定点 DSP 功能的最大差异所在。但在浮点 DSP 刚刚出现的20世纪90年代初期,其它因素往往掩盖了基本的数学计算问题。浮点功能需要的内部电路多,而 32位数据路径比当时可用的定点器件要宽一倍。晶片面积越大,引脚数量就越多,封装也越大,这就大大提高了新款浮点器件的成本
----------------------------------
循环的优化:
需要计算的公式:MONO = (Lch + Rch) / 2
假设数据存储方式为:[Lch Rch Lch Rch Lch Rch.....]
for (i = 0; i < MONO_BUFFSIZE; i++){
MonoBuffer[i] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
}
——>
i = 0;
while (i < MONO_BUFFSIZE){
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
MonoBuffer[i++] = (float)(StereoBuf[i * 2] + StereoBuf[i * 2 + 1]) / 2;
}
CPU占用资源会降低
类型强制转换去掉后专用资源会进一步降低
运行速度是差不多的
------------------------------
现状:
1.研究者多以理论研究为主,忽略了算法效率及实用性
2.Mathlab等工具和OpenCV等库函数,加快了功能初步验证,缺少对图像处理技术本质的认识及经验积累
3.想解决某一问题,必须针对性地提出解决方案才有可能研制出高效的功能模块。
4.硬件平台的依赖性太强,研制成本高
算法级的优化或简化永远放到第一位:算法的计算复杂度决定了经过优化后能够达到的最佳性能,没有好的算法作为基础,一切优化都是白搭。
代码优化的几个基本原则:
1.重点优化针对每个像素的操作,即for
2.不要在大的for循环里面调用函数,(涉及函数寻址、参数传递过程)
3.避免大量的除法运算,尽量以乘法和移位来替代
4.尽可能多的利用查表(高效运算)来代替计算,避免重复计算
5.尽量采用定点整形运算,尽量避免大量的多层级的if…else…
程序优化方法:
1.选择合适的算法和数据结构 (数组,链表,二叉树…?用指针代替数组索引)
2.使用尽量小的数据类型(char,int,long int,float,double...)
3.减少运算的强度
查表:不要在循环里搞运算,用查表
求余: a=a%8;--> a=a&7;(位操作只需一个指令周期即可完成,而大部分的 C 编译器的“ % ”运算均是调用子程序来完成.只要是求 2n 方的余数,均可使用位操作的方法来代替)
平方:a=pow(a, 2.0);--> a=a*a; (在有内置硬件乘法器的单片机中,乘法运算比求平方运算快得多,因为浮点数的求平方是通过调用子程序来实现的,在自带硬件乘法器的单片机中,乘法运算只需 2 个clk。既使没有内置硬件乘法器的单片机中,乘法运算的子程序比平方运算的子程序代码短,执行速度快。)
用移位实现乘除法:通常如果需要乘以或除以 2n ,都可以用移位的方法代替。用移位的方法得到代码比调用乘除法子程序生成的代码效率高。
实际上,只要是乘以或除以一个整数,均可以用移位的方法得到结果:
a=a*9 --> a=(a<<3)+a
避免不必要的整数除法:整数除法是整数运算中最慢的,可以由乘法代替.(比如连除的时候)
使用增量和减量操作符:在使用到加一和减一操作时尽量使用增量和减量操作符,因为增量符语句比赋值语句更快,原因在于对大多数 CPU 来说,对内存字的增、减量操作不必明显地使用取内存和写内存的指令
使用复合赋值表达式: a-=1 , a+=1 等
提取公共的子表达式:(避免重复运算)
4.结构体成员的布局:考虑到对齐。
按数据类型的长度排序,声明成员时把长的类型放在短的前面;
把结构体填充成最长类型长度的整倍数;
按数据类型的长度排序本地变量,应该把长的变量放在短的变量前面,如果第一个变量对齐了,其它变量就会连续的存放,而且不用填充字节自然就会对齐。
把频繁使用的指针型参数拷贝到本地变量:避免在函数中频繁使用指针型参数指向的值。
5.循环优化
当循环体本身很小的时候,分解循环可以提高性能,(利用CPU的指令缓存);
提取公共部分:不需要循环变量参加运算的任务可以把它们放到循环外面,这里的任务包括表达式、函数的调用、指针运算、数组访问等。现在许多编译器还是能自己干这件事,不过对于中间使用了变量的算式它们就不敢动了,所以很多情况下你还得自己干;
延时函数使用自减代替自加(for/while):几乎所有的 C 编译对自减函数生成的代码均比自加方式代码少 1~3 个字节
循环展开:
旧代码 :
for (i = 0; i < 100; i++)
{
do_stuff(i);
}
新代码 :
for (i = 0; i < 10; )
{
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
do_stuff(i); i++;
}
新代码里比较指令由 100 次降低为 10 次,循环时间节约了 90% 。许多编译程序 ( 如 gcc -funroll-loops) 能自动完成这个事。不过注意 : 对于中间变量或结果被更改的循环,编译程序往往拒绝展开, ( 怕担责任呗 ) ,这时候就需要你自己来做展开工作了;
Switch语句中根据发生频率来进行case排序:Switch 可能转化成多种不同算法的代码。其中最常见的是 跳转表 和 比较链 / 树 。当 switch 用比较链的方式转化时,编译器会产生 if-else-if 的嵌套代码,并按照顺序进行比较,匹配时就跳转到满足条件的语句执行。所以可以对 case 的值依照发生的可能性进行排序,把最有可能的放在第一位,这样可以提高性能。此外,在 case 中推荐使用小的连续的整数,因为在这种情况下,所有的编译器都可以把 switch 转化成跳转表;
将大的switch语句转为嵌套switch语句:当 switch 语句中的 case 标号很多时,为了减少比较的次数,明智的做法是把大 switch 语句转为嵌套 switch 语句。把发生频率高的 case 标号放在一个 switch 语句中,并且是嵌套 switch 语句的最外层,发生相对频率相对低的 case 标号放在另一个 switch 语句中。比如,下面的程序段把相对发生频率低的情况放在缺省的 case 标号内。
循环转置:有些机器对 JNZ( 为 0 转移 ) 有特别的指令处理,速度非常快,如果你的循环对方向不敏感,可以由大向小循环。
一些公用处理模块,为了满足各种不同的调用需要,往往在内部采用了大量的 if-then-else 结构,这样很不好,判断语句如果太复杂,会消耗大量的时间的,应该尽量减少公用代码块的使用。
无限循环 for ( ;; ) 编译后指令少,不占用寄存器,而且没有判断、跳转,比 while (1) 好;
6.提高CPU的并行性
尽可能把长的有依赖的代码链分解成几个可以在流水线执行单元中并行执行的没有依赖的代码链
for (i=0 ; i<100 ; i++)
sum += a ;
推荐的代码:
for (i = 0 ; i < 100 ; i += 4)
{
sum1 += a ;
sum2 += a[i+1] ;
sum3 += a[i+2] ;
sum4 += a[i+3] ;
}
sum = (sum4+sum3)+(sum1+sum2) ;
(提高代码并行度,最大化资源利用率);
当数据保存到内存时存在读写依赖,即数据必须在正确写入后才能再次读取,例如引进一个可以保存在寄存器中的临时变量,这样可以有很大的性能提升。
for ( unsigned int k = 1 ; k < VECLEN ; k ++)
{
x[k] = x[k-1] + y[k] ;
}
推荐的代码:
float t(x[0]) ;
for ( unsigned int k = 1 ; k < VECLEN ; k ++)
{
t = t + y[k] ;
x[k] = t ;
}
7.函数优化
inline函数:请求编译器用函数内部的代码替换对该函数的调用,第一,省去了调用指令需要的执行时间;第二,省去了传递变元和传递过程需要的时间。但是使用这种方法在优化程序速度的同时,程序长度变大了,因此需要更多的 ROM 。
不定义不使用的返回值:用void替代
减少函数调用参数:使用 全局变量 比函数传递参数更加有效率。这样做去除了函数调用参数入栈和函数完成后参数出栈所需要的时间。然而决定使用全局变量会影响程序的模块化和重入,故要慎重使用。
所有函数都应该有原型定义:原型定义可以传达给编译器更多的可能用于优化的信息。
尽可能使用常量(const): C++ 标准规定,如果一个 const 声明的对象的地址不被获取,允许编译器不对它分配储存空间。这样可以使代码更有效率。
把本地函数声明为静态的(static):如果一个函数只在实现它的文件中被使用,把它声明为静态的 (static)以强制使用内部连接。否则,默认的情况下会把函数定义为外部连接。这样可能会影响某些编译器的优化——比如,自动内联。
8.采用递归:C编译器更喜欢递归而不是循环
9.变量:register变量,使编译器把变量放入一个多用途的寄存器中,而不是在堆栈中,通过这种方式可提升对某些局部变量频繁调用的程序的性能。 c语言的编译器们总是先假定每一个函数的变量都是内部变量,这是由它的机制决定的,在这种情况下,它们的优化完成得最好。但是,一旦一个变量有可能被别的函数改变(比如把一个变量地址传递给另一个函数),这帮兄弟就再也不敢把变量放到寄存器里了,严重影响速度。
同时声明多个变量优于单独声明变量;
短变量名优于长变量名,应尽量使变量名短一点;
在循环开始前声明变量;
10.使用嵌套的if结构:在 if结构中如果要判断的并列条件较多,最好将它们拆分成多个if结构,然后嵌套在一起,这样可以避免无谓的判断。
操作系统
Wiki:
操作系统(英语:operating system,缩写作 OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
五大基本功能
1.进程和线程的管理 ——进程线程的状态、控制、同步互斥、通信调度等
2.存储管理——分配/回收、地址转换、存储保护等
3.文件管理——文件目录、文件操作、磁盘空间、文件存取控制
4.设备管理——设备驱动、分配回收、缓冲技术等
5.用户接口——系统命令、编程接口