(*array2D)[i]=(*array2D)[i-1]+columns;
returnrows*columns*sizeof(int);
}
voidfree_mem2D(byte**array2D)
{
if(array2D){
if(array2D[0])free(array2D[0]);
elseerror("free_mem2D:
tryingtofreeunusedmemory",100);
free(array2D);
}else{
error("free_mem2D:
tryingtofreeunusedmemory",100);
}
}
顺便说一下,如果你的数组寻址有一个偏移量的话,不要写为a[x+offset],而应该为b=a+offset,然后访问b[x]。
不过,如果你不是处理对速度有特别要求的程序的话,这样的优化也就不必要了。
记住,如果编普通程序的话,可读性和可移值性是第一位的。
II.从负数开始的数组
在编程的时候,你是不是经常要处理边界问题呢?
在处理边界问题的时候,经常下标是从负数开始的,通常我们的处理是将边界处理分离出来,单独用额外的代码写。
那么当你知道如何使用从负数开始的数组的时候,边界处理就方便多了。
下面是静态使用一个从-1开始的数组:
inta[M];
int*pa=a+1;
现在如果你使用pa访问a的时候就是从-1到M-2了,就是这么简单。
(如果你动态申请a的话,free(a)可不要free(pa)因为pa不是数组的头地址)
III.我们需要链表吗
相信大家在学习《数据结构》的时候,对链表是相当熟悉了,所以我看有人在编写一些耗时算法的时候,也采用了链表的形式。
这样编写当然对内存的占用(似乎)少了,可是速度呢?
如果你测试:
申请并遍历10000个元素链表的时间与遍历相同元素的数组的时间,你就会发现时间相差了百倍!
(以前测试过一个算法,用链表是1分钟,用数组是4秒钟)。
所以这里我的建议是:
在编写耗时大的代码时,尽可能不要采用链表!
其实实际上采用链表并不能真正节省内存,在编写很多算法的时候,我们是知道要占用多少内存的(至少也知道个大概),那么与其用链表一点点的消耗内存,不如用数组一步就把内存占用。
采用链表的形式一定是在元素比较少,或者该部分基本不耗时的情况下。
(我估计链表主要慢是慢在它是一步步申请内存的,如果能够象数组一样分配一个大内存块的话,应该也不怎么耗时,这个没有具体测试过。
仅仅是猜想:
P)
本文来自CSDN博客,转载请标明出处:
C程序优化之路(三)
――liyuming1978@
本文讲述在编写C程序代码的常用优化办法,分为I/O篇,内存篇,算法篇。
MMX本来我也想归在这里的,但是由于内容和标题不太符和,决定换一个名字,叫MMX技术详解,和H263视频压缩技术中的MMX应用两篇文章。
三.算法篇
在上一篇中我们讲述了对内存操作的优化,这一篇则主要讲述一些常用的优化算法。
这个东东太多,内容可能会有点凌乱,见谅。
I.从小处说起:
先说说一些小地方先:
①比如n/2写为n>>1这个是常用的方法,不过要注意的是这两个不是完全等价的!
因为:
如果n=3的话,n/2=1;n>>1=1;但是,如果n=-3的话,n/2=-1;n>>1=-2所以说在正数的时候,他们都是向下取整,但是负数的时候就不一样了。
(在JPG2000中的整数YUV到RGB变换一定要使用>>来代替除法就是这个道理)
②还有就是a=a+1要写为a++;a=a+b要写为a+=b(估计一般用VB的才会写a=a+1:
P)
③将多种运算融合:
比如a[i++];就是先访问a[i],再令i加1;从汇编的角度上说,这个确实是优化的,如果写为a[i],和i++的话,有可能就会有两次的对i变量的读,一次写(具体要看编译器的优化能力了),但是如果a[i++]的话,就一定只读写i变量一次。
不过这里有一个问题要注意:
在条件判断内的融合一定要小心,比如:
(idct变换中的0块判断,陈王算法)
if(!
((x1=(blk[8*4]<<8))|(x2=blk[8*6])|(x3=blk[8*2])|(x4=blk[8*1])|(x5=blk[8*7])|(x6=blk[8*5])|(x7=blk[8*3])))
在条件判断中融合了赋值语句,但是实际上如果条件为真的话,是不需要这些赋值语句的,也就是说当条件真的时候,多了一些垃圾语句,这些是在h263源码上的问题,虽然这些垃圾语句使得计算0块的时候,时间增加了30%,但是由于idct仅仅占1%的时间,0块又仅仅30%~70%的时间,所以这些性能损失是没有什么关系的。
(这是后来我用汇编改写源码的时候得到的结论)。
这里也说明了,程序优化一定重点在最耗时的地方。
对于不耗时的代码优化是没有太大的实用意义的。
II.以内存换速度:
天下总是难有双得的事情,编程也是一样,大多数情况,速度同内存(或者是性能,比如说压缩性能什么的)是不可兼得的。
目前程序加速的常用算法一个大方面就是利用查表来避免计算(比如在jpg有huffman码表,在YUV到RGB变换也有变换表)这样原来的复杂计算现在仅仅查表就可以了,虽然浪费了内存,不过速度显著提升,还是很划算的。
在数据库查询里面也有这样的思想,将热点存储起来以加速查询。
现在介绍一个简单的例子,(临时想的,呵呵):
比如,在程序中要经常(一定要是经常!
)计算1000到2000的阶乘,那么我们可以使用一个数组a[1000]先把这些值算好,保留下来,以后要计算1200!
的时候,查表a[1200-1000]就可以了。
III.化零为整
由于零散的内存分配,以及大量小对象建立耗时很大,所以对它们的优化有时会很有效果,比如上一篇我说的链表存在的问题,就是因为大量的零散内存分配。
现在就从一个vb的程序说起,以前我用vb给别人编小程序的时候,(呵呵,主要是用vb编程比vc快,半天就可以写一个)在使用MSFlexGrid控件的时候(就是一个表格控件),发现如果一行一行的增加新行,刷新速度十分的慢,所以我就每次增加100行,等到数据多到再加新行的时候,再加100行,这样就“化零为整”了,使用这样的方法,刷新的速度比原来快了n倍!
其实这样的思想应用很多,如:
程序运行的时候,其实就占用了一定的空间,后来的小块内存分配是先在这个空间上的,这就保证了内存碎片尽可能的少,同时加快运行速度。
IV.条件语句或者case语句将最有可能的放在前面
优化效果不明显。
想得到就用吧,想不到就算了。
V.为了程序的可读性,不去做那些编译器可以做的或者优化不明显的处理:
这个是很重要的,一个普通程序的好坏,主要是它的可读性,可移植性,可重用性,然后才是它的性能。
所以,如果编译器本身可以帮助我们优化的话,我们就没有必要写那些大家都不怎么看得懂的东西。
比如a=52(结束)-16(起始);这样写可能是因为在别人读程序的时候,一下就明白了a的含义。
我们不用写为a=36,因为编译器是会帮我们算出来的。
IV.具体情况具体分析:
具体情况具体分析,这是放之四海而皆准的真理。
没有具体的分析,就不能针对问题灵活应用解决的办法。
下面我就说说分析的方法。
即如何找到程序的耗时点:
(从最简单的办法说起,先说明一个函数GetTickCount(),这个函数在头尾各调用一次,返回值相减就是程序的耗时,精确到1ms)
①对于认为是比较耗时的函数,运行两次,或者将函数内部的语句注释掉(要保证程序可以运行),看看多(或者少了)多少时间。
这个办法简单不精确。
②每个地方都用GetTickCount()函数测试时间,注意GetTickCount()只能精确到ms。
一般的小于10ms就不太精确了。
③使用另外一个函数QueryPerformanceCounter(&Counter)和QueryPerformanceFrequency(&Frequency),前面计算cpu时钟周期,后面是cpu频率相除就是时间。
不过如果你要精确到这一步的话,建议将进程设置为最高级别,防止它被阻塞。
最后讲讲我处理的一个程序:
程序要求我忘了,反正里面有一个函数,函数里面有一个大的循环,循环内部的处理比较耗时。
结果最初程序表现出来的状况是开始还很快,越到后面越慢;我在跟踪程序中变量的时候,发现最初的循环在循环几次后就跳出了,而后面的循环次数越来越多。
找到了为什么慢的原因,就可以对症下药了,我的处理是每次循环不是从头开始,而是从上一次循环跳出的地方开始左右循环(因为可能下一次循环跳出的地方别上一次的小,所以也要遍历前面的),这样程序的速度在后面也很快了。
我讲这个的道理就是在实际运用中,要具体的分析程序慢的真正原因,才能达到最佳的优化效果。
本文来自CSDN博客,转载请标明出处:
MMX开发文档
IMMX简介
Intel的MMX™技术是对Intel体系结构(IA)指令集的扩展。
该技术使用了单指令多数据技术(SIMD)技术,以并行方式处理多个数据元素,从而提高了多媒体和通讯软件的运行速度。
MMX™指令集增加了57条新的操作码和一个新的64位四字数据类型。
MMX™技术提高了很多应用程序的执行性能,例如活动图像、视频会议、二维图形和三维图形。
几乎每一个具有重复性和顺序性整数计算的应用程序都可以从MMX™技术中受益。
对于8位、16位和32位数据元素的处理,改善了程序的性能。
一个MMX™指令可一次操作8个字节,且在一个时钟周期内完成两条指令,也就是说,可在一个时钟周期内处理16个数据元素。
另外,为增强性能,MMX™技术为其它功能释放了额外的处理器周期。
以前需要其它硬件支持的应用程序,现在仅需软件就能运行。
更小的处理器占用率给更高程度的并发技术提供了条件,在当今众多的操作系统中这些并发技术得到了利用。
在基于Intel的分析系统中,某些功能的性能提高了50%到400%。
这种数量级的性能扩展可以在新一代处理器中得到体现。
在软件内核中,其速度得到更大的提高,其幅度为原有速度的三至五倍。
MMX的缺点:
由于MMX的运算指令必须在数据配对整齐的时候才能使用,所以使用MMX指令要比普通的汇编指令多余许多分组配对的指令,如果运算不是特别的整齐的话,就要浪费大量的时间在数据的配对上,所以说MMX指令也不是万能的,也有其很大的缺陷。
同时MMX指令在处理16位数据的时候才能发挥最大的作用,处理8位数据要有一点技巧。
而处理32位数据,MMX指令几乎没有什么加速能力。
(考虑分组耗时的话)
IIMMX基本指令集
具体细节请参阅《INTEL体系结构MMX技术程序员参考手册》第五章
2.1拷贝指令
movq:
64位数据拷贝,如果内存8位对齐的话,是一个64位写,否则2个32位写。
movd:
32位数据拷贝,注意:
如果从内存向MMX寄存器拷贝,MMX高32位清零!
2.2分组指令
分组指令是MMX特有的,所以对于它我们要特别的关注。
分组指令基本上可以分为2类,一类是不带符号紧缩的,一类是带符号紧缩的。
现在我们分别予以介绍:
①punpcklbw/punpcklwd/punpckldq(l表示低位分组,bw8位,wd16位,dq32位):
它是简单的将两个MMX寄存器的低32位交错组合为一个64位数据。
所以它是不能将长数据转换为短数据的。
②packuswb将16位数据转换为无符号的8位数据。
所以可以将两个MMX寄存器不交错的合为一个64位数据。
③packsswb/packssdw将32位-》16位,16位-》8位,都是有符号的数据。
2.3运算指令
加法运算指令:
paddb(w)(d):
没有越界保护的加法,当越界的时候仅仅丢弃超出范围的高位比特,(b)(w)(d)分别为8,16,32位加法;paddsb(w):
具有越界保护的有符号加法,当上溢的时候为0x7fff,下溢的时候为0x8000;paddusb(w):
具有越界保护的无符号加法,当上溢的时候为0x7fff,下溢的时候为0x0。
减法运算指令同上;add改为sub。
乘法指令:
pmullw/pmulhw是4个16位数据的乘法,pmullw中是结果的低16位,pmulhw是结果的高16位。
pmaddwd乘加指令。
2.4逻辑指令,移位指令和EMMS指令
细节参见《INTEL体系结构MMX技术程序员参考手册》。
IIIMMX经典处理策略
①数据输入输出:
在输入数据的时候,经典的处理方法是将一个数组整个“Load”到MMX寄存器中。
这样简单同时利用了MMX64位读写数据的能力,提高了性能。
同样在输出的时候,也是将一个64位MMX寄存器中的数据内容整个“Store”到内存中。
如果实在是不能这样处理的话,就要利用移位指令了。
比如说将一个MMX内的4个16位数据分别拷贝到不同的内存变量(或者16位通用寄存器中)x1,x2,x3,x4,那么可以这样处理:
movdeax,mm1
psrlqmm1,32
movdebx,mm1
movx1,ax
movx2,bx
shreax,16
shrebx,16
movx3,ax
movx4,bx
可见如果不采用数组形式的话,输入输出将十分的麻烦。
②数据分组以及求绝对值的方法等:
细节请参阅《INTEL体系结构MMX™技术开发者手册》第五章
IV自定义组合指令
①八位无符号数的移位:
在MMX指令集中是没有8位数据的移位指令的,但是有的时候我们确实需要,所以可以用以下两个指令来实现:
psrlqmm0,1
pandmm0,0x7f7f7f7f7f7f7f7f
②如何防止计算过程中越界:
比如在计算的时候,我们有(x1+x2+1)>>1,这个时候x1+x2就会越界(8位数据),那么我们就不得不使用替代了办法,比如(x1>>1+x2>>1)这个处理是不精确的,在不需要很精确的场合,是可以使用的,但是如果结果差错1都不可容忍的话,就要进行一点处理:
pandmm0,0x01010101010101//保留数据的最后一位数
pandmm1,0x01010101010101//保留数据的最后一位数
pormm0,mm1
paddusbmmx,mm0//修正数据
(x1>>2+x2>>2):
这个处理是通用的
pandmm0,0x03030303030303//保留数据的最后两位数
pandmm1,0x03030303030303//保留数据的最后两位数
paddusbmm0,mm1
psrlqmm0,2
pandmm0,0x3f3f3f3f3f3f3f3f
paddusbmmx,mm0
③符号扩展指令:
mm0:
*,*,A,B=>现在要符号扩展为mm0:
(A符号)A,(A符号)B
movqmm1,mm0
pcgtmmm1,0//比较mm0,生成mm1:
(A符号)(B符号)()()
punpcklwdmm0,mm1
④分组指令
除了基本的分组指令以外,我们还可以利用移位指令和pandpor指令来实现分组的功能,移位主要是要产生0,这样pormm0,mm1就可以将mm0和mm1合并了。
比如:
mm0(*,*,A,B)mm1(0,0,C,D)则
psllqmm0,32
pormm0,mm1=>(A,B,C,D)当然这个例子我们可以用普通的分组指令实现,但是在某些复杂的处理中,这样的处理是必须的。
总之,要灵活运用MMX的现有指令来实现自己需要的功能。
VMMX编程心得
使用MMX技术进行编程,目的就是要提高运算速度,所以,对于如何尽可能的提高代码的效率,我们是要特别关注的。
这里,我介绍一些需要