Здравствуйте, Bell, Вы писали:
B>Здравствуйте, remark, Вы писали:
R>>С золотым сечением — это обман. Оно не будет тут работать, т.к. задумывается. R>>Допустим текущий размер вектора у нас 100, следующий будет 161, следующий — 260. Хоть третий и влазит в сумму первых двух, но второй-то не будет освобожден до выделения третьего, т.к. нам надо скопировать из второго в третий.
B>Я что-то не понял — зачем рассматиривать 3 блока, и зачем условие "третий и влазит в сумму первых двух"? B>Там же все просто как пробка: выделили больший кусок, скопировали из предыдущего, освободили предыдущий B>Стандартная стратегия с удвоением размера тоже не вписывается в эти требования...
B>Или речь о другом?
Золотое сечение как коэффициент увеличения вектора взялось из следующей особенности.
Допустим у нас коэффициент увеличения 1.5 (меньше золотого сечения), тогда мы получаем следующие размеры: 100, 150, 225... Допустим первые 2 элемента были выделены последовательно в памяти, и при освобождении они объединились в блок размером 250. Тогда третий элемент можно разместить на месте первых двух. Но там остается некоторый неиспользованный кусочек, в данном примере — размером 25.
Допустим у нас коэффициент увеличения 2.0 (больше золотого сечения), тогда мы получаем следующие размеры: 100, 200, 400, 800... Допустим первые 2 элемента были выделены последовательно в памяти, и при освобождении они объединились в блок размером 300. Его не хватит под третий элемент. Под четвертый тоже не хватит места из под первых трех, и т.д. Т.е. всегда надо будет выделять новый блок все большего и большего размера.
Допустим у нас коэффициент увеличения 1.61 (золотое сечение), тогда мы получаем следующие размеры: 100, 161, 260... Допустим первые 2 элемента были выделены последовательно в памяти, и при освобождении они объединились в блок размером 261. Его *в точности* хватит под третий элемент. И дальше это будет постоянно повторяться рекурсивно, т.е. постоянно будем переиспользовать предыдущую память без каких-либо неиспользованных кусочков.
Но когда нам надо копировать данные из предыдущего блока в следующий, т.е. он не будет освобожден пока не выделится следующий, эта схема перестает работать. В такой ситуации коэффициент увеличения должен быть 1.32 (1 + X = X^3).
Здравствуйте, Bell, Вы писали:
B>Похоже, что автор попытался сэкономить память, занимаемую вектором chunks_ за счет постоянного копирования содержимиого вектора при увеличении размера. Наверное, на небольших объемах это имеет смысл Однако, когда счет идет на десятки миллионов мелких объектов, у менеджера памяти просто напросто срывает крышу B>Наверное, проблема может крыться и в стандартном аллокаторе, но ИМХО маловероятно, чтобы такая проблема возникала в реализации STL от двух разных разроботчиков B>Впрочем, сильно глубоко я не капал
B>Ах да, проблема исчезает, если закомментарить этот злосчастный reserve.
Поздравляю! Сукандинавский божок нашёл ещё одну жертву. Божки — они такие, без жертв не могут.
Думаю, что в духе Александреску было бы ввести ещё одну политику — резервирования вектора.
На днях столкнулся с проблемой, которая, возможно, заинтересует пользователей Loki::SOA.
Решил я прикрутить SOA к очередному проекту. Подправил код, собрал дебаг-версию, запустил, полюбовался на экономию памяти. Собрал релиз, запустил, и словил bad_alloc
Вот минимальный пример, на котором эта проблема у меня повторяется:
Loki::SmallObjAllocator ga;
class Test
{
int i_;
public:
Test(int i) : i_(i) {}
static void* operator new(size_t sz)
{
return ga.Allocate(sz);
}
static void operator delete(void* ptr, size_t sz)
{
ga.Deallocate(ptr, sz);
}
};
int main()
{
try
{
for(int i = 0; i < 20000000; ++i)
new Test(i);
}
catch(const bad_alloc&)
{
cout << "bad_alloc\n";
}
cout << "end\n";
return 0;
}
Платформа: WinXP Prof. SP2, 2GB RAM, 2xPentium D 3GHz
Компилятор VC7.1 + STLPort 4.6.2 (C DinkumSTL проблема тоже повторяется)
Корень всех зол нашелся в функции void* FixedAllocator::Allocate():
void* FixedAllocator::Allocate()
{
if (allocChunk_ == 0 || allocChunk_->blocksAvailable_ == 0)
{
Chunks::iterator i = chunks_.begin();
for (;; ++i)
{
if (i == chunks_.end())
{
// Initializechunks_.reserve(chunks_.size() + 1);//...
chunks_.push_back(newChunk);
//...
Здесь Chunks — это std::vector<Chunk>.
Похоже, что автор попытался сэкономить память, занимаемую вектором chunks_ за счет постоянного копирования содержимиого вектора при увеличении размера. Наверное, на небольших объемах это имеет смысл Однако, когда счет идет на десятки миллионов мелких объектов, у менеджера памяти просто напросто срывает крышу
Наверное, проблема может крыться и в стандартном аллокаторе, но ИМХО маловероятно, чтобы такая проблема возникала в реализации STL от двух разных разроботчиков
Впрочем, сильно глубоко я не капал
Ах да, проблема исчезает, если закомментарить этот злосчастный reserve.
ЗЫ
Глядя на главу 8 (Don't optimize prematurely) в "C++ Coding Standards", вспоминается фраза "И эти люди запрещают мне ковырять пальцем в носу!" (с)
ЗЗЫ
Еще один полезный момент: при удалении множества объектов имеет смысл отсортировать указатели: FixedAllocator кэширует Chunk, для которого последний раз выполнялась операция Deallocate, так что сортировка указателей увеличивает вероятность попадания в этот кзш, и как следствие — скорость удаления объектов.
Здравствуйте, Kswapd, Вы писали:
K>А не является ли само использование SOA типичным случаем преждевременной оптимизации?
Т.е. использование SOA в любом случае является преждевременной оптимизацией? Если именно это имеется ввиду — мне даже нечего сказать
Если подразумевается что-то другое — то нельзя ли сформулировать мысль яснее?
К>struct GoldenReservePolicy : ReservePolicy<GoldenReservePolicy> // но золото не знает про дядюшку Скруджа :)
К>{
К> static size_t capacity(size_t size) { return size*161/100; }
К>};
К>
С золотым сечением — это обман. Оно не будет тут работать, т.к. задумывается.
Допустим текущий размер вектора у нас 100, следующий будет 161, следующий — 260. Хоть третий и влазит в сумму первых двух, но второй-то не будет освобожден до выделения третьего, т.к. нам надо скопировать из второго в третий.
Тут должно быть число, удовлетворяющее: 1 + X = X^3. Т.е. четвертый размер должен влазить в сумму первого и второго.
Получается где-то 1.32... Не знаю, есть у этого числа какое-то название.
Первый — 100
Второй — 132
Третий — 174
Четвертый — 230
Третий элемент пока не освобожден, когда выделяем четвертый. А вот объединение первого и второго — как раз по размеру под четвертый.
Очевидно, что Евклид и Леонардо да Винчи НЕ разбираются в компьютерных структурах данных.
Здравствуйте, remark, Вы писали:
R>С золотым сечением — это обман. Оно не будет тут работать, т.к. задумывается. R>Допустим текущий размер вектора у нас 100, следующий будет 161, следующий — 260. Хоть третий и влазит в сумму первых двух, но второй-то не будет освобожден до выделения третьего, т.к. нам надо скопировать из второго в третий.
Я что-то не понял — зачем рассматиривать 3 блока, и зачем условие "третий и влазит в сумму первых двух"?
Там же все просто как пробка: выделили больший кусок, скопировали из предыдущего, освободили предыдущий
Стандартная стратегия с удвоением размера тоже не вписывается в эти требования...
Здравствуйте, Bell, Вы писали:
B>Я что-то не понял — зачем рассматиривать 3 блока, и зачем условие "третий и влазит в сумму первых двух"? B>Там же все просто как пробка: выделили больший кусок, скопировали из предыдущего, освободили предыдущий B>Стандартная стратегия с удвоением размера тоже не вписывается в эти требования...
B>Или речь о другом?
Видимо, речь о том, чтобы использовать пространство в куче оптимальным образом. Есть вероятность, что последовательные аллокации памяти из кучи будут размещаться рядом. В таком случае, при 4-й аллокации размер подобран так, чтобы она "ложилась" на место (вероятно) соседствующих 1-й и 2-й... imho..
Здравствуйте, valker, Вы писали:
B>>Или речь о другом?
V>Видимо, речь о том, чтобы использовать пространство в куче оптимальным образом. Есть вероятность, что последовательные аллокации памяти из кучи будут размещаться рядом. В таком случае, при 4-й аллокации размер подобран так, чтобы она "ложилась" на место (вероятно) соседствующих 1-й и 2-й... imho..
Ну, между реаллокациями вектора может случиться много всего... Закладываться на это никакого смысла нет... Тоже ИМХО.
Здравствуйте, Bell, Вы писали:
B>Ну, между реаллокациями вектора может случиться много всего... Закладываться на это никакого смысла нет... Тоже ИМХО.
Зато есть смысл в гануляции... Но STL это дело не приветсвует, к сожалению
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, valker, Вы писали:
B>>>Или речь о другом?
V>>Видимо, речь о том, чтобы использовать пространство в куче оптимальным образом. Есть вероятность, что последовательные аллокации памяти из кучи будут размещаться рядом. В таком случае, при 4-й аллокации размер подобран так, чтобы она "ложилась" на место (вероятно) соседствующих 1-й и 2-й... imho..
B>Ну, между реаллокациями вектора может случиться много всего... Закладываться на это никакого смысла нет... Тоже ИМХО.
Это больше из области теории. Эдакая разминка для мозгов умных дядек.
Тем не менее хоть какой-то аргумент за число 1.61, потому что для коэф. 2 или 1.5 афаик серьезных аргументов нет. Т.е. просто почва для субъективных пристрастий и холиваров.
B>>Ну, между реаллокациями вектора может случиться много всего... Закладываться на это никакого смысла нет... Тоже ИМХО.
R>Это больше из области теории. Эдакая разминка для мозгов умных дядек. R>Тем не менее хоть какой-то аргумент за число 1.61, потому что для коэф. 2 или 1.5 афаик серьезных аргументов нет. Т.е. просто почва для субъективных пристрастий и холиваров.
Коэффициент напрямую влияет на количество реаллокаций/копирований элементов. Но само собой, общее решение не может подойти в 100% случаев.
В общем, аргумент понятен, спорить смысла нет
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, remark, Вы писали:
R>>Очевидно, что Евклид и Леонардо да Винчи НЕ разбираются в компьютерных структурах данных.
К>Э, почему Евклид и Леонардо? К>Золотое сечение — это асимптотика чисел Фибоначчи.
Здравствуйте, Bell, Вы писали:
B>На днях столкнулся с проблемой, которая, возможно, заинтересует пользователей Loki::SOA. B>Решил я прикрутить SOA к очередному проекту. Подправил код, собрал дебаг-версию, запустил, полюбовался на экономию памяти. Собрал релиз, запустил, и словил bad_alloc
Здравствуйте, remark, Вы писали:
R>А у тебя какая версия? У тебя возможно какая-то старая версия или вообще — примеры к книге.
Да, у меня более старая версия. R>На последней версии (http://sourceforge.net/project/showfiles.php?group_id=29557) твой пример не компилируется.
ДА, действительно... Кому щас нужна совместимость сверху вниз?
R>Переписал вот так: R>
R>class Test : public Loki::SmallObject<>
R>{
R> int i_;
R>public:
R> Test(int i) : i_(i) {}
R>};
R>
R>Всё замечательно отработало, скушав 160 Мб памяти.
Да, стратегия реаллокации там несколько изменилась:
std::size_t size = chunks_.size();
// Calling chunks_.reserve *before* creating and initializing the new
// Chunk means that nothing is leaked by this function in case an
// exception is thrown from reserve.if ( chunks_.capacity() == size )
{
if ( 0 == size ) size = 4;
chunks_.reserve( size * 2 );
}
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, Кодт, Вы писали:
К>>Поздравляю! Сукандинавский божок нашёл ещё одну жертву. Божки — они такие, без жертв не могут.
B>Ну, жертвой я себя не ощущаю B>Решение проблемы заняло примерно час, а цели своей я достиг — и в скорости выиграл, и в расходе памяти
А на сколько/во сколько выиграл?
А то у меня в скорости выиграть никак не получается — только в памяти.
В памяти на 25 процентов выигрываю.
А в скорости на столько же проигрываю
Здравствуйте, maggot, Вы писали:
M>А на сколько/во сколько выиграл? M>А то у меня в скорости выиграть никак не получается — только в памяти. M>В памяти на 25 процентов выигрываю. M>А в скорости на столько же проигрываю
Я SOA в 2х проектах только использую: в одном скорость главное, и стратегия простая — сначала создали порядка 100000 объектов, обработали, разом прибили (предварительно отсортировав указатели). На расход памяти даже не смотрел, а вот выигрыш в скорости составил 2-3 раза.
Во втором важнее память, и стратегия такая: сначала создаем много объектов, группируем их, в конце погруппно удаляем. В памяти выигрыш 10%, в скорости проигрыш довольно ощутимый.
Здравствуйте, Feonyf, Вы писали:
F>http://sourceforge.net/projects/loki-lib/ F>Development Status : 4 — Beta
F>я считаю что эта фраза будет уместна, когда будет не beta
На sourceforge эта штука появилась далеко не сразу... Сперва она скачивалась с сайта автора (www.moderncppdesign.com), потом силами энтузиастов её стали делать всеядной — под разные платформы и компиляторы. Никакого статуса "альфа-бета-релиз" у Loki ещё не было.
Но уже тогда божок собирал кровавую жатву.
Здравствуйте, Кодт, Вы писали:
К>... К>Но уже тогда божок собирал кровавую жатву.
Слушай, я ни разу не видел от тебя высказываний в таком ключе ни по одной теме
Чем тебе так насолил Александреску со своей Loki? ИМХО к подобным продуктам в любом случае нужно относится без фанатизма, и держать напильник под рукой...
Здравствуйте, Bell, Вы писали:
К>>Но уже тогда божок собирал кровавую жатву. B>Слушай, я ни разу не видел от тебя высказываний в таком ключе ни по одной теме B>Чем тебе так насолил Александреску со своей Loki? ИМХО к подобным продуктам в любом случае нужно относится без фанатизма, и держать напильник под рукой...
Это не вопрос того, что Александреску насолил или не насолил. Он просто выложил грабли в публичный доступ, и я в этом вижу не его злой умысел, а приколы одноимённого скандинавского божка.
А поскольку грабли прикрыты слоем очень красивых решений, то тем обиднее на них наступать.
Представляешь, если в бусте или STL были бы разбросаны такие подарки?
Кстати, в Dinkumware STL для VC6 — были. Что стоит, например, сортировка списка, игнорирующая предикат...
Еще зависит от аллокатора нижнего уровня. Виндовый выделяет блоками кратными степени 2, за вычетом заголовка, а в конечном счёте все сводится к размеру физстраниц.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Привет
B>Платформа: WinXP Prof. SP2, 2GB RAM, 2xPentium D 3GHz B>Компилятор VC7.1 + STLPort 4.6.2 (C DinkumSTL проблема тоже повторяется)
B>Корень всех зол нашелся в функции void* FixedAllocator::Allocate(): B>
B>void* FixedAllocator::Allocate()
B>{
B> if (allocChunk_ == 0 || allocChunk_->blocksAvailable_ == 0)
B> {
B> Chunks::iterator i = chunks_.begin();
B> for (;; ++i)
B> {
B> if (i == chunks_.end())
B> {
B> // Initialize
B> chunks_.reserve(chunks_.size() + 1);
B> //...
B> chunks_.push_back(newChunk);
B> //...
B>
А мне кажется что это ошибка STL. Метод reserve должен как раз выделять столько памяти, чтобы минимизировать реалокацию. А именно например if( needreallocation ) newreserve = min(текущийreserve*3/2, желаемыйreserve).
По крайней мере до этого поста я именно так и думал
Здравствуйте, рыбак, Вы писали:
Р>А мне кажется что это ошибка STL.
Р>Метод reserve должен как раз выделять столько памяти, чтобы минимизировать реалокацию. А именно например if( needreallocation ) newreserve = min(текущийreserve*3/2, желаемыйreserve).
Метод reserve должен выделять столько памяти, сколько попросят. Объем запрашиваемой памяти (стратегию реаллокации) должен определять вызывающий код. Кстати приведенная тобой формула ни в коей мере не решает исходной проблемы, потому как min (size()*3/2, size()+1) == size()+1
Р>По крайней мере до этого поста я именно так и думал
Если не навязывать вектору свою стратегию реаллокации, то при необходимости он выделяет память в объеме size() * coef. Наиболее популярные значение coef - 2, 1.5.
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, рыбак, Вы писали:
Р>>А мне кажется что это ошибка STL. B>
Р>>Метод reserve должен как раз выделять столько памяти, чтобы минимизировать реалокацию. А именно например if( needreallocation ) newreserve = min(текущийreserve*3/2, желаемыйreserve).
B>Метод reserve должен выделять столько памяти, сколько попросят. Объем запрашиваемой памяти (стратегию реаллокации) должен определять вызывающий код. Кстати приведенная тобой формула ни в коей мере не решает исходной проблемы, потому как min (size()*3/2, size()+1) == size()+1
да, я ошибся, конечно же max(текущийreserve*3/2, желаемыйreserve).
Р>>По крайней мере до этого поста я именно так и думал B>Если не навязывать вектору свою стратегию реаллокации, то при необходимости он выделяет память в объеме size() * coef. Наиболее популярные значение coef - 2, 1.5.