Каковы негативные последствия расширения классов в ActionScript 3?

В своем игровом движке я использую Box2D для физики. Соглашения об именах Box2D и плохое комментирование разрушают непротиворечивую и хорошо документированную часть моего движка, которая немного расстраивает и плохо преподносит, когда вы ее используете.

Я подумал о создании набора классов-оболочек для Box2D. То есть классы, которые расширяют каждый из общих объектов Box2D и переписывают свои функции, чтобы следовать соглашениям об именах остальной части моего движка, и чтобы их было более ясно и последовательно прокомментировать. Я даже рассматривал возможность создания некоторых классов поверх них и добавления некоторых кусочков (например, геттеров для измерений на основе пикселей в b2Vec2 учебный класс).

Это нормально, но я не уверен на 100%, каковы будут негативные последствия этого и степень, в которой это повлияет на мои приложения и игры. Я не уверен, что компилятор в какой-то мере смягчает некоторые из моих проблем или мне нужно быть внимательным при добавлении ненужных классов для удобства чтения и последовательности.

У меня есть некоторые подозрения:

  • Большее потребление памяти для размещения дополнительного уровня структуры класса.
  • Влияние на производительность при создании новых объектов из-за инициализации дополнительного уровня членов?

Я спрашиваю конкретно о воздействии во время выполнения.

5 ответов

Решение

Это довольно распространенная проблема, когда дело доходит до интеграции сторонних библиотек, особенно библиотек, которые являются портами (как Box2DAS3), где они придерживаются соглашений о кодировании и именовании родительского языка, а не полностью интегрируются с языком назначения (в данном случае: Box2DAS3 с помощью getFoo() а также setFoo() вместо .foo геттер / сеттер).

Чтобы быстро ответить на ваш вопрос, нет, создание классов-оболочек не окажет существенного влияния на производительность; не больше, чем вы увидите в иерархии классов в вашем собственном проекте. Конечно, если вы рассчитываете цикл в 5 миллионов итераций, вы можете увидеть различие в миллисекунду или две, но при обычном использовании вы не заметите этого.

"Больше потребления памяти для обеспечения дополнительного уровня структуры класса". Как и любой язык с классовым наследованием, за кулисами будет использоваться vtable, поэтому у вас будет небольшое увеличение памяти / перфоманса, но оно незначительно.

"Влияние на производительность при создании новых объектов из-за инициализации дополнительного уровня членов?" Не более, чем обычная реализация, поэтому не о чем беспокоиться, если вы не создаете огромное количество объектов.

С точки зрения производительности, у вас, как правило, не должно быть проблем (предпочтение удобочитаемости и удобству использования, а не производительности, если у вас на самом деле нет проблем с этим), но я бы рассматривал это скорее как архитектурную проблему и, учитывая это, то, что я хотел бы рассмотреть Негативное влияние расширения / модификации классов внешней библиотеки обычно делится на 3 области, в зависимости от того, что вы хотите сделать:

  • Изменить библиотеку
  • Продлить занятия
  • Композиция с вашими собственными классами

Изменить библиотеку

Поскольку Box2DAS3 является открытым исходным кодом, ничто не мешает вам прыгать и рефакторировать все имена классов / функций для вашего сердца. Я серьезно подумал сделать это время от времени.

Плюсы:

  • Вы можете изменить то, что вы хотите - функции, классы, вы называете это
  • Вы можете добавить любые недостающие фрагменты, которые вам нужны (например, помощники преобразования пикселей)
  • Вы можете исправить любые потенциальные проблемы с производительностью (я заметил несколько вещей, которые можно было бы сделать лучше и быстрее, если бы они были выполнены способом "AS3")

Минусы:

  • Если вы планируете обновлять свою версию, вам необходимо вручную объединять / преобразовывать любые обновления и изменения. Для популярных библиотек или тех, которые сильно меняются, это может быть огромной болью
  • Это отнимает много времени - кроме модификаций, вам нужно хорошее понимание того, что происходит, чтобы вы могли вносить любые изменения, не нарушая функциональность
  • Если с ним работают несколько человек, они не могут полагаться на внешнюю документацию / примеры так сильно, так как внутреннее оборудование могло измениться.

Продлить занятия

Здесь вы просто создаете свои собственные классы-обертки, которые расширяют базовые классы Box2D. Вы можете добавлять свойства и функции по своему усмотрению, включая реализацию собственной схемы именования, которая переводится в базовый класс (например, MyBox2DClass.foo() может быть просто оберткой для Box2DClass.bar())

Плюсы:

  • Вы реализуете только те классы, которые вам нужны, и вносите только необходимые изменения
  • Ваши классы-обертки могут по-прежнему использоваться в базовом движке Box2D - т.е. вы можете передать MyBox2DClass объект внутреннего метода, который принимает Box2DClass и ты знаешь, что это сработает
  • Это наименьшее количество работы, из всех трех методов

Минусы:

  • Опять же, если вы планируете поддерживать свою версию в актуальном состоянии, вам необходимо убедиться, что любые изменения не нарушают ваши классы. Обычно не большая проблема, хотя
  • Может вводить в заблуждение класс, если вы создаете свои собственные функции, которые вызывают их эквивалент Box2D (например, "Должен ли я использовать setPos() или же SetPosition()?). Даже если вы работаете самостоятельно, когда вы вернетесь на занятия через 6 месяцев, вы забудете
  • Вашим классам не хватит последовательности и согласованности (например, некоторые функции используют вашу методологию именования (setPos() в то время как другие используют это из Box2D (SetPosition()))
  • Вы застряли с Box2D; Вы не можете изменить физические движки без большого количества разработчиков, в зависимости от того, как ваши классы используются в проекте. Это может быть не так уж важно, если вы не планируете переключаться

Композиция с вашими собственными классами

Вы создаете свои собственные классы, которые внутренне содержат свойство Box2D (например, MyPhysicalClass будет иметь свойство b2Body). Вы можете реализовать свой собственный интерфейс по своему желанию, и только то, что необходимо.

Плюсы:

  • Ваши классы чище и хорошо вписываются в ваш двигатель. Только те функции, которые вас интересуют
  • Вы не привязаны к движку Box2D; например, если вы хотите переключиться на Nape, вам нужно только изменить ваши собственные классы; остальная часть вашего движка и игр не обращают внимания. Другим разработчикам также не нужно изучать движок Box2D, чтобы использовать его
  • Пока вы там, вы можете даже реализовать несколько движков и переключаться между ними с помощью переключателя или интерфейсов. Опять же, остальные ваши движок и игры забывают
  • Хорошо работает с компонентными движками - например, у вас может быть Box2DComponent, который содержит b2Body имущество

Минусы:

  • Больше работы, чем просто расширение классов, поскольку вы по сути создаете промежуточный слой между вашим движком и Box2D. В идеале, вне ваших пользовательских классов, не должно быть ссылки на Box2D. Объем работы зависит от того, что вам нужно в вашем классе
  • Дополнительный уровень косвенности; обычно это не должно быть проблемой, так как Box2D будет напрямую использовать ваши свойства Box2D, но если ваш движок часто вызывает ваши функции, это дополнительный шаг на пути к производительности

Из трех я предпочитаю заниматься композицией, так как она дает большую гибкость и сохраняет целостность модульной структуры вашего движка, то есть у вас есть базовые классы движка, и вы расширяете функциональность с помощью внешних библиотек. Тот факт, что вы можете переключать библиотеки с минимальными усилиями, также является огромным плюсом. Это метод, который я использовал в своем собственном движке, и я также распространил его на другие типы библиотек - например, объявления - у меня есть класс Ad двигателя, который может при необходимости интегрироваться с Mochi, Kongregate и т. Д. остальная часть моей игры не заботится о том, что я использую, что позволяет мне сохранять мой стиль кодирования и последовательность во всем движке, оставаясь при этом гибким и модульным.

----- Обновление 20/9/2013 -----

Большое время обновления! Поэтому я вернулся, чтобы провести тестирование на размер и скорость. Класс, который я использовал, слишком велик для вставки, поэтому вы можете скачать его по адресу http://divillysausages.com/files/TestExtendClass.as

В нем я тестирую ряд классов:

  • Empty пример; класс, который просто расширяется Object и реализует пустой getPostion() функция. Это будет наш ориентир
  • b2Body пример
  • Box2DExtends пример; класс, который расширяется b2Body и реализует функцию getPosition() это просто возвращает GetPosition() (b2Body функция)
  • Box2DExtendsOverrides пример; класс, который расширяется b2Body и переопределяет GetPosition() функция (просто возвращает super.GetPosition())
  • Box2DComposition пример; класс, который имеет b2Body собственность и getPosition() функция, которая возвращает b2Body'sGetPosition()
  • Box2DExtendsProperty пример; класс, который расширяется b2Body и добавляет новый Point имущество
  • Box2DCompositionProperty пример; класс, который имеет как b2Body собственность и Point имущество

Все тесты проводились в автономном плеере FP v11.7.700.224, Windows 7, на небольшом ноутбуке.

Test1: размер

AS3 немного раздражает, если вы звоните getSize(), он даст вам размер самого объекта, но любые внутренние свойства, которые также Objects просто приведет к 4 byte увеличить, поскольку они только считают указатель. Я могу понять, почему они это делают, просто немного неловко получить правильный размер.

Таким образом, я обратился к flash.sampler пакет. Если мы попробуем создать наши объекты и сложим все размеры в NewObjectSample объекты, мы получим полный размер нашего объекта (ПРИМЕЧАНИЕ: если вы хотите увидеть, что создано и размер, прокомментируйте в log звонки в тестовом файле).

  • Пустой размер 56 // расширяет объект
  • b2 Размер тела 568
  • Размер Box2DExtends составляет 568 // расширяет b2Body
  • Размер Box2DExtendsOverrides равен 568 // extends b2Body
  • Размер Box2DComposition 588 // имеет свойство b2Body
  • Размер Box2DExtendsProperty равен 604 // расширяет b2Body и добавляет свойство Point
  • Размер Box2DCompositionProperty равен 624 //, имеет свойства b2Body и Point

Эти размеры все в байтах. Некоторые моменты стоит отметить:

  • База Object размер 40 байты, так что просто класс и ничего больше 16 байт.
  • Добавление методов не увеличивает размер объекта (в любом случае они реализованы на основе классов), тогда как свойства, очевидно, делают
  • Просто расширение класса ничего не добавило к этому
  • Экстра 20 байты для Box2DComposition родом из 16 для класса и 4 для указателя на b2Body имущество
  • За Box2DExtendsProperty и т.д., у вас есть 16 для Point сам класс, 4 для указателя на Point собственность и 8 для каждого из x а также y имущество Numbers знак равно 36 Разница в байтах между этим и Box2DExtends

Таким образом, очевидно, что разница в размере зависит от свойств, которые вы добавляете, но в целом довольно незначительна.

Тест 2: Скорость создания

Для этого я просто использовал getTimer() с петлей 10000, сама зациклена 10 (так 100к) раз, чтобы получить среднее. System.gc() был вызван между каждым набором, чтобы минимизировать время из-за сбора мусора.

  • Время создания пустого файла составляет 3,9 мс (av.)
  • Время создания b2Body составляет 65,5 мс (av.)
  • Время создания Box2DExtends составляет 69,9 мс (av.)
  • Box2DExtendsOverrides время создания составляет 68,8 мс (av.)
  • Время создания Box2DComposition составляет 72,6 мс (av.)
  • Box2DExtendsProperty время создания составляет 76,5 мс (av.)
  • Box2DCompositionProperty время создания 77,2 мс (av.)

Здесь нет целой кучи, чтобы отметить. Классы расширения / композиции занимают немного больше времени, но это как 0.000007ms (это время создания 100000 объектов), так что не стоит об этом думать.

Тест 3: Скорость звонка

Для этого я использовал getTimer() опять же, с петлей 1000000, сама зациклена 10 (так 10м) раз, чтобы получить среднее. System.gc() был вызван между каждым набором, чтобы минимизировать время из-за сбора мусора. Все объекты имели свои getPosition()/GetPosition() вызванные функции, чтобы увидеть разницу между переопределением и перенаправлением.

  • Время пустого для getPosition() составляет 83,4 мс (av.) // пусто
  • Время b2Body для GetPosition() составляет 88,3 мс (среднее значение) // нормальное
  • Время box2DExtends для getPosition() составляет 158,7 мс (av.) // getPosition() вызывает GetPosition()
  • Время Box2DExtendsOverrides для GetPosition() составляет 161 мс (av.) // переопределение вызовов super.GetPosition()
  • Время box2DComposition для getPosition() составляет 160,3 мс (av.) // вызывает this.body.GetPosition()
  • Время Box2DExtendsProperty для GetPosition() составляет 89 мс (av.) // неявный супер (т.е. не переопределяется)
  • Box2DCompositionProperty время для getPosition() составляет 155,2 мс (av.) // вызывает this.body.GetPosition()

Это меня немного удивило, с разницей между временами ~2х (хотя это все еще 0.000007ms за звонок). Задержка, похоже, полностью зависит от наследования классов - например, Box2DExtendsOverrides просто звонит super.GetPosition(), но вдвое медленнее, чем Box2DExtendsProperty, который наследует GetPosition() из своего базового класса.

Я предполагаю, что это связано с издержками поиска и вызова функций, хотя я посмотрел на сгенерированный байт-код, используя swfdump в FlexSDK, и они идентичны, так что либо он мне врет (или не включает), либо что-то мне не хватает:) Хотя шаги могут быть одинаковыми, время между ними, вероятно, не (например, в памяти, он переходит к вашему классу vtable, затем к базовому классу vtable и т. д.)

Байт-код для var v:b2Vec2 = b2Body.GetPosition() это просто:

getlocal        4
callproperty    :GetPosition (0)
coerce          Box2D.Common.Math:b2Vec2
setlocal3

в то время как var v:b2Vec2 = Box2DExtends.getPosition() (getPosition() возвращается GetPosition()) является:

getlocal        5
callproperty    :getPosition (0)
coerce          Box2D.Common.Math:b2Vec2
setlocal3

Для второго примера это не показывает вызов GetPosition(), так что я не уверен, как они решают это. Тестовый файл доступен для скачивания, если кто-то захочет его объяснить.

Некоторые моменты, которые следует иметь в виду:

  • GetPosition() на самом деле ничего не делает; по сути это геттер, замаскированный под функцию, и это одна из причин, почему "дополнительный штраф за шаг в классе" кажется таким большим
  • Это было на 10-метровой петле, что вы вряд ли будете делать в своей игре. Наказание за звонок на самом деле не стоит беспокоиться
  • Даже если вы беспокоитесь о штрафе, помните, что это интерфейс между вашим кодом и Box2D; внутренности Box2D не будут затронуты этим, только вызовы к вашему интерфейсу

В целом, я бы ожидал, что при расширении одного из моих классов будут достигнуты те же результаты, поэтому я не буду беспокоиться об этом. Реализуйте архитектуру, которая работает лучше всего для вашего решения.

Я знаю, что этот ответ не будет претендовать на награду, так как мне лень писать тесты. Но, поработав над базой кода Flash, я могу дать несколько советов:

Avm2 - это динамический язык, поэтому компилятор не будет ничего оптимизировать в этом случае. Упаковка звонка как звонка подкласса будет стоить. Однако эта стоимость будет постоянной и небольшой.

На стоимость создания объекта также самое большее влияет постоянное количество времени и памяти. Также время и сумма, вероятно, будут незначительными по сравнению с базовой стоимостью.

Но, как и во многих вещах, дьявол кроется в деталях. Я никогда не использовал box2d, но если он выполняет какие-либо вещи, пул объектов может не сработать. В общем, игры должны пытаться запускаться без выделения объектов во время игры. Поэтому будьте очень осторожны, чтобы не добавлять функции, которые выделяют объекты, просто чтобы быть красивее.

function addvectors(a:vec,b:vec,dest:vec):void

Может быть, некрасиво, но гораздо быстрее, чем

function addvectors(a:vec,b:vec):vec

(Надеюсь, я правильно понял синтаксис AS3...). Еще более полезным и уродливым может быть

function addvectors(a:Vector.<vec>, b:Vector.<vec>, dest:Vector.<vec>, offset:int, count:int):void

Так что мой ответ: если вы только оборачиваете для удобства чтения, сделайте это. Это небольшая, но постоянная стоимость. Но будьте очень, очень осторожны, чтобы изменить работу функций.

Некоторые отличные ответы здесь, но я собираюсь бросить свои два цента.

Есть две разные концепции, которые вы должны распознать: когда вы расширяете класс и когда вы реализуете класс.

Вот пример расширения MovieClip

public class TrickedOutClip extends MovieClip {
   private var rims = 'extra large'
   public function TrickedOutClip() { 
     super();
   }
}

Вот пример реализации MovieClip

public class pimpMyClip {
   private var rims = 'extra large';
   private var pimpedMovieClip:MovieClip;
   public function pimpMyClip() { 
     pimpedMovieClip = new MovieClip();
     pimpedMovieClip.bling = rims;
   }

 public function getPimpedClip() { 
     return pimpedMovieClip;
   }
}

Я думаю, что вы, вероятно, не хотите расширять эти классы box2D, но реализуйте их. Вот примерный план:

 public class myBox2DHelper {
   private var box2d = new Box2D(...);

  public function MyBox2DHelper(stage) {

  }

   public function makeBox2DDoSomeTrickyThing(varA:String, varB:Number) { 
       // write your custom code here
     }

   public function makeBox2DDoSomethingElse(varC:MovieClip) { 
       // write your custom code here
     }
  }

Удачи.

Я не знаю, окажет ли это большое влияние на время инстанции, но я отвечу на ваш вопрос по-другому: каковы ваши другие варианты? Кажется, они будут лучше?

Джексон Данстан сделал прекрасный тест производительности функций: http://jacksondunstan.com/articles/1820

Подвести итог:

  • закрытие дорогие
  • статика медленная: http://jacksondunstan.com/articles/1713
  • переопределение, вызов функции внутри подкласса не оказывает большого влияния

Так что, если вы хотите не использовать наследование, возможно, вам придется заменить его статическими вызовами, и это ухудшит производительность. Лично я расширю эти классы и добавлю готовые экземпляры всех объектов, которые мне понадобятся во время выполнения: если он большой, сделаю красивый экран загрузки...

Также обратите внимание на оптимизацию пост-байт-кода, например, на device: http://code.google.com/p/apparat/

Я не думаю, что расширение сильно повлияет на производительность. Да, есть некоторая стоимость, но она не так высока, если вы не используете композицию. Т.е. вместо непосредственного расширения классов Box2d вы создаете экземпляр этих классов и работаете с ним внутри своего класса. Например это

public class Child extends b2Body {
    public function Child() {
        // do some stuff here
    }
}

вместо этого

public class Child  {
    private var _body:b2Body;
    public function Child() {
        ...
        _body = _world.CreateBody(...);
        ...
    }
}

Я думаю, вы знаете, что чем меньше объектов, тем лучше. Пока вы сохраняете количество созданных экземпляров, вы будете иметь одинаковую производительность.

С другой точки зрения: а) добавление еще одного слоя абстракций может сильно изменить Box2d. Если вы работаете в команде, это может быть проблемой, потому что другие разработчики должны изучить ваше имя b) быть осторожным с запахом кода Middle Man. Обычно, когда вы начинаете упаковывать уже существующую функциональность, вы получаете классы, которые являются просто делегаторами.

Другие вопросы по тегам