为什么C语言需要函数声明

2013年8月13日 没有评论 2,574 人阅读过  

C语言使用函数前为什么要声明是个老问题了,还是没忍住想简单记一下,事情原于同事写的一段测试代码。

前两天同事拿来一段C代码让我帮忙看下,main()函数里面调用了一个另一个函数,该函数返回double值,在函数内部return之前printf打印出来的返回值和在函数调用后的返回值差别非常大,我刚开始迷迷呼呼还奇怪了半天,后来把编好的binary反汇编看了一眼就知道怎么回事了,假设函数名是func,在汇编代码的main代码块里面发现这样一句话:

callq  400550 <func>
cvtsi2sd %eax,%xmm0

我次凹,这是怎么回事,x86_64汇编不是浮点型返回值不是会直接放在xmm0寄存器里面吗,为什么还会有一条奇怪的cvtsi2sd这样的指令,于是就意识到main代码块和func函数没有在一个c文件里面,而在编译的时候两个源文件是单独进去编译的,在编译完成后才会由链接器把目标文件链接起来,这也就是为什么声明是必须的,如果函数在其它的源文件中定义,没有在引用的源文件中声明,那编译器只知道要调这样一个函数,既不知道该给它传什么参数(传任务参数编译器都会认为是合法的),也不知道该返回什么值,x86_64上编译器把整形或地址返回值放在eax/rax中,浮点型放入xmm0中,单从返回值上来看,编译器默认是把这个陌生函数的返回值定为整形,由于在外层期望的是个浮点型,编译器在调用完func后就把整形的返回值再转换成浮点型放入xmm0中,也就导致了上述的问题。

...
分类: C/C++ 标签:

AVX-SSE-TRANSITION带来的性能开销

2013年8月1日 没有评论 2,952 人阅读过  

我写的技术类的文章都没有系统地介绍某项技术,自认为没有那个实力,因此都是一些平时工作中遇到的坑,总结一下也算对自己有个交待。

这次遇到的问题是avx-sse-transition这个坑。先介绍一下背景,这两天在对某算法代码进行优化,其中的向量计算部分使用SIMD来做加速,之前我们的集群CPU都是nhm微架构,只支持sse,而集群上使用的gcc版本也是4.1.2,所以不论是使用intrinsic也好,还是使用汇编也好,都没有问题,后来我们部署了一个SandyBrige CPU的新集群,为了充分利用cpu的向量计算能力,我开始在代码中使用avx来提升算法的性能,而gcc应该是从4.6才开始支持avx,因此对于4.1的gcc而言,无论是intrinsic还是内嵌汇编还是纯汇编,gcc都是无法编译的,所以我想到的办法是在线下使用汇编编写向量运算相关的函数,然后用高版本的gcc编成目标文件,再扔给线上的环境去链接。

没想初次用avx就碰到了一个坑,举一个向量运算最简单的例子,两个向量a和b做内积,C的实现方法如下:

float res = 0.0;
for (i = 0; i < size; i++)
	res += a[i] * b[i];
...
分类: Linux 标签:

由原子操作引起的关于Cache的讨论

2013年5月30日 没有评论 2,938 人阅读过  

最近MPI集群有用户抛出这样一个问题,当MLR算法或PLSA算法与PLDA同时运行在某个节点时,MLR的效率会降低二十倍,PLSA的效率也会下降非常厉害,而与其它的算法重合时,即使两个算法的程序都可以所有CPU吃满,效率也未必会下降如此厉害,用户怀疑是我的PLDA代码设计的问题,这个问题也引起了大家比较激烈的讨论。

大家对于PLDA的质疑源自于我引入了原子操作共享了一个大矩阵,而用户理解的是PLDA的原子操作过于频繁,频繁地锁住内存总线导致运行在同一节点上的其它任务性能退化严重,这里面其实是存在一个误区的,首先看看我实现原子操作的方法:

#define atomic_incl(v) __asm__ __volatile__("lock incl %0;":"=m"(v))

使用lock指令前缀,实现原子操作很常用的方法,而大家质疑的也正是这个lock前缀,我查过Intel的手册,在PentiumPro之前是的CPU使用lock前缀确实是需要对总线加锁的,这种情况对于性能的影响是非常大的,而新的CPU设计都采用了writeback的内存访问方式,使用lock前缀时,CPU的行为不再是去锁住内存总线,而是cache line级别的lock,我一直是这样理解的,在讨论的时候专用计算组的大牛长仁指出,锁cache的概念是不存在的,这和我理解的确实不一样,而且我查了Intel手册和相关的官方文档,里面对于lock前缀的介绍都用到了cache lock这样的说法,而lock前缀实现原子操作的原理也确实是通过类似对cache line加锁来实现的,后来我又和长仁请教了下,他告诉我其实cache上没有lock的功能,硬件实现上cache不能实现lock,其实是通过cache协议来实现的,声称为cache lock是方便软件开发人员理解。

...
分类: Linux 标签:

ruserok()引发的MPI集群故障

2013年5月27日 2 条评论 2,724 人阅读过  

最近我们的MPI集群遇到一个问题,集群管理用的是开源的Torque,运行时需用root账户启动一个pbs_server进程,该服务进程在运行过程中会创建一些中间文件,而创建文件的目录权限都是属于root用户,最近的一次集群故障导致用户不能提交任务,故障发生时我们观察到集群的pbs_server进程的有效用户ID(EUID)变成了普通用户(xiaowen.zl),从而导致pbs_server丧失了对某些关键目录的写权限,从而导致用户提交任务失败。

故障发生时使用ps命令看到的pbs_server进程已经运行了37天,而在这期间集群是运行正常的,因此排除了普通用户xiaowen.zl启动pbs_server的可能性,问题发生的可能性就只可能是pbs_server在运行过程中调用了setuid(),seteuid()或setresuid()这一系列函数。

我们在测试集群中通过脚本对pbs_server加压,瞬间提交大量的job,发现在某些瞬间pbs_server进程的euid确实会由root用户切换到普通用户,而后又切换回来,在某些极端的情况下切换成普通用户后未能切换回root,从而出现我们集群中遇到的job不能提交的故障问题。

...
分类: Torque/Maui 标签:

读书笔记-《细说清朝》

2013年4月20日 1 条评论 3,438 人阅读过  

读历史首先是要看写这段历史的人的口碑和人品,再去看读者对这本书的评价,历史被歪曲的太多,没有经过沉淀的历史书籍从不敢读,黎东方教授的细说系列长久以来被认为是历史读物中的上品,而清朝又是和我们衔接最紧密的一段历史,对于清朝发生的那些事情多多少少都有些了解,却从来没有完整客观地知道那个时代都经历过什么,也从来没有对那个时代的人有一个客观的评价,于是鼓起勇气读完了《细说清朝》。

读这本书感觉作者仿佛一位说书人,将那个年代的历史对你娓娓道来,清朝瞬间的崛起,短暂的辉煌,上百年的内忧外患都在本书中尽然展现,这本书更好地帮我们了解了那个时代的历史,更好地了解了那个时代的人。

中国的历史是帝王的历史,相比之下美国的历史则是平民的历史,这一点也不假,那么厚的一本《光荣与梦想》也不过讲了区区四十年的美国历史,刨除几个大的历史事件,绝大多数篇幅都在描述民众生活常态,以至于读起来会感觉枯燥很多,甚至一部《阿甘正传》都能概括美国大半部历史,相比之下四十年在中国的历史长河中不过是弹指一瞬,中国有太多的四十年,有太多的朝代更迭,帝王更替,而在这漫长的历史中最不值钱的也最不值得一提的便是平民百姓,思想被禁锢被奴化的中国人最容易被历史淡忘,于是历史上只留下了帝王将相。

...
分类: 读书笔记 标签: ,

记一下那些伪随机数生成函数

2013年3月27日 2 条评论 3,889 人阅读过  

今天踩了一个伪随机数生成函数的坑,与其说是个坑到不如说自己功力不够深厚,对这些随机数生成的函数族欠缺了解,先来介绍下我的问题吧。

拿了100M的数据用LDA算法来跑,单线程的时候一次迭代需要大约15s的时间,而改用两个线程跑,线程之间对不同的数据块进行迭代,一次迭代完成的时间居然需要大约50s,而且线程数越多就会越慢,因为迭代的采样函数是一个纯计算的函数,多线程的情况下每个线程的数据量要比单线程成倍地缩小,迭代速度相比单线程应该快N倍才对,现在的结果是不符合常理的,因为每个线程都是独立的训练集,线程之前没有共享数据。

其实在我用几M的小数据测试的时候就发现这个问题,当时以为是数据量小迭代速度太快,以至于在迭代完成后的线程同步占用了一些时间才导致多线程版本会慢于单线程版本,但数据量大的时候这样就不可理解了。

后来开了两个线程发现,迭代计算的时候两个CPU大部分时间居然都没有跑在用户空间,相反大量的cpu时间都耗在了系统时间,仔细看了代码,这部分就只有一个random()函数不是我的计算函数,改成定值之后测试速度马上彪上去了,那就可以断写问题就出在这里。

random()函数是不可重入的函数,不保证它是线程安全的,但我看了glibc的代码发现, 其实它在glibc的实现里面是线程安全的:

...
分类: C/C++ 标签:

读书笔记-《观念的水位》

2013年3月17日 3 条评论 4,199 人阅读过  

网络上对于刘瑜的评论似乎趋向于两个极端,说她好的自然大有人在,说她不好的也不在少数,还有人调侃地把她和安妮宝贝和孤郭敬明相提并论,不同的人有不同的政治立场,刘瑜作为一名时评作家,她的观点自然有人赞同有人反对,这都无可厚非,不管是《民主的细节》还是刚读完的这本《观念的水位》,对我而言都是非常有营养的。

根据百度百科里面的介绍,刘瑜在《南方周末》写时评专栏,在《新周刊》写书评和影评专栏,也正像作者在序言中提到的,《观念的水位》这本书就是这些专栏文章的合集,所以前半部分大多是时政评论,后半部分就是书评和影评,我对书评和影评不太感冒,没有读过的书没看过的电影被别人一评价,这书这电影再看起来就没有原来的味道了,观点就会不自觉得被这些评论牵着鼻子走,所以对我而言前半部分的吸引力要远大于后半部分。这本书把一些离散的文章集合在一起,作者也没有刻意地去让他们之前有什么衔接,所以读起来思维可能需要有一点跳跃,比如前一篇在讲伯林墙,后一篇就已经在说曾国潘了。

书中有一篇文章名字和书名相同,以这篇文章的标题作为书名,我相信作者有她的意图,此书的出版意在提高读者的政治观念,文化观念,历史观念,因为只有民众观念的提高才能促成社会变革,这篇文章中有一段话我很赞同,在这里引用一下:

我心中理想的社会变革是一个“水张船高”的地程:政治制度的变革源于公众政治观念的变化,而政治观念的变化又根植于人们生活观念的变化。水涨了,般自然浮起来。我观察社会变革的动力,不那么关注船上有没有技艺高超的船夫,而更关注水位的变化。

...
分类: 读书笔记 标签:

读书笔记-《万历十五年》

2013年3月9日 没有评论 3,116 人阅读过  

多书,科幻玄幻政治历史,有些书是用来娱乐打发时间,有些书是为了开阔眼界,丰富内涵,每读完一本总会有些许的思考,但却一直没有把读完这本书带来的收获记录下来,今天决定开始对读过的>每本书写读书笔记,不能让这些书的内容只是作为过眼云烟,记录下来也算对自己有所沉淀。

黄仁宇的这本《万历十五年》是从杭州回北京的飞机上读完的,读这本书花了不少时间,这期间也主要因为工作太忙,工作之余还要抽出时间来补专业知识,所以就冲淡了读这些文学作品的时间。出差唯一的好处是可以>在旅途中读书,这个过程是很享受的。

《万历十五年》这本书写的是万年皇帝第十五年也就是公元1587年前前后后的历史,作者黄仁宇博士履历丰富,学识也很渊博,《万历十五年》在我看来没有博人眼球的华美文字,字里行间也是朴实无华,作者站在一个>后来人的角度上客观公正地去审视那个年代的历史,为我们重现了一个典型的中国封建王朝有政治和民生。

...
分类: 读书笔记 标签:

Sheepdog块设备驱动死锁的问题

2013年1月15日 2 条评论 4,404 人阅读过  

Sheepdog的块设备驱动写好有一段时间了,陆续修改了几个版本之后,近期进行压测的时候遇到一个死锁的问题,头痛了一个多星期,今天请教了一下淘宝内核组的@伯瑜同学,在他的热情帮助下分析出来了死锁出现的原因,解决的办法暂未找到,或者说这问题无解,待我细细说来。

问题是这样的,在把Sheepdog中的某个VDI挂载成块设备之后(/dev/sheepa),我在sheepa上安装了一个debian,然后启动QEMU,启动盘就设为/dev/sheepa:

qemu-system-x86_64 -m 512 -hda /dev/sheepa

启动完成后开始狂做IO,也没用啥复杂的办法,就是dd

dd if=/dev/zero of=xxx bs=4096 count=1000000

就这样在写入几个G的数据后就有可能会出现死锁,而且出现的机率很低,复现一次要花好长时间,有时候几十个G的磁盘都被Sheepdog写满了也复现不出来。死锁发生时,sheep其中一个进程会D住(一个sheep会启动两个进程,一个是主进程,一个是日志进程),这时候QEMU也会hang住,甚至在有些极端的情况下ps -ef也会hang住。

我在sheep block driver里面加入debug信息定位到hang住的位置,是在read_reply()这个函数里面,这个函数是在等待sheep的请求响应,这个时候sheep已经hang住当然不会响应,于是QEMU的IO一直没有返回,也会一直hang在那里,可为什么sheep会hang住呢,这个问题是当时我没想明白的,后来经过伯瑜大神的指点才明白过来。

...
分类: Kernel 标签: ,

Sheepdog块设备驱动

2012年12月28日 没有评论 3,629 人阅读过  

最初刚开始玩Sheepdog的时候就想着要是能把它作为一个像硬盘一样的块设备,直接挂在终端机器上就好玩了,直到最近工作不是特别忙的时候才有时间想想这事情,花了好几个晚上终于把给Sheepdog写好了一个块设备驱动sheepdev,可以把集群中创建好的VDI直接注册到kernel中,然后使用用户态的相关工具fdisk/mkfs/mount等对这个块设备进行分区格式化挂载等,代码已经提交到upstream,至于还要修改几次,最终能不能merge到upstream中去都还不确定,先简单记录一下自己的工作经历。

块设备驱动作为一个kernel module,在module init的时候我注册了一个proc entry /proc/sheep,通过这个proc entry对这个块设备进行控制,比如:

echo “add 127.0.0.1:7070 a5d05d” > /proc/sheep

可以将VDI Id为a5d05d的VDI作为一个块设备安装到本地,/dev目录下会出现一个/dev/sheepa这样的块设备文件,添加第二个后出现/dev/sheepb,以此类推。

也可以通过下面的方法来删除已经注册上来的块设备:

echo “del sheepa” > /proc/sheep

在安装块设备的时候根据用户提供的VDI Id和sheep地址,首先来向sheep创建一个长连接,并通过该连接向sheep获取该VDI的inode数据,该长连接创建后会一起保持用于driver与sheep的通信,只有获取inode的过程是同步的,后期的读写操作都是异步进行的。

...
分类: Kernel 标签: ,