Posts Tagged ‘Flash’

Mommy, where do framescripts come from?

When writing ActionScript you have your choice of using external class files or attaching code to a timeline by simply typing it into the .fla code editor window. I’m going to ask you to follow four simple steps and observe the results.

  1. Create a new .fla in Flash, set to use AS3
  2. On frame 1, type gotoAndStop(3);
  3. On frame 2, type trace(“I’m a script on frame 2″);
  4. On frame 3, type this["frame2"]();

This should result in your trace window showing “I am a script on frame 2″.

“No way, Mr. Horseman! How could this have happened? The playhead never touched frame 2!” I hear your cries of astonishment even from here. Indeed, how did this happen? Let’s stop to think for a minute about what happens when Flash actually compiles a swf. The first thing to remember is that in a published swf frame scripts do not exist. In a previous entry I talked briefly about what the compiler does with instances on the stage. Note the following line of code from that old entry:

public var foo_mc:NewMovieClip0; // Where did this come from?????

Something similar happens every time you use the Flash Authoring Tool to write a framescript. There is an undocumented (but by now very well known) function of the MovieClip class called addFrameScript, which allows you to attach “frame scripts” at runtime to a MovieClip or any class derived from MovieClip. Indeed one thing you’ll find is that if you try to subclass Sprite and link that to a library symbol with frame scripts, you’ll recieve an error to the effect that Sprite does not have a function called addFrameScript. This should tell you that internally Flash uses this function to give the illusion that scripts you write in the Flash editor just “exist” as part of the movieclip. If we decompile the swf we just created, we will see exactly that.

package DecompileMe_fla
{
    import flash.display.*;
 
    dynamic public class MainTimeline extends MovieClip
    {
 
        public function MainTimeline()
        {
            addFrameScript(0, frame1, 1, frame2, 2, frame3);
            return;
        }// end function
 
        function frame3()
        {
            var _loc_1:String;
            _loc_1.this["frame2"]();
            return;
        }// end function
 
        function frame1()
        {
            gotoAndStop(3);
            return;
        }// end function
 
        function frame2()
        {
            trace("I am a script on frame 2");
            return;
        }// end function
 
    }
}

When Flash is required to attach frame scripts to a class (regardless of whether you explicitly created the class, or whether you are implicitly creating one simply by nature of the fact that you’ve attached a framescript to a movieclip in the editor) it adds (implicitly) public functions to that class named after the frame on which the code appears, and then uses addFrameScript in the constructor to add those frames at runtime. Since these functions are just public and freely accessable, the fact that the “playhead” never even needs to “touch” a frame in order for the frame’s scripts to be called should be quite obvious.

“You’re making my head hurt, Horseman! I implore you, cease this haunting with accursed visions from beyond the abyss!”

Piercing the veil of abstraction is always a perception-altering experience. You can expect to feel a slight discomfort, but more than that you should feel a distinct thrill about uncovering another of the world’s great mysteries.

Tags: ,

2 Comments


The Object Pool is now open!

“Okay, so the Horseman has been learning Java and Android development… does this mean he won’t ever write about ActionScript again?”

Of course I will, dear reader. In my last entry about ActionScript, I mentioned that I believed that you could gain performance benefits in Box2DAS3 by using object pooling. I’ve been toying with the concept, and so far my idea seems sound.

The concept of an object pool is of benefit when you work in an environment where instantiation is an expensive process, and you’ll find yourself instantiating repeatedly, possibly ceaslessly for the life of the program. In other words, the dictionary definition of the Flash Player.

In examining the source code for the version of Box2D I’ve used for my site’s banner, just at a glance it appears that the most commonly instantiated object in the library is the humble b2Vec2. It is used everywhere, and is frequently instantiated in a one-off fashion to generate throw-away data. When using the default version of the library, my banner instantiates between 600 and 2400 b2vec2 objects every frame during the very first word drop. That is not a small number. Even worse, it grows with every new word on the screen. It doesn’t take very long to have 12000 instantiations every frame. Where do all these instances come from and are they a symptom of a bigger problem? What is the solution, if so?

You can hunt for every instance of new b2Vec2 in the source code and come up with some interesting results. For instance, take the definition for the ClipVertext object:

package Box2D.Collision{
 
 
import Box2D.Common.Math.*;
import Box2D.Collision.*;
 
 
public class ClipVertex
{
	public var v:b2Vec2 = new b2Vec2();
	public var id:b2ContactID = new b2ContactID();
 
};
 
 
}

Notice that any time you instantiate one of these, you create a new b2Vec2. So how often is a ClipVertex instantiated? Quite frequently, and all in one single class file! They are generated exclusively in b2Collision:

var incidentEdge:Array = [new ClipVertex(), new ClipVertex()];
var clipPoints1:Array = [new ClipVertex(), new ClipVertex()];
var clipPoints2:Array = [new ClipVertex(), new ClipVertex()];

Well that’s not so bad, right? I mean, that only appears in one function call: b2CollidePolygons. Well… b2Contact.b2CollidePolygons is called inside of b2PolygonContact.Evaluate, which is… well… here’s a stack trace:

	at Box2D.Dynamics.Contacts::b2PolygonContact/Evaluate()
	at Box2D.Dynamics.Contacts::b2Contact/Update()
	at Box2D.Dynamics::b2ContactManager/Collide()
	at Box2D.Dynamics::b2World/Step()

Hey, that’s a really familiar set of functions. They’re ones we looked at last time. How often were they being called, anyhow? Well, c.Update happens any time a collision persists between “awake” objects… as it turns out, that’s a lot. So what can we do here? One way to handle this is with an object pool for ClipVertex.

The basic idea behind a pool is that you perform several expensive instantiations in one process, taking a relatively small hit upfront, so that you can recycle a limited number of objects without re-instantiating. Bear in mind that maintaining the pool requires some processor overhead itself, and in the case of a very simple object (like b2Vec2) will not gain you a direct advantage in terms of processing performance, though in the Flash context fewer junk objects created mean fewer runs for the garbage collector, which is itself an expensive process.

So, what might our pool look like? Here is a very simple object pool for ClipVertex instances:

package Box2D.Pools {
	import Box2D.Collision.b2ContactID;
	import Box2D.Collision.ClipVertex;
	/**
	 * ...
	 * @author The Horseman @ http://www.scriptocalypse.com
	 */
	public class ClipVertexPool{
 
		public static var m_pool:Array = [];
		public static var m_poolLength:uint;
 
		public static function get poolLength():Number {
			return m_pool.length;
		}
 
 
		public static function Retrieve():ClipVertex {
 
 
 
			if (!m_pool.length) {
				for (var i:int = 0; i < 6 ; i++) {
					m_pool[m_pool.length] = new ClipVertex();
				}
			}
			m_poolLength = m_pool.length - 1;
			var result:ClipVertex = m_pool[int(m_poolLength)];
			m_pool.length = m_poolLength;
			result.v.x = result.v.y = 0.0;
			result.id = new b2ContactID();
			return result;
 
 
		}
 
		public static function Recycle(pClipVertex:ClipVertex):void {
			if(pClipVertex is ClipVertex){
				m_pool[m_pool.length] = pClipVertex;
			}
		}
 
		public static function RecycleMany(pClipVertices:Array):void {
			var cv:ClipVertex
			for (var i:int = 0, ilen:int = pClipVertices.length ; i < ilen ; i++) {
				cv = pClipVertices[int(i)] as ClipVertex;
				if (cv) m_pool[m_pool.length] = cv;
			}
		}
 
 
	}
 
}

Here’s what we’re doing:

When you want an instance of ClipVertex, you call ClipVertexPool.Retrieve(). If there are any spare instances in the pool, one will be sent back to you in an initialized state. Otherwise, we will instantiate six more objects, add them to the pool, and keep them for use later.

Now you replace all the calls to new ClipVertex with calls to ClipVertexPool.Retrieve(), and at the end of the function’s lifecycle remember to pass the instances back to ClipVertexPool.Recycle(). You will quickly notice a few things:

  1. Your Retrieve function only seems to ever have to instantiate one time. The first time it’s ever called.
  2. The number of b2Vec2 instantiations per frame has just dropped by several hundred.

There’s a one-to-one relationship between instantiations of ClipVertex and instantiations of b2Vec2, and as we’ve seen we eliminated hundreds of wasteful calls to new ClipVertex with a single code construct. It’s interesting to note that in fact for the purposes of the flaming banner the application truly only needs six instances of ClipVertex to exist for the entire lifetime of the program.

“But Mr. Horseman, why go to the trouble of implementing a pool for these six instances of ClipVertex? We could store them as static variables in b2PolygonContact and just re-initialize them there.”

Well that’s a valid point. Pooling ClipVertex is slightly (but only just slightly) academic, as you’ve surmised. Here’s a more meaty version of the object pool. Allow me to introduce to you the b2OBB object:

package Box2D.Collision{
 
import Box2D.Common.Math.*;
 
/// An oriented bounding box.
public class b2OBB
{
	public var R:b2Mat22;		///< the rotation matrix
	public var center:b2Vec2 = new b2Vec2();	///< the local centroid
	public var extents:b2Vec2 = new b2Vec2();	///< the half-widths
 
 
 
};
 
 
}

Here we have another object that implicitly instantiates b2Vec2 (2 of them) as part of its own instantiation. So, how often is b2OBB instantiated?

 
package Box2D.Collision.Shapes{
 
 
 
import Box2D.Common.Math.*;
import Box2D.Common.*;
import Box2D.Collision.Shapes.*;
import Box2D.Dynamics.*;
import Box2D.Collision.*;
import Box2D.Factories.b2OBBPool;
import Box2D.Factories.b2Vec2Pool;
 
/// Convex polygon. The vertices must be in CCW order for a right-handed
/// coordinate system with the z-axis coming out of the screen.
 
public class b2PolygonShape extends b2Shape
{
 
/// ***  SNIP
 
// implicitly instantiated as part of instantiation of b2PolygonShape
public var m_obb:b2OBB = new b2OBB();

Well, how often is a b2PolygonShape instantiated? Not nearly every frame, but definitely any time we generate a new physics body. So why not create a pool for it just like the one we created for ClipVertex?

package Box2D.Pools {
	import Box2D.Collision.b2OBB;
	import Box2D.Common.Math.b2Mat22;
	import Box2D.Common.Math.b2Vec2;
	/**
	 * ...
	 * @author The Horseman @ http://www.scriptocalypse.com
	 */
	public class b2OBBPool{
 
		private static var m_pool:Array = [];
		private static var m_poolLength:uint = 0;
 
		public static function get poolLength():Number {
			return m_pool.length;
		}
 
		public static function Retrieve(x:Number = 0.0, y:Number = 0.0):b2OBB {
 
			var result:b2OBB;
 
			if (!m_pool.length) {
				for (var i:int = 0 ; i < 500 ; i++) {
					m_pool[int(m_pool.length)] = new b2OBB();
				}
			}
			m_poolLength = m_pool.length - 1;
			result = m_pool[int(m_poolLength)];
			m_pool.length = m_poolLength;
			result.R = new b2Mat22();
			result.center.x = 0;
			result.center.y = 0;
			result.extents.x = 0;
			result.extents.y = 0;
			return result;
		}
 
		public static function Recycle(pOBB:b2OBB):void {
 
 
			if (!(pOBB is b2OBB)) return;
			m_pool[m_pool.length] = pOBB;
		}
 
	}
 
}

And now instead of calling new b2OBB() we will call b2OBBPool.Retrieve().

“But hold your horses, Horseman! When are those objects ever recycled? Aren’t you going to create them until the cows come home?”

Aaaaah, but that is not precisely true! You see, the library has built into it a non-implemented set of Create / Destroy functions for just such things as polygon shapes. In fact I believe the intention of the library authors was to have pooling of Bodies and Joints judging by the way they are created. You see, there is only one place where a call to new b2PolygonShape is ever made: b2Shape.Create(); Similarly, b2Shape has a .Destroy() function but the AS3 version of it is unimplemented. Such a shame to have an empty function call! Well since we now actually have something for that Destroy function to do (clean up our b2PolygonShape and its b2OBB) let’s implement Destroy().

static public function Destroy(shape:b2Shape, allocator:*) : void
	{
 
 
		/*b2Shape does not natively have a 
RecycleInstanceProperties function.  I have however, 
defined one for it and left it unimplemented.  The 
b2PolygonShape subclass, does override and
 implement the function.*/
 
		shape.RecycleInstanceProperties();
	}
// in b2PolygonShape
override public function RecycleInstanceProperties():void {
	super.RecycleInstanceProperties();
	b2OBBPool.Recycle(m_obb);
}

The recycle functions are called when a body is destroyed. This means that until the number of b2PolygonShape instances in existence at one time exceeds the number available in the pool we won’t ever have to re-instantiate another one. As it turns out, the most we ever seem to need is around 250. Once that number has been reached, we save on creation of b2OBB and its associated b2Vec2.

So then, when will the Horseman become adventuresome enough to fully implement those unimplemented Create/Destroy functions and give the Box2D library the ultimate in pooling?

Not anytime soon. I believe I’ve found most of the major bottlenecks already and opened them. Recycling bodies and joints might be nice, but probably won’t yield the benefits I’ve managed to gain by pooling ClipVertex, b2OBB, b2Mat22, b2XForm, b2Manifold, and the like. I’ve reduced the number of both explicit and implicit instantiations of my canary in the coal mine from 2400 per frame down to about 500 per frame during the first word drop. My changes seem to have improved the time in the physics step in the test bed applications and don’t appear to have broken them. Similarly, my banner also runs faster and has not yet shown itself to have been compromised. This is enough for the time being.

Tags: , , , , ,

No Comments


Refactoring Box2D

Have you ever wondered whether your code is performing as best it can? What are your benchmarks, anyhow? How do you determine what “as best it can” means? These are all questions of heavy existential weight, but usually we have a reasonable idea of what it is we’d like to improve and why. Are you looking to turn a mess of unreadable and unmaintainable code into something more pleasurable to behold? Are you tasked with putting a processor pig on a diet? Usually you’ll have a sense of what you really need.

The real question is “How?”

I’m going to examine a concept called “Refactoring” and I’m going to give you a case study in it as I refactor parts of the Box2DAS3 (version 2.0.1) engine with the goal of improving runtime performance and memory consumption.

To refactor code is to change its inner workings without destructively changing its interfaces or altering the output of its functions and expressions in any way. You may wonder “How can this possibly help? Doesn’t this mean that the code is just doing what it always did?”

No, not in the slightest.

I had an itch to see if I could manage to squeeze some more juice out of the ol’ physics engine for the sake of the banner up above. If it’s running more smoothly on your machine now (and it should be. It certainly is on mine), this is the reason why.

Before we begin, there is one thing to keep in mind when you decide to refactor a 3rd party library : thier updates can break code you’re relying on! Your refactored code may not be compatible with the vision the creators originally conceived when they wrote it. They might have code they were intending to implement but simply haven’t, and their ideas might be better than yours. In this case you’re stuck with a lot of time sunk into your own custom branch of the project which might fall far behind the official branch. I will do my best to point out the danger zones as I encounter them.

I will admit to you that what follows is not the most formal method of refactoring. You can use a debugger to step through your code, or you can simply do as I have done here and take the initiative to “run through” yourself, from function to function and “execute” the code in your head. We’ll take this latter approach for now, as I find it tells an interesting story.

A reasonable place to look when identifying performance bottlenecks is anything that happens in a loop, or on an interval. The most obvious interval in the world of Box2D (and the one that is called 30 times per second in my banner) is b2World.Step(); Let’s examine this function. I’ll call out some places in the code via comments:

	public function Step(dt:Number, iterations:int) : void{
 
		m_lock = true;
 
		// *** An instantiation happens 30 times per second *** //
		var step:b2TimeStep = new b2TimeStep();
 
		step.dt = dt;
		step.maxIterations	= iterations;
		if (dt > 0.0)
		{
			step.inv_dt = 1.0 / dt;
		}
		else
		{
			step.inv_dt = 0.0;
		}
 
		step.dtRatio = m_inv_dt0 * dt;
 
		step.positionCorrection = m_positionCorrection;
		step.warmStarting = m_warmStarting;
 
		// Update contacts.
		m_contactManager.Collide();
 
		// Integrate velocities, solve velocity constraints, and integrate positions.
		if (step.dt > 0.0)
		{
			Solve(step);
		}
 
		// Handle TOI events.
		if (m_continuousPhysics && step.dt > 0.0)
		{
			SolveTOI(step);
		}
 
		// Draw debug information.
 
		//*** Regardless of whether it's useful, or whether we're debugging,
		         this function is called 30 times per second ***//
		DrawDebugData();
 
		m_inv_dt0 = step.inv_dt;
		m_lock = false;
	}

First, note the instantiation of a b2TimeStep each time we call the Step function. What is a b2TimeStep? It’s this:

package Box2D.Dynamics{
 
 
public class b2TimeStep
{
	public var dt:Number;			// time step
	public var inv_dt:Number;		// inverse time step (0 if dt == 0).
	public var dtRatio:Number;		// dt * inv_dt0
	public var maxIterations:int;
	public var warmStarting:Boolean;
	public var positionCorrection:Boolean;
};
 
 
}

It’s simply a data structure. There are no functions at all. Now, in case you did not know, instantiation is expensive. It is not something you want to do willy-nilly if you can possibly help it, and from the look of the code above it seems like all the variables are set immediately after instantiation within the scope of the Step function. So instead of instantiating from scratch, let’s simply make one local Class-level variable to contain our Step function’s b2TimeStep object, and do some refactoring:

 
	private var m_stepScopeTimeStep:b2TimeStep = new b2TimeStep();
 
	public function Step(dt:Number, iterations:int) : void{
 
		m_lock = true;
 
		//var step:b2TimeStep = new b2TimeStep();
		var step:b2TimeStep = m_stepScopeTimeStep;

“Why did he choose to to simply assign the old step variable with the object referenced by m_stepScopeTimeStep? Why not just find/replace all instances of the word “step” with “m_stepScopeTimeStep”?

When refactoring it is critical to take small steps. We know that the code works as it was written, so the goal at least for now is to modify it as little as possible while still making it better. Yes, we are still allocating memory for an unneccessary variable at the start of the Step function, but what’s more important right now is to stop instantiating a needless variable every time Step is called.

We now dutifully confirm that our change has not broken the code. It is best to do this with a debugger, but for our purposes we’ll simply execute the code and ensure that it still runs correctly.

This same sort of redundant instantiation happens in other places in the library as well. notably, there are a great many b2Island objects instantiated every frame when only a single one is ever needed. It can simply be re-initialized and reused.

Cutting down on instantiations of b2Islands and b2TimeSteps alone helps save several MB of memory over time, which can be better spent rendering the awesomeness of physics.

The next thing we’ll do in the Step function is examine the call to the DrawDebugData function. Here’s what’s going on inside of it:

public function DrawDebugData() : void{
 
		if (m_debugDraw == null)
		{
			return;
		}
 
		// snipped ...

One thing to remember about ActionScript is that function calls are relatively expensive to perform. You shouldn’t do it without a good reason, and particularly not on a loop. So what we’ll do instead is this:

//DrawDebugData();
if(m_debugDraw) DrawDebugData();

In the event that you’re not debugging the application, you’ll save 30 function calls per second here just by confirming that there’s even a reason to call the function in the first place. Again, we’re not going to change anything inside the DrawDebugData function. It’s not quite in the scope of the refactor… I really couldn’t care less right now about how well it runs in debug mode, as I’m only displaying content in production mode.

So, that’s all well and good. Where should we go next? Let’s scan our way down and see what functions are being called in Step

 
// I wonder what's happening in this function call?
m_contactManager.Collide();

We’ve seen our first call here in this line. m_contactManager is an instance of b2ContactManager, so let’s open it up and see what this function does:

	public function Collide() : void
	{
		// Update awake contacts.
		for (var c:b2Contact = m_world.m_contactList; c; c = c.m_next)
		{
			var body1:b2Body = c.m_shape1.m_body;
			var body2:b2Body = c.m_shape2.m_body;
			if (body1.IsSleeping() && body2.IsSleeping())
			{
				continue;
			}
 
			c.Update(m_world.m_contactListener);
		}
	}

Doesn’t look like anything suspicious is happening here, does it?

Wait!

    // getter functions for "IsSleeping"?  Is this just a boolean we can retrieve for ourselves?
    if (body1.IsSleeping() && body2.IsSleeping())

Let’s open up b2Body and have a look-see:

/// A rigid body.
public class b2Body
{
	/// Creates a shape and attach it to this body.
	/// @param shapeDef the shape definition.
	/// @warning This function is locked during callbacks.
	public function CreateShape(def:b2ShapeDef) : b2Shape{
 
           /*** snip ***/
 
	/// Is this body sleeping (not simulating).
	public function IsSleeping() : Boolean{
		return (m_flags & e_sleepFlag) == e_sleepFlag;
	}
 
           /*** snip ***/
 
	public var m_flags:uint;
 
           /*** snip ***/
 
	// m_flags
	//enum
	//{
		static public var e_frozenFlag:uint			= 0x0002;
		static public var e_islandFlag:uint			= 0x0004;
		static public var e_sleepFlag:uint			= 0x0008;
		static public var e_allowSleepFlag:uint		= 0x0010;  // this is the one!
		static public var e_bulletFlag:uint			= 0x0020;
		static public var e_fixedRotationFlag:uint	= 0x0040;
	//};
		static public var e_sleepFlag:uint			= 0x0008;

So the b2Body not only has a function call for IsSleeping, but it does a bitwise operation based on its current flags uint to determine whether or not it counts as “asleep”. Changing the internal guts of how Box2D determines whether an object sleeps might be worthwhile, but that would require some benchmarking. For now what we’ll do is take advantage of the fact that the variables are all public and perform the comparison without calling the function:

	public function Collide() : void
	{
		// Update awake contacts.
		for (var c:b2Contact = m_world.m_contactList; c; c = c.m_next)
		{
			var body1:b2Body = c.m_shape1.m_body;
			var body2:b2Body = c.m_shape2.m_body;
			//if (body1.IsSleeping() && body2.IsSleeping())
			if ((body1.m_flags & b2Body.e_sleepFlag) == b2Body.e_sleepFlag && 
				(body2.m_flags & b2Body.e_sleepFlag) == b2Body.e_sleepFlag)
			{
				continue;
			}
 
			c.Update(m_world.m_contactListener);
		}
	}

We’ve now eliminated two needless function calls, each of which were being called by Step. Testing this code shows that the application continues to behave properly. But before we go on…

Danger : While we have done nothing to change the actual results of the code on execution we have definitely “painted ourselves into a corner” in a sense. The code becomes vastly more efficient by removing needless get/set function calls, but by breaking encapsulation we’re now at the mercy of fate. By no longer relying on the IsSleeping() function, we’ve lost out on the potential that the function itself may be made more efficient… or more disastrously, if in a future version of the library the way “sleep” is determined changes we’re no longer protected from it by a function that abstracts it away from us. It’s a potentially future-breaking change. In this particular case, it’s not likely that there would be a change, but that’s not the case for other parts of the library. Particularly, there are a number of functions of b2Vec2 that simply return new “cloned” instances of the b2Vec2 with certain transformations or maths applied to them. These functions, called repeatedly, are quite inefficient as they not only are a wasteful function call but also an instantiation. We *could* simply instantiate our own new b2Vec2 instances and apply the simple math functions ourselves, which would save us a function call. Potentially a better solution though, would be to make the function call itself less wasteful. If the authors (or we) choose to create an object pooling scheme that allows them to generate a limited number of b2Vec2 instances and recycle them it would justify keeping the function around. At times like those, it’s completely up to your own judgment and what risks you’re willing to live with. In the case of Box2D, the library is overly encapsulated in a number of ways that hamper performance. More frustratingly, there are several function calls that happen repeatedly where the function body has nothing inside other than a //TODO. While I’m sure in the future there is something to be done in those stub functions, in the meaintime those function calls should be commented. An empty function call iterated for every single “body” in the simulation, 30 times every second can quickly deteriorate your performance. Do you ever wonder how much reality there is in those contrived 100,000 iteration for-loops? This is one of those cases where it actually happens.

And there you have a very basic rundown of what a refactoring is. These changes seem small, but they add up very quickly and the more of them you implement in performance-critical areas the more juice you get from your code. They can be combined with microoptimizations, such as more efficient for-loop declarations, factoring out the use of b2Math convenience functions such as min/max, or the simple vector arithmetic functions. Another place for improvement is to use array-literal style instantiations where a pre-determined array length is unimportant. Truly though, I got my biggest gains from eliminating needless instantiation and allowing direct property access instead of using the get functions for properties that have no actual mutations applied to them in the process. I’d say that one of the biggest potential roads for improvement would be to implement object pools for commonly instantiated trump objects that do not persist, and that serve little value other than as fodder for calculation.

In this particular refactoring you’ll note that I’m tearing down certain OOP constructs for the sake of performance. This should not be taken to mean that well-designed systems and Object Orientation is by default a hog. It’s in general better to start with a system that is well-designed and possibly slower and to selectively break encapsulation to gain boosts than to start with a mess and try to organize it later.

Tags: , , , ,

4 Comments