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

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

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

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

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

Изменить:

Хорошо, есть ограничения, для окон это ограничение по времени ссылки. Для 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

person Hassan Syed    schedule 27.04.2011    source источник
comment
Кажется, тест не подходит для лязга при включенной оптимизации. Внутренние вызовы alloca оптимизированы (удалены), так как нет побочного эффекта (на уровне IR-кода llvm) или использования их результатов.   -  person osgx    schedule 18.05.2011
comment
Я работал с парнями (EE), которые раньше создавали оборудование, используемое во встроенных системах массового потребления, таких как шлюзы кабельной компании. Они выделили буфер фиксированного размера, а затем использовали его повторно. Ни разу не заходил в диспетчер памяти.   -  person jww    schedule 27.07.2015


Ответы (5)


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

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

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

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

person Chris Eberle    schedule 27.04.2011
comment
Интересно, знаете ли вы, есть ли в Unix какой-то контролируемый сигнал выхода для переполнения стека? - person Hassan Syed; 27.04.2011
comment
Быстрый man 7 signal ничего не показывает. Насколько мне известно, очень плохое переполнение стека приведет к SIGSEGV. IIRC, вы можете поймать SIGSEGV, но есть реальный вопрос: можете ли вы сделать что-нибудь полезное? Ваш стек уже является швейцарским сыром, выполнение любых действий приведет к увеличению использования стека, и кто даже знает, что произойдет. - person Chris Eberle; 27.04.2011

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

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

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

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

person DXM    schedule 27.04.2011
comment
поэтому alloca () блок памяти в стеке намного быстрее, чем в куче. Но как насчет доступа к памяти, выделенной функцией alloca (), по сравнению с malloc ()? Он также обычно быстрее записывается / читается из-за локальности памяти? Спасибо! - person dragonxlwang; 05.08.2015
comment
возможно, но следует иметь в виду, что современный ЦП настолько сложен, когда дело доходит до кешей L1-3 и всех тех операций предварительной выборки и сумасшедшего выполнения, которые они делают, что пытается построить ментальную модель, которая объяснила бы локальность и скорость данных. Прибыль практически невозможна. В 9999/10000 раз вы не заметите прироста производительности и не позаботитесь о нем. В очень немногих случаях, когда вы оптимизируете очень жесткий цикл в очень важном фрагменте кода, лучше всего поэкспериментировать и посмотреть, какое изменение дает прирост производительности. В противном случае память - это память, и скорость доступа, вероятно, будет такой же - person DXM; 10.08.2015

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

person Puppy    schedule 28.04.2011
comment
Вы уверены, что все работает именно так, то, что вы предлагаете, подразумевает, что либо компилятор имеет статические знания о alloca, либо это механизм времени выполнения, который делает то, что вы говорите. Например, механизм времени выполнения для создания таблицы смещений при обнаружении использования alloca? - person Hassan Syed; 03.05.2011
comment
@Hassan Syed: alloca - это не настоящая функция. Компилятор должен относиться к этому особо. Это сделано статически. - person Puppy; 03.05.2011
comment
Разве переменные не находятся в начале стека функций (а после этого выделяется память)? По крайней мере, для переменных, объявленных до вызова alloca? - person aberaud; 25.10.2016

Один момент, который не был замечен, это то, что стек часто является смежным, в то время как куча нет. В целом неверно утверждать, что в стеке с такой же вероятностью не хватит памяти, как и в куче.

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

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

person Steve Townsend    schedule 27.04.2011

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

person Community    schedule 27.04.2011
comment
Вы правы, я забыл подробности, но все же речь идет о значительном ограничении. Я могу предусмотреть некоторые приложения, превышающие 1 МБ. но я ожидаю, что общий зарезервированный предел виртуального адреса будет составлять минимум 32–128 МБ (в 32-разрядных системах). Думаю, мне нужно кое-что изучить. - person Hassan Syed; 27.04.2011