Анимация
JavaScript
|
Главная Библионтека Оптимизация в особых ситуациях Если адрес переменной класса получать не требуется, ее можно хранить в виде внедренного объекта. Впрочем, как показывает следующий фрагмент, ситуация не всегда находится под контролем разработчика класса: void f(int); class Foo { private: int x; Адрес получать не нужно, поэтому храним непосредственно public: void F() { f(x); } Вышядит вполне безопасно, не правда ли? А теперь предположим, что автор функции f() привел ее интерфейс к следующему виду: void f(int&); И вот вся тщательно спроектированная оптимизация обрушивается вам на голову. У внедренных объектов есть еще одна проблема: вы должны проследить не только за тем, чтобы никогда не получать адрес объекта, но и за тем, чтобы никогда не получать адресов рекурсивно внедренных членов. class Bar { private: Foo foo; Допустим, вы сможете доказать, что ни одна из функций Bar не получает адрес foo. Но вам придется сделать следующий шаг и проследить еще и за тем, чтобы все функции Foo тоже были безопасными. Та же логика относится и к базовым классам. Важно понимать, что такая оптимизация должна осуществляться на уровне всей программы, а не только проектируемого класса. Алгоритм Бейкера Один из алгоритмов уплотнения жертвует неимоверным количеством (а точнее, половиной) памяти в интересах скорости. Процесс уплотнения понемногу вплетается в обычную работу программы. Этот алгоритм называется алгоритмом Бейкера (Bakers Algorithm). Пул памяти делится на две половины, A и B. В любой момент времени одна из этих половин является активной (то есть в ней создаются новые объекты). Память выделяется снизу вверх, а в момент удаления объекта не делается никаких попыток вернуть занимаемую им память. Время от времени все активные объекты копируются из одной половины памяти в другую. В процессе копирования автоматически происходит уплотнение нижней части новой активной половины. Активным называется объект, для которого в стане ведущих указателей найдется ссылающийся на него ведущий указатель (в нашем случае VoidPtr). Пространства объектов Половины представлены в виде пространств памяти для создания объектов. Класс HalfSpace изображает одну половину, а Space - всю память, видимую клиентам. Класс HalfSpace Каждая половина по отдельности выглядит как обычное пространство памяти со специализированной функцией Allocate (). Парная функция Dea11ocate() не понадобится. class HalfSpace { private: unsigned long next byte; Следующий выделяемый байт unsigned char bytes[HALFSIZE]; public: Ha1fSpace() : next byte(0) {} void* Al1ocate(size t size); void Reinitia1ize() { next byte = 0; } void* Ha1fSpace::Al1ocate(size t size) Выровнять до границы слова size = ROUNDUP(size); if (next byte + size >= HALFSIZE) return NULL; Не хватает памяти void* space = &bytes[next byte]; next byte += size; return space; Класс Space Общий пул представляет собой совокупность двух половин. Он также имеет функцию Al1ocate(), которая в обычных ситуациях просто поручает работу активной половине. Если в активной половине не найдется достаточно памяти, происходит переключение половин и копирование активных объектов в другую половину функцией Swap(). Эта схема основана на предыдущем материале - специализированном пуле VoidPtr со средствами перебора. class Space { private: HalfSpace A, B; HalfSpace* active; HalfSpace* inactive; void Swap(); Переключить активную половину, скопировать объекты public: Space() : active(&A), inactive(&B) {}; void* Al1ocate(size t size); void* Space::Al1ocate(size t size) void* space = active->Al1ocate(size); if (space != NULL) return space; Swap(); Space = active->Al1ocate(size); if (space == NULL) Исключение - нехватка памяти return space; void Space::Swap() if (active == &A) active = &B; inactive = &A; else active = &A; inactive = &B; active->Rei nitia1ize(); Перебрать все VoidPtr и найте активные объекты VoidPtrIterator* iterator = VoidPtr::poo1->iterator(); while (iterator->More()) VoidPtr* vp = iterator->Next(); if (vp->address >= inactive && vp->address < inactive + sizeof(*inactive)) void* new space = active->Al1ocate(vp->size); if (new space == NULL) Исключение - нехватка памяти memcpy(new space, vp->address, vp->size); vp->address = new space; delete iterator; Все существенное происходит в цикле while функции Space::Swap(). Каждый объект в предыдущей, ранее активной половине копируется в новую активную половину. Вскоре вы поймете, зачем мы проверяем, принадлежит ли адрес старой половине. Оператор new Конечно, у нас появляется перегруженный оператор new, который использует эту структуру. void* operator new(size t size, Space* space) return space->Al1ocate(size); Ведущие указатели Наконец, ведущие указатели должны использовать это пространство при создании объектов. template <c1ass Type> class BMP : public VoidPtr { private: Запретить копирование и присваивание указателей BMP(const MP<Type>&) {} BMP<Type>& operator=(const BMP<Type>&) { return *this; } public: BMP() : VoidPtr(new(object space) Type, sizeof(Type)) {} virtual ~BMP() { ((Type*)address->Type::~Type(); } Type* operator->() { return (Type*)address; } Здесь object space - глобальная переменная (а может быть, статическая переменная класса VoidPtr), которая ссылается на рабочее пространство Space. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 [ 72 ] 73 74 75 76 77 78 79 80 81 82 |