deepway

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  1 Posts :: 9 Stories :: 1 Comments :: 0 Trackbacks

常用链接

留言簿(4)

我参与的团队

搜索

  •  

最新评论

  • 1. re: 软件运行日志[未登录]
  • 我觉得调试日志不应该只记录软件的非正常过程吧,而是记录软件的运行过程。期间有不正常的时候用warning,error和fetal来记录。
  • --hi
为避免大家翻墙,将原文贴在下面了。另外,如果下载chrome的源代码,其中就包含了tcmalloc的,它里面已经帮你把这篇文章要做的都做了,用脚本的形式。

Hi,

I wanted to post a little information about some changes that I'm
working on finishing up for the windows version of tcmalloc. If
you've ever had trouble overriding malloc/free on windows, you might
find this useful.


With Chrome, we wanted to override the default C runtime allocators
with TCMalloc. Chrome links the C runtime statically (/MT) in
VS2005. Unfortunately, VS2005 does not have a static mechanism to
override all allocators. This sounds easy, but it is not - VS2005 and
VS2008 both use C runtimes with internal functions that cannot be
overridden. We also didn't like the runtime patching approach which
tcmalloc currently uses. So, to get static linkage to work, we take
the C runtime library from Microsoft and remove all heap allocators
from it using the LIB.EXE tool. We then implement stub functions for
the non-overridable functions in the C runtime and manually link
Chrome to use the new library.


If you want to do this too, here are the steps:


Steps
1) Create a slimmed down version of the C Runtime Library. The C
Runtime Library ships with VS2005 in $VCInstallDir\lib\libcmt.lib. We
use the script below to do this.
2) In TCMalloc's config.h, define WIN32_OVERRIDE_ALLOCATORS
3) Modify your DLL or EXE build with the following:
a) link in tcmalloc.lib by adding a Project Dependency to it.
b) in Properties -> Linker -> Input, set "Ignore Specific Library"
to "libcmt.lib"
c) in Properties -> Linker -> Input, add "mylibcmt.lib" to the
"Additional Dependencies" line.


SLIM_CRT.BAT
REM
REM This script takes libcmt.lib for VS2005 and removes the allocation
related
REM functions from it.
REM
REM Usage: prep_libcmt.bat <VCInstallDir> <OutputFile>
REM
REM VCInstallDir is the path where VC is installed, typically:
REM C:\Program Files\Microsoft Visual Studio 8\VC\
REM
REM OutputFile is the directory where the modified libcmt file should
be stored.
REM


SET LIBCMT=%1lib\libcmt.lib
SET LIBCMTPDB=%1lib\libcmt.pdb
SET OUTDIR=%2
SET OUTCMT=%2\libcmt.lib


MKDIR %OUTDIR%
COPY %LIBCMT% %OUTDIR%
COPY %LIBCMTPDB% %OUTDIR%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\malloc.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\free.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\realloc.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\calloc.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\new.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\delete.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\new2.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\delete2.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\align.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\msize.obj %OUTCMT%


LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\heapinit.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\expand.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\heapchk.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\heapwalk.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\heapmin.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\sbheap.obj %OUTCMT%
LIB /IGNORE:4006,4221 /REMOVE:build\intel\mt_obj\smalheap.obj %OUTCMT%
小内存分配器主要作用是“减小内存碎片化趋势,减小薄记内存比例,提高小内存利用率”,从性能上说,系统内存分配器已针对小内存分配进行优化,单纯使用自定义的小内存分配器,对性能帮助不会很大。内置分配器意义还是体现在,实现无锁分配,避免API调用切换开销。
CRT自身new-delete会用到500个时钟周期,而一个CS会消耗50个时钟周期,一个mutex会用到2000个时钟周期,以上是无竞争的情况。所以,如果用mutex做互斥,那还不如用系统的分配器;如果用CS,也不见会好多少,因为CS会随锁竞争加剧大幅增加时间,甚至会超过mutex。
所以结论是,对于单线程,内置分配器有一定的价值;对于多线程,带锁内置分配器基本上可以无视了(至少对于winxp以后是这样,win2k好像要打补丁)呵呵,从你说的情况来看,很有可能你们原来的分配器用mutex帮倒忙了。

tcmalloc中的唯一亮点应该是,如何做到跨线程归还内存,又能保持高性能,猜想可能使用了某种二级分配策略,内存块可以属于任何线程的内存池,归还到那个线程内存池,就由这个内存池管理。由于各个线程的分配和释放多半不平衡,有线程池会撑满,有的会不足。估计撑满的就会归还到公共内存池。第一级分配无锁,如果内存池不足了,就进入第二级带锁批量分配,而且第二级分配会先从公共内存池获取,如果还不够,这才使用系统内存分配,这该算是第三级分配了。


最后,tcmalloc也是可以用于MT版本的哦,详见(要翻墙才能看见)http://groups.google.com/group/google-perftools/browse_thread/thread/41cd3710af85e57b
re: C++的流设计很糟糕 maxime 2010-08-04 00:36
最后,我感觉楼主,似乎想在一个输出语句中,输出很长很长的,可能跨越多次物理输出的内容。

这样做,首先代码不易理解,不易修改维护。

根据本人的实际经验来看,日志输出最好还是按实际物理行为单位比较好,所以glog没有支持所谓endl特性。

楼主可能真正担心的是另一个问题,在多线程程环境下,想要连续输出的几行文本,会被其他线程打断,以致阅读性变差。

对此,我建议,如果不希望被打断,使用glog那就需要八几行输出写在一个glog句子,作为一次原子输出就行了。但是,如果楼主对这样的原子输出,还要求再被分成多次物理输出,那这是为什么呢?有这个必要吗?既然打算连续输出几行,且在一个语句之中,整个语句时间是非常快的,对观察者而言,一次原子输出是由一次物理输出还是多次物理输出构成,没有任何实际意义。








re: C++的流设计很糟糕 maxime 2010-08-04 00:22
5. 最后谈一下,C++流的真正缺点?

从安全性的角度讲,C++流相对sprintf是一次飞跃。从实际项目来看,C++程序员的代码产出和维护量,通常会数倍甚至几十倍于C程序员,这表面了在某些问题域上,C++比更有开发效率。

但由此带来的问题是,在代码量少的时候,C程序员可以花时间慢慢检查代码,保证sprintf没问题。而C++程序员再这样做效率就太低了。所以才会有了C++流的方案,C++流设计者正是从实践中品尝到了sprintf的苦果。

事实是,C++语法形式,从实用性角度,的确很蹩脚。而且性能只有sprintf的1/3.不过实际环境下,性能通常不是问题,流输出很少会是一个应用系统真正的瓶颈。

蹩脚的语法,是个问题,尤其当你需要做格式控制的时候,代码可能非常长。这个问题,我的看法是,写的时候可能多花点时间,不过以后维护起来就轻松了。毕竟,我宁愿选择安全性,花三天时间去找一个缓冲区溢出是不会宁人愉悦的。当你认为语法问题很重要时,通常暗示代码管理上有问题。我通常认为代码的书写只占20%的时间,80%时间是在维护代码。维护效率远比书写效率重要。

在C++领域,新发明似乎是没有止境的,有一个新的,利用重载“()”操作符的格式化库出现了,具体我本人没有用过,看起来还不错,据说在性能上优于sprintf,在安全性上不输于C++流,在格式上类似sprintf。由于缺乏大规模应用,实际情况如何,还不好说。


就我本人而言,我认为C++流的效率和格式问题,并非致命问题,所以也就不急着使用更先进的东西了,短期内我C++流仍是最好的格式化输出工具。除非,项目主要业务逻辑就是格式化字符串,那也许我会选择sprintf或者其他的东西。















re: C++的流设计很糟糕 maxime 2010-08-04 00:03
4. 关于“假如需要考虑多线程的话,那么一次输入有多个函数函数中被调用”

要在多线程进行IO操作,肯定是要用锁的,就算你不直接用,系统API的流API,比如Win32的WriteFile,也是要用的。


所以,答案很简单,用锁。问题不在于有几次函数调用,而在于能否让这几次函数调用位于同一个锁当中。

传统上,一个sprinf,你可以加一次锁,就够了。
而现在呢,分成了好几次调用,那么就在这几次调用之间和之后加锁就行了,在本例中,也就是那个被认为过于调用繁琐的临时对象了,在它的构造函数加锁,在它的析构函数中解锁,就能保证输出的原子性。如果这样还不满意,还可以考虑流操控符加锁,不过有点危险。


不过呢,说道最后,如果你明白,那个看似效率低下的临时对象其实对整行的输出做了缓存,所以在glog中,临时对象中是没必要用锁的,因为临时对象中保存的字串是不会被多线程打断的,它能够保证所有的“<<”调用在输出上的原子性。最后析构函数中,真正进行输出时,在下层的实际输出位置,实际上是有锁。



re: C++的流设计很糟糕 maxime 2010-08-03 22:50
3. 关于“要使用这门语言写出正确的程序来,需要了解底下多少的细节呢?!”

首先答案是,不需要知道细节,只需要知道“规范”。C++真正的问题不是太复杂,而是在实践中缺乏规范,尤其在中国的软件作坊里面。就像你会开汽车一样的,你没比要知道汽车发动机原理,同样能把汽车开好。因为你遵守了开汽车的规范,比如启动的时候,慢加油门。

很多人的问题在于,在思想上,忽视了规范,到头来却怪东西太复杂。

其次是了解细节,可以工作更深入。再说了,就算复杂,C++能有多复杂,一个C++语言里面能有多少东西呢?相比一个Java库,这点东西真算不了什么。很多人掌握不好,是因为没有正正经经的机会去学,去练。这点像数学,学的时候比较枯燥,不管怎么说,这点东西就叫复杂,那只能说,做的应用系统太简单。
re: C++的流设计很糟糕 maxime 2010-08-03 22:38
2.所谓“比如log << "hello " << "world",是无法判断到底在输出"hello"还是"world"的时候上面的参数输入已经结束了”

其实,这个问题,流的设计者早已考虑到了,std::endl就是用来干这件事情的。事实上,自定义的流操控符,还可以干很多事情比如:
std::cout << v1 << mylock(v2) << v2 << myunlock(v2);
上面的mylock,myunlock就是自定义的操作符,用来给v2加锁解锁,而不输出任何字符。它到底能做什么,取决于你的想象力。我总爱把C++比作机械行业的钳工,他们比不上机器的速度,但没他们不行,很多事情机器做不了。使用正确的工具做正确的事情,如果你感觉不对,先想想选对工具没,而不是抱怨工具很烂。

额外,说明一点,有人告诉你sprintf存在写错的可能性,所以,你可以说,如果别人忘了写上他的endl怎么办?

我来告诉你吧,写错了其实没什么大不了的,问题关键是,写错了会带来什么危害。sprintf写错了,可能带来的是内存溢出覆盖,这才是我们恐惧他的原因,一个内存溢出带来的危害我就不说了。
反之,少写了一个endl,最多就是两行日志重叠,或者一个日志输出时间晚了一会儿。如果你真看到这个情况,把endl加上去就行了。

不知道现在是否能理解了,不要害怕bug,不要害怕写错,要怕会让你掉进深渊的bug。我得承认,这是C/C++的弱点,java/C#相对好很多。
C++最害怕的,就是指针操作,内存覆盖可以毁掉整个程序的运行基础,却不容易找到错误的代码。但这也是C++的优点,C++为什么要用流替换C的sprintf,就是要减少内存覆盖错误的机会。当然,C++中仍然有这种错误的机会,因为抛弃了指针,C++和Java就没区别了。如果说C是做操作系统的,java是做应用的,C++就是做系统和应用结合部的,只有理解了这点,你才能用好C++,而不是抱怨,它既没C简单,也没java安全。
事实是,C++就是这么个怪胎,比Java更快,比C更安全更有开发效率。



















re: C++的流设计很糟糕 maxime 2010-08-03 22:20
感觉楼主对C++语言还缺乏较为深入的理解,下面对几个问题做点说明,其实很简单,很多人不懂,是因为C++标准教材没这些东西。C++是一门在工业实践中成长起来的语言,工业界发明这些东西是因为需要,学院派却总跟不上进度,教材几十年一变。要用C++,就要做好准备,否则,你干嘛不用Java或者C#。

1. 关于所谓“频繁的构造/析构开销大”
你首先要清楚“构造”和“析构”中编译器到底为你做了什么。1.)分配对象空间:如果是在堆中分配对象,那么会有一个代价很大的堆分配(new,在2.7G的CPU上单线程new性能是5M次/秒);如果在堆栈上分配,内存分配代价几乎为零。2)调用构造函数和析构函数,这有两个开销,一个是调用本身的开销,一个是函数体内部代码的开销,很明显,前者才C++带来的额外开销。我可以告诉你的是,如果是内联,这个开销为0,如果不是内联,这个开销在2.7G的CPU上单线程性能是1200M次/秒,作为类比,2.7G的CPU上单线程可以做400M次32位整型变量写入操作,也就是这个开销比写一个整型变量还小。
现在,看看你说的情况,局部对象的构造和析构,每次的代价比写一个32位整型的变量还小得多,相比每次日志输出至少十几个字节的内存拷贝,这点开销完全可以忽略不计,除非打算每秒中打算做1M次的日志,它带来的代价不占用1%的CPU而已,不过事实是,每秒钟写不了1M次的文件IO。
最后从设计的角度考虑这个问题,你的系统打算每秒中写多少次日志,应该心理有数吧,从这个意义上,从设计的角度,上面我写的那些分析毫无必要,只是为了加深对C++的理解,事实是,即便“频繁的构造/析构开销大”很大,它们仍然不是系统的真正瓶颈,没必要过早优化。如果它们真成了瓶颈,你应该做的事情是,调整成合理的日志策略。