re: 推薦一個跨平臺內存分配器 maxime 2010-08-07 08:31
為避免大家翻墻,將原文貼在下面了。另外,如果下載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%
re: 推薦一個跨平臺內存分配器 maxime 2010-08-07 08:27
小內存分配器主要作用是“減小內存碎片化趨勢,減小薄記內存比例,提高小內存利用率”,從性能上說,系統內存分配器已針對小內存分配進行優化,單純使用自定義的小內存分配器,對性能幫助不會很大。內置分配器意義還是體現在,實現無鎖分配,避免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++的理解,事實是,即便“頻繁的構造/析構開銷大”很大,它們仍然不是系統的真正瓶頸,沒必要過早優化。如果它們真成了瓶頸,你應該做的事情是,調整成合理的日志策略。