Пользователи моей софтины умудрились так размножить мелкие объекты, что довели бедняжку до bad_alloc (32 битная арх.).
Соответственно встала задача оптимизации потребления памяти.
Вскрытие показало, что размер объектика (честный sizeof, не глубокий) 140 байт.
Из которых 2 х 28 байт занимают 2 std::basic_string — от них я легко избавлюсь.
Но есть еще 9 x 4 = 36 байт виртуальных таблиц, и вот здесь вопрошаю совета, что с ними делать.
Желательно уменьшить размер объекта, т.к. сейчас sizeof(Bar)*instance_count > 500 Mb.
Класс выглядит следующим образом:
class Bar: public IBar1,
public IBar2,
...
public IBar9
{
int ref_count;
unsigned flags;
...
// реализация IBar1
virtual void foo1() override;
...
};
Есть ли какой-нибудь паттерн/хак, чтобы "упаковать" несколько виртуальных таблиц в одну.
Или использовать NVI + что-то?
Избавиться от IBar1 ... IBar9 тяжело, т.к. есть много зависимого кода, который их активно использует.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>Я верно понимаю, что методы IBar1, ... IBar9 не пересекаются и ты можешь их менять?..
Не пересекаются. Менять могу не особо, т.к. клиентский код на них завязан.
E>А ещё такой вопрос: класс Bar -- он твой?
Мой, но это класс реализации, типа Bar_impl, который не виден клиентам.
Объявлен в приватном хидере, а IBarN — публичные интерфейсы.
E>То есть у тебя всё выглядит примерно так:
Все так.
Можно не химичить с приватным наследованием, и сделать FooN protected в ISuperBar, тогда NVI-обёртки IBarN можно выводить из ISuperBar публично, а нужные FooN поднимать в public через using.
Соответственно, вместо того, что бы выводить MDT из IBar1, ... IBar9 можно будет вывести из ISuperBar, а то, что IBarN поддержан выражать, переопределяя соответствующий operator IBarN*
Да, это всё, конечно UB или где-то рядом, так как мы приводим ISuperBar* к IBarN*, хотя MDT-объект наследником IBarN не является.
Работать будет если в IBarN нет ничего, кроме using и статических методов.
При этом всё равно нельзя будет между IBarN ходить по dynamic_cast, зато можно будет прямо operator IBarN*() позвать
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
E>QueryInterface знаешь?...
ессно...
Но QueryInterface решает другую задачу, когда есть разные реализации и мы спрашиваем, какие интерфейсы поддерживает данная реализация.
У меня же реализация одна и только одна, просто спрятана от клиентов за абстрактный интерфейс, а точнее за пачку интерфейсов.
E>При этом всё равно нельзя будет между IBarN ходить по dynamic_cast, зато можно будет прямо operator IBarN*() позвать
Идея понятна, но в свете избавления от хранения указателей на кучу vtbl возникает вопрос, кто же все таки реализует интерфейс IBarN и как индекс функции
в vtbl ISuperBar преобразуется в индекс функции в vtbl IBarN?
Здравствуйте, qaz77, Вы писали:
Q>Здравствуйте, Erop, Вы писали:
E>>Я верно понимаю, что методы IBar1, ... IBar9 не пересекаются и ты можешь их менять?.. Q>Не пересекаются. Менять могу не особо, т.к. клиентский код на них завязан.
E>>А ещё такой вопрос: класс Bar -- он твой? Q>Мой, но это класс реализации, типа Bar_impl, который не виден клиентам.
А как клиенты получают доступ к нужному им интерфейсу?
Есть какие-то функции получающие на вход "opaque" тип и выдающие IBarX?
Здравствуйте, Zhendos, Вы писали:
Z>И в нужных местах создавать `ImplementInterfaces`, при этом ваш класс `Foo` будет минимального размера.
Это если объектами Foo владеют не через интерфейсы, как, например, в COM'е, а как-то ещё.
Иначе только хуже будет.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, qaz77, Вы писали:
Q>Но QueryInterface решает другую задачу, когда есть разные реализации и мы спрашиваем, какие интерфейсы поддерживает данная реализация. Q>У меня же реализация одна и только одна, просто спрятана от клиентов за абстрактный интерфейс, а точнее за пачку интерфейсов.
Ну тем лучше.
Делаешь универсальный интерфейс, а из твоих интерфейсов делаешь пустые NVI адаптеры.
И привет.
Q>Идея понятна, но в свете избавления от хранения указателей на кучу vtbl возникает вопрос, кто же все таки реализует интерфейс IBarN и как индекс функции
Интерфейс IBarN -- это просто подмножество (часть методов) единственного интерфейса ISuperBar.
А IBarN -- это просто дурилка для статических проверок компилятора, которая позволяет вызывать один методы ISuperBar и не позволяет вызывать другие
Q>в vtbl ISuperBar преобразуется в индекс функции в vtbl IBarN?
vtb есть только у ISuperBar.
Объекты типа IBarN можно вообще запретить создавать.
Кстати, если интерфейсы все твои и реализация только одна, то можно ещё и по другому пути пойти.
Можно просто вывести их все друг из друга защищённо, и получить тот же эффект без хаккерства...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, qaz77, Вы писали:
Q>Класс выглядит следующим образом: Q>
Q>class Bar: public IBar1,
Q> public IBar2,
Q> ...
Q> public IBar9
Q>{
Q> int ref_count;
Q> unsigned flags;
Q> ...
Q> // реализация IBar1
Q> virtual void foo1() override;
Q> ...
Q>};
Q>
Q>Есть ли какой-нибудь паттерн/хак, чтобы "упаковать" несколько виртуальных таблиц в одну.
Есть такой хак, но насколько он применим в твоем случае тебе лучше знать. А хак прост как топор:
class IBar1 {};
class IBar2: public IBar1 {}
class IBar3: public IBar2 {}
...
class Bar: public IBarN
{
Impl;
}
...
PROFIT
Нормальный компилятор в этом случае должен сделать только лишь один указатель на ТВМ класса.
Хотя тут private/protected наследование может быть лучше, но это от ситуации зависит.
Здравствуйте, qaz77, Вы писали:
Q>Но есть еще 9 x 4 = 36 байт виртуальных таблиц, и вот здесь вопрошаю совета, что с ними делать.
Виртуальные таблиц являются статическими данными. Т.е. у вас 36 байт на весь класс Bar и не важно сколько вы создадите экземпляров. Тут вы ничего не выиграете.
Здравствуйте, Videoman, Вы писали:
V>Виртуальные таблиц являются статическими данными. Т.е. у вас 36 байт на весь класс Bar и не важно сколько вы создадите экземпляров. Тут вы ничего не выиграете.
Таблицы да, а вот указатели на них совсем даже нет.
Здравствуйте, andrey.desman, Вы писали:
V>>Виртуальные таблиц являются статическими данными. Т.е. у вас 36 байт на весь класс Bar и не важно сколько вы создадите экземпляров. Тут вы ничего не выиграете.
AD>Таблицы да, а вот указатели на них совсем даже нет.
Да, да неправ. Не о том подумал. Сморозил спросонья
Здравствуйте, Erop, Вы писали:
E>Делаешь универсальный интерфейс, а из твоих интерфейсов делаешь пустые NVI адаптеры.
Там есть довольно общие интерфейсы, типа ISerializable, который реализует еще 100+ класов.
Если все перевести на NVI, то нельзя будет Bar'ы скармливать функциям, ожидающим полиморфное поведение.
Либо все такие функции переделывать в шаблоны.
E>Кстати, если интерфейсы все твои и реализация только одна, то можно ещё и по другому пути пойти. E>Можно просто вывести их все друг из друга защищённо, и получить тот же эффект без хаккерства...
Я об этом тоже подумывал.
Теряется принцип "is a ...", но здесь этим можно пожертвовать.
E>>Кстати, если интерфейсы все твои и реализация только одна, то можно ещё и по другому пути пойти. E>>Можно просто вывести их все друг из друга защищённо, и получить тот же эффект без хаккерства...
Q>Я об этом тоже подумывал. Q>Теряется принцип "is a ...", но здесь этим можно пожертвовать.
В результате так и сделал.
Интерфейсы наследовал через public, чтобы было совместимо с существующим клиентским кодом.
Так появились артефакты в виде возможности неявного приведения IBar3 к IBar2, но я готов с эти мириться.
Спасибо за обсуждение.
Здравствуйте, qaz77, Вы писали:
Q>Там есть довольно общие интерфейсы, типа ISerializable, который реализует еще 100+ класов. Q>Если все перевести на NVI, то нельзя будет Bar'ы скармливать функциям, ожидающим полиморфное поведение. Q>Либо все такие функции переделывать в шаблоны.
Почему?
С точки зрения клиентского кода вообще ничего, кроме того, что кастить надо иначе, не поменяется же?
Q>Я об этом тоже подумывал. Q>Теряется принцип "is a ...", но здесь этим можно пожертвовать.
is a к интерфейсам вообще не применим. Там другое отношение "поддерживает интерфейс"...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Erop, Вы писали:
Q>>Если все перевести на NVI, то нельзя будет Bar'ы скармливать функциям, ожидающим полиморфное поведение. Q>>Либо все такие функции переделывать в шаблоны.
E>Почему? E>С точки зрения клиентского кода вообще ничего, кроме того, что кастить надо иначе, не поменяется же?
Функции ожидают параметр ISerializable& (к примеру) и зовут его виртуальные функции,
т.е. полиморфизм времени выполнения в чистом виде.
Если я для какого-то IBar: public ISerializable уберу наследование ISerializable
и сделаю не виртуальные функции load, save и пр. одноименные членам ISerializable,
то IBar нельзя будет скормить функциям, ожидающим ISerializable.
Сделать полиморфное поведение (времени компиляции) по одноименным функциям-членам можно
в шаблонной функции, когда не важно виртуальные функции-члены или нет.
Вот я и имел в виду, что если один из многих классов переделать на NVI,
то тогда все функции, куда его передавали как абстрактный интерфейс, нужно из обычных функций переделывать в шаблонные:
Даже если все классы, реализующие ISerializable, переделать на NVI, то все равно
полиморфизм времени выполнения использовать не получится, т.к. общей базы не будет.
E>is a к интерфейсам вообще не применим. Там другое отношение "поддерживает интерфейс"...
Я обычно думаю об интерфейсе как о роли, которую выполняет реализующий его класс по отношению к другому классу или алгоритму.
При этом роль может делиться на под-роли, т.е. интерфейс может иметь несколько базовых интерфейсов.
Можно также представить какие-то не связанные роли: ISerialiable, IObservable, IPrintable...
Оптимизация, требующая выводить несвязанные интерфейсы друг из друга, нарушает логику разделения на роли.
Даже порядок абсурден в любом случае:
class ISerializable: public IPrintable { ... };
// или
class IPrintable : public ISerializable{ ... };
Т.е. жертвуем логичным дизайном ради оптимизации размера объекта.
Здравствуйте, qaz77, Вы писали:
Q>Если я для какого-то IBar: public ISerializable уберу наследование ISerializable Q>и сделаю не виртуальные функции load, save и пр. одноименные членам ISerializable, Q>то IBar нельзя будет скормить функциям, ожидающим ISerializable.
Теперь тот, кто выводился из IClonable, может продолжать это делать, только лучше бы при этом ещё определить operator IClonable*
Но в целом для него ничего не поменяется, и для того, кто использует интерфейс IClonable не поменяется, за исключением того, что
нельзя запрашивать поддерживаемые интерфейсы через dynamic_cast.
Единственное что поменяется, так это если кто-то поддерживает несколько интерфейсов из ISuperBar, то он может вывестись просто публично из ISuperBar и реализовать нужные методы и операторы приведения
Q>Сделать полиморфное поведение (времени компиляции) по одноименным функциям-членам можно Q>в шаблонной функции, когда не важно виртуальные функции-члены или нет.
Это место не понял о чём тут речь, но, IMHO, это не важно.
Q>Вот я и имел в виду, что если один из многих классов переделать на NVI, Q>то тогда все функции, куда его передавали как абстрактный интерфейс, нужно из обычных функций переделывать в шаблонные:
Не нужно
Как видишь ничего шаблонным делать не надо.
Q>полиморфизм времени выполнения использовать не получится, т.к. общей базы не будет.
Почему? ISuperBar жеж?
ISerializable это такое view на ISuperBar, в котором в public видны только методы ISerializable и всё.
То есть для программиста он останется таким же ISerializable, за исключением того, что QueryInterface иначе далать надо, а для С++ это будет просто NVI view на интерфейс, но это будет спрятано за protected
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, qaz77, Вы писали:
E>>is a к интерфейсам вообще не применим. Там другое отношение "поддерживает интерфейс"...
Q>Я обычно думаю об интерфейсе как о роли, которую выполняет реализующий его класс по отношению к другому классу или алгоритму. Q>При этом роль может делиться на под-роли, т.е. интерфейс может иметь несколько базовых интерфейсов. Q>Можно также представить какие-то не связанные роли: ISerialiable, IObservable, IPrintable...
Ну да, "играет роль", реализует интерфейс" -- это другое отношение, чем "является".
Смотри, актёр Смоктуновский является человеком, и играет роль Гамлета Ы?
Q>Оптимизация, требующая выводить несвязанные интерфейсы друг из друга, нарушает логику разделения на роли. Q>Даже порядок абсурден в любом случае: Q>
Q>class ISerializable: public IPrintable { ... };
Q>// или
Q>class IPrintable : public ISerializable{ ... };
Q>
Q>Т.е. жертвуем логичным дизайном ради оптимизации размера объекта.
Ну да, мне решение с вьюшками нравится больше...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском