Подтвердить что ты не робот

Об использовании и злоупотреблении alloca

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

Я всегда хотел использовать alloca в предыдущем коде аналогичного характера. Однако, когда вы смотрите на литературу по системному программированию, всегда есть огромные предостережения против этого вызова функции. Лично я не могу думать о машине класса сервера за последние 15 лет, у которой нет виртуальной памяти, и я знаю, что в стеке стека растет страница виртуальной памяти по-разному, поэтому я предполагаю Унификации тоже. Здесь нет кирпичной стены (больше), стек так же быстро исчерпывает пространство, как куча, и что дает? Почему люди не собираются разгадать алоку? Я могу думать о многих случаях использования ответственного использования 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;
}

выход:

[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

- Изменить: результаты на домашней машине, 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
4b9b3361

Ответ 1

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

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

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

В моей коробке разработки на работе (Gentoo) у меня есть размер стека по умолчанию, равный 8192 кб. Это не очень большое, и если alloca переполняет стек, то поведение undefined.

Ответ 2

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

Так, как указывали другие, это не "виртуальная память", о которой вам нужно беспокоиться, а размер, зарезервированный для стека. Хотя другие ограничивают себя "несколькими сотнями байтов", если вы знаете свое приложение и заботитесь об этом, мы выделили до 256 килобайт без каких-либо проблем (размер стека по умолчанию, по крайней мере для визуальной студии, равен 1 мб, и вы всегда можете увеличьте его, если вам нужно).

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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