20

我正在研究軟實時事件處理系統。我想盡量減少我的代碼中有非確定性時間的電話。我需要構造一個由字符串,數字,時間戳和GUID組成的消息。可能是boost::variantstd::vector的。關於使用和濫用alloca

我一直想在以前的代碼中使用alloca。然而,當我們研究系統編程文獻時,總會對這個函數調用提出大量的警告。就我個人而言,我無法想象過去15年來沒有虛擬內存的服務器類機器,並且我知道Windows堆棧一次只增加一個虛擬內存頁面,所以我假設Unices也是如此。這裏再也沒有磚牆了,堆棧和堆一樣可能用盡空間,所以給了什麼?爲什麼人們不會通過aloca來加盟?我可以想到很多負責使用alloca的用例(字符串處理任何人?)。無論如何,我決定測試性能差異(見下文),並且alloca和malloc之間存在5倍的速度差異(測試捕獲了我將如何使用alloca)。那麼,有沒有改變?只要我們能夠絕對確定我們物體的壽命,我們是否應該謹慎行事並使用alloca(包裹在std::allocator中)?

我厭倦了生活在恐懼之中!

編輯:

好了,所以有限制,對於Windows是一個鏈接的時間限制。對於Unix來說,它似乎是可調的。它似乎是一個頁面對齊的內存分配器是爲了:D任何人都知道一個通​​用的便攜式實現:D?

代碼:

#include <stdlib.h> 
#include <time.h> 

#include <boost/date_time/posix_time/posix_time.hpp> 
#include <iostream> 

using namespace boost::posix_time; 

int random_string_size() 
{ 
    return ((rand() % 1023) +1); 
} 

int random_vector_size() 
{ 
    return ((rand() % 31) +1); 
} 

void alloca_test() 
{ 
    int vec_sz = random_vector_size(); 

    void ** vec = (void **) alloca(vec_sz * sizeof(void *));  

    for(int i = 0 ; i < vec_sz ; i++) 
    { 
     vec[i] = alloca(random_string_size());  
    } 
} 

void malloc_test() 
{ 
    int vec_sz = random_vector_size(); 

    void ** vec = (void **) malloc(vec_sz * sizeof(void *));  

    for(int i = 0 ; i < vec_sz ; i++) 
    { 
     vec[i] = malloc(random_string_size());  
    } 

    for(int i = 0 ; i < vec_sz ; i++) 
    { 
     free(vec[i]); 
    } 

    free(vec); 
} 

int main() 
{ 
    srand(time(NULL)); 
    ptime now; 
    ptime after; 

    int test_repeat = 100; 
    int times = 100000; 


    time_duration alloc_total; 
    for(int ii=0; ii < test_repeat; ++ii) 
    { 

     now = microsec_clock::local_time(); 
     for(int i =0 ; i < times ; ++i) 
     { 
      alloca_test();  
     } 
     after = microsec_clock::local_time(); 

     alloc_total += after -now; 
    } 

    std::cout << "alloca_time: " << alloc_total/test_repeat << std::endl; 

    time_duration malloc_total; 
    for(int ii=0; ii < test_repeat; ++ii) 
    { 
     now = microsec_clock::local_time(); 
     for(int i =0 ; i < times ; ++i) 
     { 
      malloc_test(); 
     } 
     after = microsec_clock::local_time(); 
     malloc_total += after-now; 
    } 

    std::cout << "malloc_time: " << malloc_total/test_repeat << std::endl; 
} 

輸出:

[email protected]:~/test$ ./a.out 
alloca_time: 00:00:00.056302 
malloc_time: 00:00:00.260059 
[email protected]:~/test$ ./a.out 
alloca_time: 00:00:00.056229 
malloc_time: 00:00:00.256374 
[email protected]:~/test$ ./a.out 
alloca_time: 00:00:00.056119 
malloc_time: 00:00:00.265731 

- 編輯:家用機,鐺,和谷歌的結果perftools--

G++ without any optimization flags 
alloca_time: 00:00:00.025785 
malloc_time: 00:00:00.106345 


G++ -O3 
alloca_time: 00:00:00.021838 
cmalloc_time: 00:00:00.111039 


Clang no flags 
alloca_time: 00:00:00.025503 
malloc_time: 00:00:00.104551 

Clang -O3 (alloca become magically faster) 
alloca_time: 00:00:00.013028 
malloc_time: 00:00:00.101729 

g++ -O3 perftools 
alloca_time: 00:00:00.021137 
malloc_time: 00:00:00.043913 

clang++ -O3 perftools (The sweet spot) 
alloca_time: 00:00:00.013969 
malloc_time: 00:00:00.044468 
+0

似乎,啓用優化的叮噹測試是錯誤的。對alloca的內部調用被優化(刪除),因爲沒有副作用(在llvm IR代碼級別)或其結果的使用。 – osgx 2011-05-18 01:53:28

+0

我曾經與傢伙(EE's)合作過,他們曾經構建商用嵌入式系統中使用的硬件,如有線公司網關。他們分配了一個固定大小的緩衝區,然後重新使用它。從未進入內存管理器。 – jww 2015-07-27 05:39:28

回答

14

首先,即使有很多虛擬內存並不意味着你的進程將被允許填充它。在* nix上有堆棧大小的限制,而堆是更寬容的。

如果你只打算分配幾百個字節,那麼一定要繼續。除此之外的任何事情都將取決於任何給定系統上的限制(ulimit),這只是一個災難的祕訣。

Why is the use of alloca() not considered good practice?

在我的開發工作(Gentoo的)我有8192 KB的默認堆棧大小限制。這不是很大,如果alloca溢出堆棧,那麼行爲是不確定的。

+0

我想知道,你知道在Unix上是否有某種受控制的退出信號用於堆棧溢出? – 2011-04-27 17:06:27

+1

快速「man 7 signal」不會顯示任何內容。據我所知,一個嚴重的錯誤堆棧溢出將導致SIGSEGV。 IIRC你可以捕獲一個SIGSEGV,但是真正的問題是你甚至可以做任何有用的事情嗎?你的堆棧已經是瑞士奶酪,運行任何操作都會導致更多的堆棧使用,甚至誰知道會發生什麼。 – 2011-04-27 17:10:48

1

窗口堆棧不會增長 - 它的保留大小是在鏈接時設置的,但這個大小內的頁面只會根據需要進行提交。見http://msdn.microsoft.com/en-us/library/ms686774%28v=vs.85%29.asp。由於默認保留大小爲1Mb,因此在使用alloca()時可以輕鬆超過此值。

+0

你是對的,我忘記了細節,但是,我們正在談論一個重要的限制。我可以設想一些超過1MB的應用程序。但我希望整個保留的虛擬地址限制至少在32-128 mb之間(在32位系統上)。我想看一些我必須看的東西。 – 2011-04-27 16:59:35

6

我認爲在理解alloca究竟是什麼時,你需要小心點。不像malloc進入堆中,通過各種緩衝區的桶和鏈接列表進行搜索,alloca只需將你的堆棧寄存器(在x86上的ESP)移動並在線程堆棧上創建一個「空洞」,在那裏你可以存儲任何你想要的東西。這就是爲什麼它超快速,只有一個(或幾個)彙編指令。

正如其他人指出的,它不是您需要擔心的「虛擬內存」,而是爲堆棧保留的大小。儘管其他人將自己限制爲「幾百字節」,但只要您知道應用程序並仔細閱讀,我們就可以分配高達256kb的內容(沒有任何問題)(默認堆棧大小,至少對於Visual Studio而言,爲1mb,您可以隨時如果你需要增加它)。

此外,你真的不能使用alloca作爲通用的分配器(即將其包裝在另一個函數中),因爲無論內存alloca爲你分配什麼內存,當該彈出的當前函數的堆棧框架時,功能退出)。

我也見過一些人說alloca並不是完全跨平臺兼容的,但是如果你正在爲特定的平臺編寫特定的應用程序,並且你有使用alloca的選項,有時候這是你擁有的最佳選擇,只要你明白增加堆棧使用的含義。

+0

所以alloca()堆棧上的內存塊比堆上快得多。但是,與malloc()相比,如何訪問由alloca()分配的內存呢?它是否也*通常*寫入/讀取更快,因爲內存的地方?謝謝! – dragonxlwang 2015-08-05 19:34:50

+1

可能,但有一點需要記住的是現代CPU在涉及到L1-3緩存以及所有預取和瘋狂執行分叉時如此複雜,以至於嘗試構建一個心理模型來解釋數據的局部性和速度收益幾乎是不可能的。 9999/10000次你不會注意或關心性能增益。在極少數情況下,如果您要在一個非常關鍵的代碼中優化非常緊密的循環,最好的辦法就是試驗,看看哪些變化具有性能增益。否則內存是內存,訪問速度可能會相同 – DXM 2015-08-10 14:02:55

3

有一點,還沒有提出afai可以看到的是stack is often contiguous,而堆不是。通常情況下,這種堆棧可能會像堆一樣耗盡內存。

在C++中,將對象實例聲明爲locals是非常常見的,它有點像alloca,但是是結構化內存而不是N字節塊 - 也許你可以把它看作是對你的主要觀點的敬意,更多地使用基於棧的內存是一個好主意。我會盡快這樣做(將一個對象實例聲明爲RAII本地),而不是在C++程序中使用malloc(或alloca)。所有free調用使異常安全...

這通常假定該對象的範圍侷限於此函數及其調用的函數。如果情況並非如此,那麼使用基於棧的內存通常不是一個好主意。

4

首先,這是因爲alloca內存很難控制。它是無類型的,儘早死亡,這使得它不是很有幫助。此外,alloca還有一些不幸的副作用,那些副作用是現在的常規堆棧變量必須動態索引而不是常量,這會影響您在訪問它們的基本操作中的性能,並且會消耗寄存器/堆棧空間來存儲動態偏移。這意味着使用alloca的實際成本不會在函數返回所需的時間內記錄下來。另外,與堆內存相比,堆棧內存非常有限 - 在Windows中,我相信默認情況下,堆棧限制爲8MB,而堆可以幾乎是整個用戶地址空間。更重要的是,最終,無論你想要返回的數據都必須放在堆上,所以你可以把它當作工作空間。

+0

你確定這是事情的方式,你的建議意味着編譯器有靜態的alloca知識,或者它們是運行時機器,可以執行你所說的事情。例如運行時機器在遇到alloca使用時創建偏移表? – 2011-05-02 23:34:03

+2

@Hassan Syed:'alloca'不是一個真正的函數。編譯器必須專門處理它。它是靜態完成的。 – Puppy 2011-05-02 23:54:16

+0

在函數堆棧的開始處是不是變量(以及之後分配的內存)?至少對於在alloca調用之前聲明的變量? – aberaud 2016-10-25 20:28:08