[c++] растет стек от временных объектов
Как это объяснить?Компилятор не достаточно оптимизирующий, и под каждую неявную переменную выделяет новое место на стеке, хотя мог бы использовать место от уже неиспользуемой предыдущей переменной.
А зачем так делать?
Мне это ломает тесты
В тестах на каждый кейс в вектор пихается большой объект с тестовыми данными. Его размер на стеке где-то 40 Кб. Можно было бы читать из файла, но я задаю в коде подобным образом, как тут написано. Соответственно, через 200 тест-кейсов кончится стек - но до этого я еще не дошел, я дошел до того, что через 50 тест-кейсов валгринд ругается на большой скачок stack pointer'а - больше 2 Мб.
Как-то это можно обойти, я придумаю, но я два дня потратил на расковыривание того, что происходит, и до сих пор у меня нет объяснения такому нелепому расходованию памяти.
Чем тебе new/delete не угодили?
size 2 capacity 2
S 0x7fffe214b3be
S(S&&) 0x6dbc22 0x7fffe214b3be
S(const S&) 0x6dbc20 0x6dbc40
S(const S&) 0x6dbc21 0x6dbc41
~S 0x6dbc40
~S 0x6dbc41
~S 0x7fffe214b3be
size 3 capacity 4
Объясни, зачем создавать объекты в стеке.Затем что аллокация на стеке намного быстрее?
Чем тебе new/delete не угодили?
size 0 capacity 4
S 0015FCB7
S(S&&) 002A9408 0015FCB7
~S 0015FCB7
size 1 capacity 4
S 0015FCB7
S(S&&) 002A9409 0015FCB7
~S 0015FCB7
size 2 capacity 4
S 0015FCB7
S(S&&) 002A940A 0015FCB7
~S 0015FCB7
size 3 capacity 4
S 0015FCB7
S(S&&) 002A940B 0015FCB7
~S 0015FCB7
size 4 capacity 4
Возможно, я что-то путаю, но при выходе из функции созданные внитури ее локальные перемнные из стека уничтожаются. Если функция возвращает объект, созданный в стеке, то его таки придется куда-то из стека скопировать, так что память где-то кроме стека все равно выделять. Так зачем это промежуточный стек? Или я что-то недопонимаю? Я как бы давно уже отстал от жизни компиляторов и языков, прошу пояснить.
Если функция возвращает объект, созданный в стеке, то его таки придется куда-то из стека скопировать, так что память где-то кроме стека все равно выделять. Так зачем это промежуточный стек?сохраняет созданные элементы в векторе, который аллоцирует папять в хипе непрерывным куском.
Так что если в векторе уже есть память под новый элемент (скорее всего то в случае создания переменной на стеке мы вообще не делаем хип аллокаций (просто копируем объект в уже выделенную память если нет - реаллоцируем весь вектор. В твоем варианте в обоих случаях есть лишняя аллокация под сам объект, которой можно избежать.
Пока что я вижу стрельбу себе в ногу.
Проще сделать вектор ссылок, создавать отдельно в куче и вставлять ссылки в вектор.Про это уже писали выше. Создание на стеке заметно быстрее.
Можно как-то показать,что заметно быстрее?
size 0 capacity 4
S 0056A4B8
size 1 capacity 4
S 0056A4B9
size 2 capacity 4
S 0056A4BA
size 3 capacity 4
S 0056A4BB
size 4 capacity 4
А ты обратил внимание на количество конструкторов-деструкторов при каждом следующем вызове?Разумеется. Это элементарно объясняется тем, что вектору нужно переаллоцировать память. По адресам видно, что деструкторы вызываются как у объектов в куче - тех, которые уже в векторе, так и у объекта на стеке, который добавился в вектор последним. Происходит это когда size = capacity и, по-моему не имеет отношения к делу. Пример можно упростить, избавившись от векторов, до такого:
#include <iostream>
using namespace std;
struct S
{
S { cout << "S " << this << endl; }
~S { cout << "~S " << this << endl; }
S(const S& r) { cout << "S(const S&) " << this << " " << &r << endl; }
S(S&& r) { cout << "S(S&&) " << this << " " << &r << endl; }
S& operator= (const S& r) { cout << "S& operator=(const S&) " << this << " " << &r << endl; return *this; }
S& operator= (S&& r) { cout << "S& operator=(S&&) " << this << " " << &r << endl; return *this; }
};
int main
{
S;
S;
S;
S;
return 0;
}
Результат:
S 0x7fff5df38a88
~S 0x7fff5df38a88
S 0x7fff5df38a80
~S 0x7fff5df38a80
S 0x7fff5df38a78
~S 0x7fff5df38a78
S 0x7fff5df38a70
~S 0x7fff5df38a70
Да, на всякий случай:Как ты добился такого эффекта?
Ну и как бы в реальных задачах у меня было бы так:Чем реальные задачи отличаются от модельного примера?
int main
{
S;
S;
S;
S;
return 0;
}
S 0028F92B
~S 0028F92B
S 0028F92B
~S 0028F92B
S 0028F92B
~S 0028F92B
S 0028F92B
~S 0028F92B
Проверь, включена ли оптимизация компилятора.
Как ты добился такого эффекта?Избавился от реаллокации вектора.
Чем реальные задачи отличаются от модельного примера?Возможностью использовать emplace =)
Проверь, включена ли оптимизация компилятора.Собирал вызовом "clang++ sp.cpp -std=c++14 -O3".
> clang++ --version
Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
Target: x86_64-apple-darwin14.3.0
Thread model: posix
Проще сделать вектор ссылок, создавать отдельно в куче и вставлять ссылки в вектор.Сами объекты при этом будут разбросаны по памяти, что при многократном доступе к вектору увеличит промахи по кэшу и уронит производительность.
до сих пор у меня нет объяснения такому нелепому расходованию памятисклоняюсь к тому, что это баг, а твои ожидания вполне разумны.
временный объект живет до конца выражения, затем должен быть вызван деструктор и освобождена занимаемая память. возможность реюза памяти в твоих примерах довольно очевидна, однако я сомневаюсь, что это (обязательный реюз и нераздувание памяти для временных переменных) зафиксировано в стандарте языка. так что текущее поведение компиляторов вполне соответствует официальному стандарту
стратегия аллоцирования объектов на стеке может быть сложнее и приводить к фрагментации (дыркам) имхо.
еще можно почитать доку к -fstack-reuse : http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html , которая подтверждает наличие нескольких стратегий у компилятора
Можно как-то показать,что заметно быстрее?можно почитать в инетах:
http://stackoverflow.com/questions/5807612/on-the-use-and-ab...
http://stackoverflow.com/questions/714692/alloca-implementat...
Сами объекты при этом будут разбросаны по памяти, что при многократном доступе к вектору увеличит промахи по кэшу и уронит производительность.Я тоже это сначала написал, но потом прочитал, что у автора объекты намного больше кэш-линии, так что этим эффектом вроде можно пренебречь.
Это баг. http://llvm.org/bugs/show_bug.cgi?id=5629За 6 лет не починят такую важную проблему?
Ну, да, если программа занимается исключительно вставкой в вектор, то выигрышь будет очень заментым на больших объемах. Стоит ли оно того, учитывая первый пост и существенную вероятность, что программа занимается в основном другими операциями, далеко не так очевидно. Впрочем, если удастся победить, этот опыт будет крайне полезен всем.
у автора объекты намного больше кэш-линииДаже больше чем кэш второго и третьего уровня?
Оставить комментарий
erotic
Что я ожидаю увидеть - это то, что каждый временный объект типа S создается на стеке по одному и тому же адресу и впоследствии уничтожается. Это и наблюдается с gcc-4.9.2 (адрес напротив S:
А если использовать clang-3.6.0:
Т.е. стек постоянно растет от временных объектов. Все еще хуже, если я раскомментирую деструктор класса S - тогда и в g++ начинается подобное поведение:
Вероятно, это не бага, а просто я чего-то не понимаю, но чего?
Если засунуть наполнение вектора в цикл, а не делать это отдельными вызовами, то в обоих компиляторах временная переменная создается по одному и тому же адресу, что наводит на мысли о том, что временный объект хоть и уничтожается в конце полного выражения, как и положенно временному объекту, но память из-под него становится возможным использовать только после выхода из блока. Однако если в изначальном варианте каждый push_back поместить в отдельный блок, то все равно стек растет.
Как это объяснить?