Анимация
JavaScript


Главная  Библионтека 

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

Оптимизация в особых ситуациях

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

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