在ActionScript 3中扩展类有什么负面影响?

Mar*_*rty 8 memory performance actionscript-3 avm2

在我的游戏引擎中,我使用Box2D进行物理.Box2D的命名惯例和糟糕的评论破坏了我的引擎的一致且记录良好的剩余部分,这有点令人沮丧并且当你使用它时表现不佳.

我考虑过为Box2D制作一套包装类.也就是说,扩展每个常见Box2D对象并重写其功能的类遵循我的引擎其余部分的命名约定,并使它们更清晰,更一致地进行评论.我甚至考虑构建一些类的ontop并添加一些零碎(比如b2Vec2类中的基于像素的测量的getter ).

这很好,但我不能100%确定这会产生什么负面影响以及这些影响我应用程序和游戏的程度.我不确定编译器是否在一定程度上减轻了我的一些顾虑,或者为了可读性和一致性而添加一些不必要的类时我是否需要考虑周全.

我有一些怀疑:

  • 更多的内存消耗,以适应更高级别的类结构.
  • 由于初始化更多级别的成员而在创建新对象时对性能产生影响?

我特别询问运行时影响.

div*_*ges 6

在集成第三方库时,这是一个非常常见的问题,尤其是作为端口的库(如Box2DAS3),它们保留了母语的编码和命名约定,而不是与目标语言完全集成(例如:Box2DAS3使用getFoo()setFoo()不是.foogetter/setter).

要快速回答您的问题,不会,制作包装类不会对性能产生重大影响; 只不过你将在自己的项目中的类层次结构中看到.当然,如果你计算500万次迭代的循环,你可能会看到一两毫秒的差异,但在正常使用中,你不会注意到它.

"更多的内存消耗,以适应更高级别的类结构." 就像任何具有类继承性的语言一样,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引擎才能使用它
  • 当你在那里时,你甚至可以实现多个引擎,并使用切换或接口在它们之间切换.同样,你的引擎和游戏的其余部分都是遗忘的
  • 与基于组件的引擎很好地配合 - 例如,您可以拥有一个包含b2Body属性的Box2DComponent

缺点:

  • 除了扩展类之外,还有更多工作,因为您实际上是在引擎和Box2D之间创建了一个中间层.理想情况下,在自定义类之外,不应该引用Box2D.工作量取决于你班上的需要
  • 额外的间接水平; 通常它不应该是一个问题,因为Box2D将直接使用你的Box2D属性,但如果你的引擎大量调用你的函数,那么这是一个额外的步骤,性能明智

在这三者中,我更喜欢使用组合,因为它提供了最大的灵活性并保持引擎的模块化特性,即您拥有核心引擎类,并且您可以使用外部库扩展功能.您可以用最少的工作量切换库这一事实也是一个巨大的优势.这是我在自己的引擎中使用的技术,我也将它扩展到其他类型的库 - 例如广告 - 我有我的引擎广告类,可以根据需要与Mochi,Kongregate等集成 - 我的游戏的其余部分并不关心我正在使用什么,这让我在整个引擎中保持我的编码风格和一致性,同时仍然灵活和模块化.

-----更新2013年10月9日-----

大更新时间!所以我回去做一些关于尺寸和速度的测试.我使用的课程太大而无法在此处粘贴,因此您可以在http://divillysausages.com/files/TestExtendClass.as下载它.

在其中,我测试了许多类:

  • 一个Empty例子; 一个只扩展Object并实现空getPostion()函数的类.这将是我们的基准
  • 一个b2Body实例
  • 一个Box2DExtends例子; 一个扩展b2Body并实现一个getPosition()只返回GetPosition()b2Body函数的类(函数)
  • 一个Box2DExtendsOverrides例子; 扩展b2Body和覆盖GetPosition()函数的类(它只返回super.GetPosition())
  • 一个Box2DComposition例子; 一个具有b2Body属性和getPosition()返回函数的函数的类b2Body's GetPosition()
  • 一个Box2DExtendsProperty例子; 扩展b2Body并添加新Point属性的类
  • 一个Box2DCompositionProperty例子; 具有b2Body财产和Point财产的类

所有测试均在独立播放器FP v11.7.700.224,Windows 7上,在不太好的笔记本电脑上完成.

测试1:大小

AS3有点烦人,如果你调用getSize()它,它会给你对象本身的大小,但任何内部属性也Objects只会导致4 byte增加,因为它们只计算指针.我可以看出为什么他们这样做,只是让得到合适的尺寸有点尴尬.

于是我转向flash.sampler包裹.如果我们对对象的创建进行采样,并在对象中添加所有大小NewObjectSample,我们将获得对象的完整大小(注意:如果您想查看创建的内容和大小,log请在测试中调用注释文件).

  • Empty的大小是56 //扩展Object
  • b2Body的大小是568
  • Box2DExtends的大小是568 //扩展b2Body
  • Box2DExtendsOverrides的大小是568 //扩展b2Body
  • Box2DComposition的大小是588 //具有b2Body属性
  • Box2DExtendsProperty的大小为604 //扩展b2Body并添加Point属性
  • Box2DCompositionProperty的大小为624 //具有b2Body和Point属性

这些大小都是以字节为单位.一些值得注意的要点:

  • 基本Object大小是40字节,所以只是类,没有别的是16字节.
  • 添加方法不会增加对象的大小(无论如何它们都是在类的基础上实现的),而属性显然也是如此
  • 只是扩展课程并没有添加任何内容
  • 来自类的额外20字节和指向属性的指针Box2DComposition164b2Body
  • 为了Box2DExtendsProperty等你16Point类本身,4对于指向Point特性,并8为每个xy财产Numbers= 36这之间的差异字节Box2DExtends

显然,大小的差异取决于你添加的属性,但总而言之,几乎可以忽略不计.

测试2:创作速度

为此,我简单地使用getTimer()循环10000,循环10(如此100k)次以获得平均值.System.gc()在每组之间调用以最小化由于垃圾收集而导致的时间.

  • 空的创作时间是3.9毫秒(av.)
  • b2Body的创作时间是65.5ms(av.)
  • Box2DExtends创作时间为69.9ms(av.)
  • Box2DExtendsOverrides的创作时间是68.8ms(av.)
  • Box2DComposition的创作时间是72.6ms(av.)
  • Box2DExtendsProperty的创作时间是76.5ms(av.)
  • Box2DCompositionProperty的创作时间是77.2ms(av.)

这里没有一大堆需要注意.扩展/组合类需要稍微长一点,但它就像0.000007ms(这是100,000个对象的创建时间),所以它不值得考虑.

测试3:通话速度

为此,我getTimer()再次使用循环1000000,循环10(因此10米)以获得平均值.System.gc()在每组之间调用以最小化由于垃圾收集而导致的时间.所有对象都getPosition()/GetPosition()调用了它们的函数,以查看覆盖和重定向之间的区别.

  • getPosition()的空时间为83.4ms(av.)//为空
  • b2Body的GetPosition()时间是88.3ms(平均)//正常
  • Box2DExtends的getPosition()时间是158.7ms(av.)// getPosition()调用GetPosition()
  • Box2DExtendsOverrides的GetPosition()时间是161ms(av.)//覆盖调用super.GetPosition()
  • Box2DComposition的getPosition()时间是160.3ms(av.)//调用this.body.GetPosition()
  • Box2DExtendsProperty的GetPosition()时间是89ms(av.)//隐式超级(即没有被覆盖)
  • Box2DCompositionProperty的getPosition()时间是155.2ms(av.)//调用this.body.GetPosition()

这个让我感到有些惊讶,时间之间的差异是2倍(虽然这仍然是0.000007ms每次通话).延迟似乎完全取决于类的继承 - 例如Box2DExtendsOverrides简单地调用super.GetPosition(),但速度是从其基类Box2DExtendsProperty继承的两倍GetPosition().

我想这与函数查找和调用的开销有关,虽然我看一下在swfdumpFlexSDK中使用生成的字节码,它们是相同的,所以它对我说谎(或者不包括它),或者有一些我缺少的东西:)虽然步骤可能是相同的,但它们之间的时间可能不同(例如在内存中,它跳到你的类vtable,然后跳到基类vtable等)

字节码var v:b2Vec2 = b2Body.GetPosition()只是:

getlocal        4
callproperty    :GetPosition (0)
coerce          Box2D.Common.Math:b2Vec2
setlocal3
Run Code Online (Sandbox Code Playgroud)

var v:b2Vec2 = Box2DExtends.getPosition()(getPosition()返回GetPosition())是:

getlocal        5
callproperty    :getPosition (0)
coerce          Box2D.Common.Math:b2Vec2
setlocal3
Run Code Online (Sandbox Code Playgroud)

对于第二个例子,它没有显示调用GetPosition(),所以我不确定他们是如何解决的.如果有人想解释它,可以下载测试文件.

要记住一些要点:

  • GetPosition()什么都不做; 它本质上是一个伪装成功能的吸气剂,这是"额外阶级惩罚"显得如此之大的一个原因
  • 这是一个10米的循环,你不太可能在你的游戏中做.每次通话罚款并不值得担心
  • 即使你担心罚款,记住,这是你的代码和Box2D的之间的接口; Box2D内部将不受此影响,只对您的界面的调用

总而言之,我希望通过扩展我自己的一个类来获得相同的结果,所以我不会真的担心它.实施最适合您的解决方案的架构.