На использование и злоупотребление alloca - PullRequest
20 голосов
/ 27 апреля 2011

Я работаю над системой обработки событий в реальном времени. Я хотел бы свести к минимуму столько вызовов в моем коде, которые имеют недетерминированную синхронизацию. Мне нужно создать сообщение, которое состоит из строк, чисел, меток времени и GUID. Вероятно, std::vector из boost::variant.

Я всегда хотел использовать alloca в прошлом коде аналогичного характера. Однако, когда кто-то изучает системное программирование, всегда есть серьезные предостережения против этого вызова функции. Лично я не могу думать о машине серверного класса за последние 15 лет, у которой нет виртуальной памяти, и я точно знаю, что стек Windows увеличивает виртуальную память на странице за раз, поэтому я предполагаю, Юниты тоже так делают. Здесь больше нет кирпичной стены, стек, скорее всего, исчерпает пространство так же, как и куча, так что дает? Почему люди не выходят из-под алока? Я могу вспомнить множество вариантов использования ответственного использования alloca (кто-нибудь обрабатывает строки?).

Во всяком случае, я решил проверить разницу в производительности (см. Ниже), и есть 5-кратная разница в скорости между alloca и malloc (тест фиксирует, как я буду использовать alloca). Итак, все изменилось? Должны ли мы просто проявить осторожность и использовать alloca (завернутый в std::allocator) всякий раз, когда мы можем быть абсолютно уверены в сроке службы наших объектов?

Я устал жить в страхе!

Edit:

Хорошо, существуют ограничения, для окон это ограничение времени соединения. Для 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;
}

выход:

hassan@hassan-desktop:~/test$ ./a.out 
alloca_time: 00:00:00.056302
malloc_time: 00:00:00.260059
hassan@hassan-desktop:~/test$ ./a.out 
alloca_time: 00:00:00.056229
malloc_time: 00:00:00.256374
hassan@hassan-desktop:~/test$ ./a.out 
alloca_time: 00:00:00.056119
malloc_time: 00:00:00.265731

- Изменить: Результаты на домашнем компьютере, Clang и Google 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

Ответы [ 5 ]

15 голосов
/ 27 апреля 2011

Ну, во-первых, даже если виртуальной памяти много, это не значит, что вашему процессу будет разрешено ее заполнять.В * nix есть ограничения на размер стека, тогда как куча намного более простительна.

Если вы собираетесь выделять всего несколько сотен / тысяч байт, обязательно продолжайте.Все, что выходит за рамки этого, будет зависеть от того, какие ограничения (ulimit) установлены в любой конкретной системе, и это всего лишь рецепт катастрофы.

Почему использование alloca () не считается хорошей практикой?

На моем компьютере для разработки на работе (Gentoo) ограничение размера стека по умолчанию составляет 8192 КБ.Это не очень много, и если alloca переполняет стек, то поведение не определено.

6 голосов
/ 27 апреля 2011

Я думаю, вам нужно быть немного осторожнее, чтобы понять, что на самом деле представляет собой alloca.В отличие от malloc, который попадает в кучу, просматривает сегменты и связанные списки различных буферов, alloca просто берет ваш регистр стека (ESP на x86) и перемещает его, чтобы создать «дыру» в стеке вашего потока, где вы можете хранить все, что захотите.Вот почему это очень быстро, всего одна (или несколько) инструкция по сборке.

Так что, как отмечали другие, вам нужно беспокоиться не о «виртуальной памяти», а о размере, зарезервированном для стека.Хотя другие ограничивают себя «несколькими сотнями байтов», при условии, что вы знаете свое приложение и внимательно относитесь к нему, мы выделили до 256 КБ без каких-либо проблем (размер стека по умолчанию, по крайней мере для visual studio, составляет 1 МБ, и вы всегда можетеувеличьте его, если вам нужно).

Кроме того, вы действительно не можете использовать alloca в качестве распределителя общего назначения (то есть, оборачивая его в другую функцию), потому что независимо от того, какая память выделяется для вас, эта память исчезнет, ​​когдакадр стека для текущей функции выталкивается (т. е. при выходе из функции).

Я также видел, как некоторые люди говорят, что alloca не полностью кроссплатформенно совместим, но если вы пишете конкретное приложение для конкретной платформыи у вас есть возможность использовать alloca, иногда это лучший вариант, если вы понимаете последствия увеличения использования стека.

4 голосов
/ 28 апреля 2011

Во-первых, это потому, что alloca памятью очень сложно управлять.Он не типизирован, умирает при первой же возможности, что делает его не очень полезным.Кроме того, alloca имеет некоторые неприятные побочные эффекты, и эти побочные эффекты заключаются в том, что обычные переменные стека теперь должны динамически индексироваться вместо констант, что может повлиять на вашу производительность даже при доступе к ним базовых операций и потребляет пространство регистра / стека для хранениядинамические смещения.Это означает, что реальная стоимость использования alloca записывается не только за время, необходимое для возврата функции.Кроме того, стековая память очень ограничена по сравнению с кучей памяти - в Windows ограничение стека по умолчанию составляет 8 МБ, тогда как куча может составлять почти все адресное пространство пользователя.Более того, в конечном итоге любые данные, которые вы хотите вернуть, должны быть в куче, так что вы можете просто использовать их в качестве рабочего пространства.

3 голосов
/ 27 апреля 2011

Одна вещь, которая не была сделана afai, состоит в том, что стек часто является непрерывным , а куча - нет.В целом неверно утверждать, что стеку, скорее всего, не хватит памяти, так же как и куче.

В C ++ очень часто встречаются экземпляры объектов, объявленные как локальные, что в некотором роде напоминает alloca но структурированной памяти, а не блока из N байтов - возможно, вы можете думать об этом как о почтении к вашему главному пункту, который заключается в том, что более широкое использование стековой памяти является хорошей идеей.Я бы скорее сделал это (объявил экземпляр объекта как локальный RAII), чем использовал бы malloc (или alloca) в программе на C ++.Все эти free вызовы, чтобы сделать исключение безопасным ...

Это обычно предполагает, что область действия объекта ограничена этой функцией и ее вызываемыми функциями.Если это не так, тогда использование стековой памяти обычно не очень хорошая идея.

2 голосов
/ 27 апреля 2011

Стек Windows не увеличивается - его зарезервированный размер задается во время ссылки, но страницы в этом размере будут фиксироваться только по мере необходимости. См. http://msdn.microsoft.com/en-us/library/ms686774%28v=vs.85%29.asp. Поскольку зарезервированный размер по умолчанию составляет 1 МБ, его можно легко превысить, если использовать alloca().

.
...