Здравствуйте, WolfHound, Вы писали:
C>>Между процессами или отображаемыми в память файлами. WH>Детали.
Ну ты базу данных используешь? Так ведь она (О ужас!) тоже распределяется между процессами.
C>>Одно слово: кэши. WH>Какие и сколько данных в кеше?
32 мегабайта Это встроеное устройство.
C>>Я с его помощью сделал почти прозрачное транзакционное disk-backed кэширование (операционная система еще и предоставляет кэширование в памяти). WH>Транзакционное? Что-то както сомневаюсь. WH>В самом Boost.Interprocess нет упоминаний о транзакциях.
Там есть упоминания о мьютексах.
WH>А самому их делать
Совсем несложно — я и в обычном стиле почти так же пишу.
C>>По скорости ничего даже близко не приближается. WH>Тоже не факт.
Пробовал SQLite, MySQL и db4o. У меня быстрее всего работает, что совсем неудивительно.
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, eao197, Вы писали:
E>>Я считал, что проблема фрагментации памяти для new/delete (malloc/free) заключается в другом. В том, что адресное пространство процесса постоянно растет. Что приводит к частым попаданиям мимо кэша или слишком частым вытеснениям занятых процессом страниц памяти в своп. GZ>Та программа была под Dos и выполнялась на 286/386 машинах. Так что не было ни кэша, ни свопа.
Да уж, представляю себе САПР под Dos-ом на 286 на менеджед языке со сборкой мусора
С тех пор не только железо лучше стало, но и алгоритмы malloc-а (dlmalloc, к примеру).
E>>Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются. GZ>Когда работал на С++, для серверов изначально не использовал STL по данной причине. Сейчас уже не знаю, по дурости или без. Поэтому сказать ничего не могу.
Я использую, но вот с фрагментацией не сталкивался.
E>>А то, о чем ты написал -- это просто неподходящий алгоритм выделения памяти для конкретной задачи. Непосредственно к проблеме явного управления памяти он, имхо, не имеет отношения. GZ>Отчего же. GC — позволяет дефрагментировать память, и обходиться без прохода по спискам выделенных/пустых областей. То — что не умеет malloc. Так что замечание было в тему.
Линейный список блоков в аллокаторе -- это уже история. Я надеюсь
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Cyberax, Вы писали:
C>Ну ты базу данных используешь? Так ведь она (О ужас!) тоже распределяется между процессами.
Это ты вобще к чему?
C>32 мегабайта Это встроеное устройство.
Лично я кеши десятками гигабайт меряю...
Кстати, а зачем на встроеном устройстве кешь. Да еще и расшаренный между процессами.
C>Там есть упоминания о мьютексах.
И? С каких это пор мьютексы обеспечивают транзакции?
C>Совсем несложно — я и в обычном стиле почти так же пишу.
Я какбы писал код гарантирующий ACID.
C>Пробовал SQLite, MySQL и db4o. У меня быстрее всего работает, что совсем неудивительно.
Правильно. Там есть транзакции, а у тебя их нет.
По крайней мере ты так и не сказал откуда они взялись.
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
C>>Ну ты базу данных используешь? Так ведь она (О ужас!) тоже распределяется между процессами. WH>Это ты вобще к чему?
Это про shared-данные.
C>>32 мегабайта Это встроеное устройство. WH>Лично я кеши десятками гигабайт меряю...
Это неинтересно. Туда можно и RAID-массив воткнуть и пару гигабайт памяти.
WH>Кстати, а зачем на встроеном устройстве кешь. Да еще и расшаренный между процессами.
Там есть несколько процессов, собирающих данные с разных устройств, процесс-обработчик и закачивальщик результата на центральный сервер.
И это все асинхронно работает.
C>>Там есть упоминания о мьютексах. WH>И? С каких это пор мьютексы обеспечивают транзакции?
Это у меня основной инструмент для их обеспечения.
C>>Совсем несложно — я и в обычном стиле почти так же пишу. WH>Я какбы писал код гарантирующий ACID.
А я писал ACID? У меня, на самом деле, ACI (без D — его дейстивительно сложно делать).
C>>Пробовал SQLite, MySQL и db4o. У меня быстрее всего работает, что совсем неудивительно. WH>Правильно. Там есть транзакции, а у тебя их нет.
Есть, с оптимистическим версированием. При коммите блокирую нужные объекты (мьютексами), проверяю версии и заливаю обновления.
Здравствуйте, Cyberax, Вы писали:
C>Это неинтересно. Туда можно и RAID-массив воткнуть и пару гигабайт памяти.
Восемь.
C>Там есть несколько процессов, собирающих данные с разных устройств, процесс-обработчик и закачивальщик результата на центральный сервер. C>И это все асинхронно работает.
По описанию идеально ложится на объмен сообщениями вобще и ОСь типа сингулярити в частности.
C>Это у меня основной инструмент для их обеспечения.
Ну то что без блокировок транзакции не сделать это и так ясно.
C>Есть, с оптимистическим версированием. При коммите блокирую нужные объекты (мьютексами), проверяю версии и заливаю обновления.
Те что-то типа STM?
... << RSDN@Home 1.2.0 alpha rev. 673>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, GlebZ, Вы писали:
E>>А то, о чем ты написал -- это просто неподходящий алгоритм выделения памяти для конкретной задачи. Непосредственно к проблеме явного управления памяти он, имхо, не имеет отношения. GZ>Отчего же. GC — позволяет дефрагментировать память, и обходиться без прохода по спискам выделенных/пустых областей. То — что не умеет malloc. Так что замечание было в тему. ;)
Нет, не в тему. Алгоритмы распределения памяти с линейными списками благополучно начали дохнуть ещё в конце 80-х и окончательно сдохли к середине 90-х по причине того, что проход по спискам безнадёжно замусоривал кэши процессоров. Все новые алгоритмы в этот период писались уже с построением деревьев занятости и структурами для оптимизации поиска по желаемому размеру, а для мелких объектов — пулов фиксированного размера с внешними таблицами занятости (как правило, битовыми массивами). Сейчас же имеем ещё дополнительно 15 лет вылизывания этих алгоритмов.
Так что говорить, что malloc "не умеет" "обходиться без прохода по спискам выделенных/пустых областей" означает безнадёжно отставать от современного опыта, пользуясь данными не просто прошлого тысячелетия, а как минимум раннего допотопья.:) Признайтесь — Вы небось ни одну современную реализацию malloc не смотрели, а про списки областей вычитали где-нибудь в книгах написанных в лучшем случае в начале 80-х, а задуманных в конце 70-х? У Вирта? ;)))
E>>Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются. GZ>Когда работал на С++, для серверов изначально не использовал STL по данной причине. Сейчас уже не знаю, по дурости или без. :-\ Поэтому сказать ничего не могу.
Видимо, таки по ней, родимой:) — иначе сложно назвать ориентирование на данные двадцатилетней давности.
Здравствуйте, eao197, Вы писали:
E>Я считал, что проблема фрагментации памяти для new/delete (malloc/free) заключается в другом. В том, что адресное пространство процесса постоянно растет. Что приводит к частым попаданиям мимо кэша или слишком частым вытеснениям занятых процессом страниц памяти в своп. Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются.
Что-то не замечал подобного эффекта и плохо себе представляю, как его можно получить. Даже если реализация способна только забирать память у системы и не возвращать, а нагрузка постоянна (или колеблется с периодом значительно меньше набранного времени жизни процесса), то потребление памяти должно стабилизироваться на уровне немного большем, чем необходимо для держания всех данных. "Немного" зависит от разнообразия размера объектов и может быть хоть 1.5, но оно постоянно для шаблона потребления. Если же объём памяти постоянно растёт — это скорее признак какой-то утечки, нежели метода работы с памятью. Наблюдение за долгоживущими нагруженными серверами (из моих подопечных это innd, squid, ircd, netmond, sendmail, exim и так далее) подтверждает эту теорию.
О фрагментации имеет смысл говорить, если был пик нагрузки значительно выше обычного уровня (при котором действительно были проблемы кэша и свопа) и затем она упала до обычного уровня. В этом случае вполне вероятно иметь множество слабозаполненных страниц, в тяжёлом случае по 1-2 живых объекта на страницу. Но в любом случае такое растёт из прошлого всплеска.
Ряд программ реализует защиту от подобного использованием своего менеджера памяти, но не ради оптимизации по размеру или чему-то подобному, а ради возможности потом одной порцией устранить пул памяти, использованный, например, под обработку одного запроса:) Такие реализации лишены большей части проблем с недоиспользованием. Оборотная сторона — необходимость полностью контролировать выделение памяти в коде с использованием нужного менеджера — приводит к почти полной потере возможности использовать стандартные библиотеки. Шаблонные классы C++ с заданием аллокатора порождены именно подобной спецификой.
E>А то, о чем ты написал -- это просто неподходящий алгоритм выделения памяти для конкретной задачи. Непосредственно к проблеме явного управления памяти он, имхо, не имеет отношения.
Здравствуйте, eao197, Вы писали:
E>Справедливости ради нужно сказать, что в C++, к счастью, есть возможность создавать объекты на стеке и передавать их по значению. Что часто очень серьезно снижает количество операций new/delete.
В Java SE 6 реализован т.н. Escape-анализ: вумный JIT может принять решение о выделении памяти под объекты на стеке, нежели массиве — так что стек (неявно, конечно) доступен и в GC-средах.
Здравствуйте, netch80, Вы писали:
E>>Я считал, что проблема фрагментации памяти для new/delete (malloc/free) заключается в другом. В том, что адресное пространство процесса постоянно растет. Что приводит к частым попаданиям мимо кэша или слишком частым вытеснениям занятых процессом страниц памяти в своп. Что особо чувствительно для 24x7 процессов, которые месяцами не выгружаются и не останавливаются.
N>Что-то не замечал подобного эффекта и плохо себе представляю, как его можно получить. Даже если реализация способна только забирать память у системы и не возвращать, а нагрузка постоянна (или колеблется с периодом значительно меньше набранного времени жизни процесса), то потребление памяти должно стабилизироваться на уровне немного большем, чем необходимо для держания всех данных. "Немного" зависит от разнообразия размера объектов и может быть хоть 1.5, но оно постоянно для шаблона потребления. Если же объём памяти постоянно растёт — это скорее признак какой-то утечки, нежели метода работы с памятью. Наблюдение за долгоживущими нагруженными серверами (из моих подопечных это innd, squid, ircd, netmond, sendmail, exim и так далее) подтверждает эту теорию.
N>О фрагментации имеет смысл говорить, если был пик нагрузки значительно выше обычного уровня (при котором действительно были проблемы кэша и свопа) и затем она упала до обычного уровня. В этом случае вполне вероятно иметь множество слабозаполненных страниц, в тяжёлом случае по 1-2 живых объекта на страницу. Но в любом случае такое растёт из прошлого всплеска.
Об этом я и говорил. Наверноее выразился неудачно.
Тем более, что на практике с фрагментацией памяти я пока не встречался.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, rsn81, Вы писали:
R>Здравствуйте, eao197, Вы писали:
E>>Справедливости ради нужно сказать, что в C++, к счастью, есть возможность создавать объекты на стеке и передавать их по значению. Что часто очень серьезно снижает количество операций new/delete. R>В Java SE 6 реализован т.н. Escape-анализ: вумный JIT может принять решение о выделении памяти под объекты на стеке, нежели массиве — так что стек (неявно, конечно) доступен и в GC-средах.
Речь шла не о том, что в управляемых средах нет стека, а о том, что в C++ стековые объекты и объекты по значению значительно уменьшают использование new/delete, что приводит к уменьшению проблем с явным управлением памятью.
А в Java, что есть стек, что его нет, как создавались все объекты через new, так и создаются.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>при использовании GC выделение памяти обходится (по слухам) дешевле C-шных malloc-ов, а освобождается затем всем скопом,
В C++ тоже можно создать много структур, а потом освободить их всех одним махом. При этом вполне можно обойтись без хитрых алгоритмов сборки мусора. Правда есть одна тонкость: каждая структура должна иметь тривиальный деструктор, чтобы его можно было не вызывать.
Вот несколько примеров.
Глава 10. Управление памятью
… 10.4. Размещение объекта в памяти
…
Для конкретной арены operator new() можно определить так:
void* operator new(size_t s, fast_arena& a)
{
return a.alloc(s);
}
а использовать следующим образом:
void f(fast_arena& arena)
{
X* p = new(arena) X; // распределить X на арене
// ...
}
Здесь предполагается, что fast_arena — это класс, который имеет функцию-член alloc(), применяющуюся для выделения памяти. Например:
class fast_arena {
// ...char* maxp;
char* freep;
char* expand(size_t s); // получить дополнительную память от
// распределителя общего назначенияpublic:
void* alloc(size_t s) {
char* p = freep;
return ((freep += s) < maxp) ? p : expand(s);
}
void free(void*) { } // ничего не делает
clear(); // освободить всю выделенную память
};
Такая арена специально предназначена для быстрого выделения памяти и почти мгновенного её освобождения. Важной областью применения арен является предоставление специальной семантики управления памятью.
10.5. Проблемы освобождения памяти
…
Идеальный вариант — тот, при котором пользователю вообще не нужно освобождать память, занятую объектом. Для этого, в частности, применяются специальные арены. Арену можно определить так, что в некоторой известной точке программы будет освобождаться вся память, занятая этой программой. Можно также написать для неё сборщик мусора. Первый подход очень распространён, второй — нет. Специальный сборщик мусора должен быть написан удачно, иначе проще встроить в программу стандартный [Boehm, 1993].
2. JPEG lib, файл jpeg-6b\structure.doc:
*** Memory manager services ***
…
In all cases, allocated objects are tied to a particular compression or decompression master record, and they will be released when that master record is destroyed.
The memory manager does not provide explicit deallocation of objects. Instead, objects are created in "pools" of free storage, and a whole pool can be freed at once. This approach helps prevent storage-leak bugs, and it speeds up operations whenever malloc/free are slow (as they often are). The pools can be regarded as lifetime identifiers for objects. Two pools/lifetimes are defined:
* JPOOL_PERMANENT lasts until master record is destroyed
* JPOOL_IMAGE lasts until done with image (JPEG datastream)
Когда-то, во времена зарождения языка C, его распределитель памяти был самым слабым из существующих. Это был алгоритм «первый попавшийся», то есть он работал следующим образом: распределитель просматривал все узлы в списке блоков памяти, и первый же попавшийся свободный блок, который был не меньше нужного размера, разбивался на две части — одна возвращалась по запросу, вторая (общий размер блока минус запрошенный размер) возвращалась в список свободных узлов. «Преимущества» этого очевидны — очень низкая скорость работы и дикая фрагментация памяти. В действительности это хуже, чем вы можете себе представить. При выделении памяти приходилось пробегать весь список блоков, игнорируя уже выделенные. Поэтому при увеличении числа блоков производительность падала, а блоки становились всё меньше и были непригодны к использованию. Они отнимали время без всякой реальной пользы.
…
Я сходил в свой кабинет, где лежала копия книги IDL, принёс её с собой назад, и открыл главу «Распределение памяти».
…
«Я написал эту главу, в которой рассказывается о разработке высокопроизводительного, минимально фрагментирующего распределителя памяти.»
…
Распределитель памяти в NT работает весьма схожим образом с тем, что я описал в книге IDL, и основан на алгоритме «быстрого совпадения», разработанном Чаком Вейнстоком для его докторской в CMU около 1974 года.
3. Распределитель памяти в Quake 2. Файл quake2-3.21\qcommon\common.c:
// memory tags to allow dynamic memory to be cleaned up#define TAG_GAME 765 // clear when unloading the dll#define TAG_LEVEL 766 // clear when loading a new level
Здравствуйте, Пётр Седов, Вы писали:
ПС>Здравствуйте, eao197, Вы писали:
E>>при использовании GC выделение памяти обходится (по слухам) дешевле C-шных malloc-ов, а освобождается затем всем скопом, ПС>В C++ тоже можно создать много структур, а потом освободить их всех одним махом. При этом вполне можно обойтись без хитрых алгоритмов сборки мусора. Правда есть одна тонкость: каждая структура должна иметь тривиальный деструктор, чтобы его можно было не вызывать. ПС>Вот несколько примеров.
<...примеры поскипаны...>
Все это так, но это все меняет код программы или требует каких-то специальных условий. В то время как GC оставляет текст программы в первоначальном виде (как были new, так и остались, вне зависимости от расположения объектов в хипе или на стеке).
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
<все skipped после чтения данного сообщения и ответов>
У меня на все это одна мысль возникла. А почему бы авторам некоей системы программирования не совместить GC и не-GC в одной коробке ? То есть ввести в систему GC и его управление памятью, но в то же оставить и возможность выделять/освобождать память вручную. 2 кучи — одна управляемая a la C#-Java, вторая — неуправляемая a la C++. И были бы и волки сыты и овцы целы. Выбирай, что тебе больше нравится и что больше соотвествует твоей задаче. А может быть, даже и в рамках одной задачи выбирать и то и другое для разных объектов.
Кстати, то же относится к деструктированию объектов. Почему бы не сделать так, чтобы программист мог либо поручить это GC, либо сказать — делай детерминированно сейчас ?
Никаких принципиальных причин, почему это невозможно, я не вижу (если кто видит — скажите). Конечно, в нынешние языки и системы это не лезет. Может, пора новый язык/систему создать с такими возможностями.
Здравствуйте, GlebZ, Вы писали:
GZ>Было много мелких объектов, алгоритм должен был быть очень быстрым. При выделении памяти — стандартный malloc проходил по всей цепочке пытаясь найти место, в результате проседала производительность. Пришлось переопределять new, и делать свой менеджер памяти.
Если алгоритм создаёт много мелких структур и по окончании алгоритма эти структуры становятся не нужны, то лучше использовать не heap (malloc/free или глобальные new/delete), а арену — это такой распределитель памяти вроде stack-а. Вообще, мимолётные временные структуры лучше размещать в арене. Тогда и фрагментация heap-а меньше будет.
GZ>Примерно такая-же ситуация была с VirtualAlloc(работает по тому же принципу).
Переопределить VirtualAlloc не получится .
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, remark, Вы писали:
PD><все skipped после чтения данного сообщения и ответов>
PD>У меня на все это одна мысль возникла. А почему бы авторам некоей системы программирования не совместить GC и не-GC в одной коробке ? То есть ввести в систему GC и его управление памятью, но в то же оставить и возможность выделять/освобождать память вручную. 2 кучи — одна управляемая a la C#-Java, вторая — неуправляемая a la C++. И были бы и волки сыты и овцы целы. Выбирай, что тебе больше нравится и что больше соотвествует твоей задаче. А может быть, даже и в рамках одной задачи выбирать и то и другое для разных объектов.
Ну вот вопрос с ходу. Как отслеживать ссылки из не-GC пула в GC пул?
PD>Кстати, то же относится к деструктированию объектов. Почему бы не сделать так, чтобы программист мог либо поручить это GC, либо сказать — делай детерминированно сейчас ?
А смысл? Проще ввести счётчик ссылок и удалять объект сразу как только у него счётчик упадёт до нуля. GC после этого остаётся только для случаев циклической зависимости, и то — программист может помочь этому разорвав циклы в нужных местах.
Так работает, кстати, python — на долю GC остаются только сложные циклические конструкции.
PD>Никаких принципиальных причин, почему это невозможно, я не вижу (если кто видит — скажите). Конечно, в нынешние языки и системы это не лезет. Может, пора новый язык/систему создать с такими возможностями. :-)
Здравствуйте, Pavel Dvorkin, Вы писали: PD>У меня на все это одна мысль возникла. А почему бы авторам некоей системы программирования не совместить GC и не-GC в одной коробке ? То есть ввести в систему GC и его управление памятью, но в то же оставить и возможность выделять/освобождать память вручную. 2 кучи — одна управляемая a la C#-Java, вторая — неуправляемая a la C++. И были бы и волки сыты и овцы целы. Выбирай, что тебе больше нравится и что больше соотвествует твоей задаче. А может быть, даже и в рамках одной задачи выбирать и то и другое для разных объектов.
Совершенно непонятно, как должна себя вести такая комбинация. Что делать GC, если на управляемый им объект есть ссылки из неуправляемой им кучи? Как ему вообще понять, есть ли эти ссылки?
PD>Кстати, то же относится к деструктированию объектов. Почему бы не сделать так, чтобы программист мог либо поручить это GC, либо сказать — делай детерминированно сейчас ?
И какую семантику это будет иметь? Ну то есть вот допустим есть объект в управляемой куче, и мы говорим "умри!". Использовать место, ранее занимаемое этим объектом, до компактификации кучи всё равно нельзя. Точнее, неоправданно дорого. Делать упаковку сразу — еще дороже. Чем это поможет GC? Если речь не об освобождении памяти (которое GC и так неплохо проводит), то разрушение сводится к вызову определенного метода, после которого объект считается мертвым. В нынешних языках и системах такой метод называется Dispose() и для удобства объявлен в интерфейсе IDisposable.
PD>Никаких принципиальных причин, почему это невозможно, я не вижу (если кто видит — скажите).
Как только ты придумаешь ответы на эти принципиальные вопросы, отличающиеся от того, что есть сейчас, можно приниматься создавать новый язык. PD>Конечно, в нынешние языки и системы это не лезет. Может, пора новый язык/систему создать с такими возможностями.
В одной коробке совместить коня и трепетную лань не получается. Получается развести их по разным коробкам, что можно наблюдать невооруженным взглядом в нынешних языках и системах в полный рост.
... << RSDN@Home 1.2.0 alpha rev. 677>>
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Совершенно непонятно, как должна себя вести такая комбинация. Что делать GC, если на управляемый им объект есть ссылки из неуправляемой им кучи? Как ему вообще понять, есть ли эти ссылки?
Из неуправляемой кучи ссылок на управляемые объекты и обратно быть не должно. Не держишь же ты в С++ куче ссылки на автоматические объекты в стеке
Я просто предлагаю разделить эти два множества без пересечения. Если же оно все же потребуется, то идти по пути, аналогичному boxing-unboxing.
PD>>Кстати, то же относится к деструктированию объектов. Почему бы не сделать так, чтобы программист мог либо поручить это GC, либо сказать — делай детерминированно сейчас ? S>И какую семантику это будет иметь? Ну то есть вот допустим есть объект в управляемой куче, и мы говорим "умри!". Использовать место, ранее занимаемое этим объектом, до компактификации кучи всё равно нельзя. Точнее, неоправданно дорого. Делать упаковку сразу — еще дороже. Чем это поможет GC? Если речь не об освобождении памяти (которое GC и так неплохо проводит), то разрушение сводится к вызову определенного метода, после которого объект считается мертвым. В нынешних языках и системах такой метод называется Dispose() и для удобства объявлен в интерфейсе IDisposable.
С этим соглашусь.
S>В одной коробке совместить коня и трепетную лань не получается.
Почему ? Совместили же в C# кучу и стек. Я не вижу, почему стек с GC может в одной коробке уживаться, а неуправляемая куча нет. В конце концов стек от неуправляемой кучи отличается только механизмом выделения/освобожденяи памяти, если уж на то пошло
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>У меня на все это одна мысль возникла. А почему бы авторам некоей системы программирования не совместить GC и не-GC в одной коробке ? То есть ввести в систему GC и его управление памятью, но в то же оставить и возможность выделять/освобождать память вручную.
PD>Почему ? Совместили же в C# кучу и стек. Я не вижу, почему стек с GC может в одной коробке уживаться, а неуправляемая куча нет. В конце концов стек от неуправляемой кучи отличается только механизмом выделения/освобожденяи памяти, если уж на то пошло
Вдогонку. А кстати, почему в C# жестко привязали тип объектов (object или value) к способу размещения (куча или стек). Почему нельзя value разместить в куче или object в стеке ? Это принципиально или просто особенность реализации ?