LLVM小结
如果说gcc是FSF的传奇,llvm就是Chris Lattner的小清新。当然啦,想具体看看这位四处游山玩水还GPA 4.0的大神和他的LLVM编译链还有他与苹果之间的故事的读者可以移步http://news.cnblogs.com/n/127343/。另外,据悉,FreeBSD自10.0开始将会完全采用llvm编译链编译,而之前的版本,与Linux一样,都是采用的gcc编译的。
以上,就算是“拉大旗扯虎皮”,既是给llvm做个简介,也是让大家知道本篇博文还是说的是比较“有用”的东西,而不是什么虚无飘渺的东西。
llvm在编译链中的环节其实是属于后端,前端可以采用gcc或者clang(从某些编译课程来看,用yacc和bison写的前端也行)。gcc想必大家都很熟悉,我也就不再赘述。clang则是专门为llvm定制的前端,据说当初开发它的一个很重要的因素就是gcc和IDE配合得不太好,而且gcc模块之间写得比较混杂,难以修改。作为前端,虽然我没有使用过配合clang的IDE(Apple developer能使用到的XCode算一个,不过我还没真正用过),但确实,在终端下调试的时候clang给出的输出比gcc的要好理解得多。不过clang是名副其实的CLang,只支持C/Obj-C/C++这三种语言;而gcc当然是无所不包无所不能的了。这里我上个C程序的例子吧(如果觉得不过瘾,你可以自行尝试C++,据说C++的类模板最能体现这两者的区别,也可以直接去http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html):
1 #include <stdio.h>
2
3 int main(){
4 return 1? 2 3
5 }
我们来看看clang和gcc给出的出错信息分别是什么(为了证实确实是这段程序,我先cat了它):
首先,其实gcc没有给全出错信息,如果你只照gcc的输出在第4行14列添加了冒号,那么你再次编译的时候它还会告诉你这一行的末尾缺分号;而clang不仅给全了错误而且每个错误都是出错信息一行,源代码一行,建议一行(note也是这样的),并且输出有色彩分别。笔者的gcc版本是4.7.3,clang版本是3.2-1,都是Ubuntu 13.04下最新的(顺带插一句:不知道为什么llvm 3.3已经发布很久了,Ubuntu还没有正式采用)。我觉得前端有一个很重要的功能就是报错,clang做得很好,至少这点比gcc做得好。苹果的产品在“看得见”的地方一向都做得很好,clang也是这样的。
编译器的前端,不仅要能生成AST,而且如果源代码有语法错误或者警告(虽然程序猿貌似都忽略警告,但实际上有的时候警告还是很有用的)要应当能给我们很好的指出,毕竟我们是人不是机器,要人性化一点;另外,后端又要能够在前端分析出的AST为程序做出极致的优化,当然,我不是说程序优化就靠编译器了,程序猿本身就应当编写出好的代码,但是编译器确实应当负责优化程序。llvm和gcc到底谁优化得更好,大家说法不一,给出的数据差距也很大,但是呢,不过呢,“LLVM has been awarded the 2012 ACM Software System Award!”,估计肯定是不会差的了。除了各种天花乱坠的数据外(如果非要看的话,我还是给个早年的数据吧:http://llvm.org/pubs/2007-07-25-LLVM-2.0-and-Beyond.pdf),毕竟程序最终是要给用户用的,毕竟用户体验才是最重要的,所以我们直接比较产品。举个栗子:LLVMpipe,这是一个类似于即时编译或者说在线编译的东西,它将OpenGL的代码(本来是抛给GPU的)编译为CPU可执行的代码并交由CPU执行。这个东西被很多支持老旧电脑的Linux发行版所采用,所以想来效率也不会太差。当然,效率高不高和运行快不快其实并不等价,因为运行快不快和具体的硬件环境还是有很大关系的。
说到编译优化,llvm似乎还有一个奇怪的优化方法:llvm(low level virtual machine)本身就是一种抽象的、虚拟的计算机架构,其特性介于RISC和CISC之间,llvm会先将代码编译为llvm架构的字节码(这里还是说说数据吧,从其官方数据来看,生成的字节码略多于x86的目标代码而少于SPARC的目标代码),然后可以对字节码进行JIT优化然后再翻译为目标架构的二进制代码。另外,llvm实际上采用的是一种全生命周期(lifelong)的优化策略(虽然还是很偏重静态优化),最直接的体现就是比起gcc能做到的 -O3,llvm可以做到 -O4,而且 -O4采用的是LLVMgold.so所提供的运行时库。llvm本身的设计思想就是希望做到编译时、链接时、运行时、空闲时的全方位优化。关于这些优化,在llvm官网可以找到,请移步http://llvm.org/pubs/2004-01-30-CGO-LLVM.html。
最后来说一下llvm和gcc的兼容性,我前面只是说了llvm的后端兼容gcc的前端生成的AST,其实llvm对gcc的兼容性是很高的,我现在系统的环境变量CC设置的就是clang,我编译了很多工程都不用改Makefile就可以成功编译,不过很多工程里只是采用的 -O2,让我略不爽,所以我就手动将之改为 -O4,当然也就需要修改一下CFLAGS和LDFLAGS,为之加上llvm-config的输出。llvm本身在脚本上就设计为和gcc的兼容,所以改换编译链十分容易。
开源库CImg 数据格式存储之二(RGB 顺序)
在上一篇博客中已经初步说明了GDI和CImg数据的存储格式感谢博友 Imageshop 评论说明
CImg的说明文档中已有详细说明(详见上篇博客说明)
CImg的数据格式确实是RRRGGGBBB顺序存储的已经毫无疑问,但是其参考手册中对其他GDI
的数据格式说明是略有瑕疵,参考手册说其他GDI的数据格式是RGBRGBRGB,其实则不是经过验证
bmp类型的数据格式应该是BGRBGRBGR 下面用code验证
说明:使用MFC 同时用CImage和CImg加载同一幅图片
void ImageIO::loadImage(const BiCImg & image, T*& pImagePlane,int& width,int& height,int& nchannels)
{
// get the image information width=image.width();
height=image.height();
nchannels=3;
int rgb_leng=width*height;
pImagePlane=new T[width*height*nchannels]; // check whether the type is float point
bool IsFloat=false;
if(typeid(T)==typeid(double) || typeid(T)==typeid(float) || typeid(T)==typeid(long double))
IsFloat=true; const unsigned char* plinebuffer;
plinebuffer=image.data(0,0);
for(int i=0;i<height;i++)
{
//plinebuffer=image.scanLine(i);
for(int j=0;j<width;j++)
{ pImagePlane[(i*width+j)*3]=plinebuffer[i*width+j+2*rgb_leng];//RGB b
pImagePlane[(i*width+j)*3+1]=plinebuffer[i*width+j+rgb_leng];//RGB g
pImagePlane[(i*width+j)*3+2]=plinebuffer[i*width+j];//RGB r }
}
}
上述为正确的顺序,若改为如下代码
pImagePlane[(i*width+j)*3]=plinebuffer[i*width+j];//RGB r
pImagePlane[(i*width+j)*3+1]=plinebuffer[i*width+j+rgb_leng];//RGB g
pImagePlane[(i*width+j)*3+2]=plinebuffer[i*width+j+2*rgb_leng];//RGB b
实验效果如下图
右图为原始图片,明显左图的蓝色部分取代了原始图片的红色应该是BGR缺写成了RGB