Friday, October 10, 2008

Rotating Polymorphic Interface (RPI)




This is just an experiment. Just some fun. I doubt the practicality of such an interface, but then again, I felt compelled to create it. Its CPU intensive and won't look good if you have an older machine (IE 7 looked ok for me on my 1GB/pentium 4), but not great. Other browsers worked wonderfully. If you view it as text, of course it speeds up.

I made it a mooTools 1.2 set of classes and you can have multiple polyhedra on one page in different styles, although for simplicity I am showing one. It starts off life as a base polyhedra class, then can become a cube, a pyramid, or whatever you want! I have made a cube, a pyramid and a diamond shape, but there is no limit as long as you know the coordinates.

The buttons show how you can not only switch between text and images, but also you can morph the classes from one to another.

Aside from the core logic for making the polyhedron, here is what I added/changed:


  • Object itself or a handle can turn the cube. (its not great if you use the object itself as the handle and I have no example of this). The handle I added was the touchpad. You make it turn by clicking and dragging on the touchpad. The yellow spotlight indicates where you left off (it maps to the polyhedra).
  • If you use the ctrl key and move up or down, you can scale the entire shape.
  • Can take images as shown, or plain old text, as it was.
  • I added opacity for a more real effect for the images. (although at the moment in IE there is a small bug where opacity is not working for text).
  • You can mouseover an image for a closer view with some moo fx.
  • Click on an image to see a "details" view that I did not flesh out, its just a wireframe. I would imagine some sort of ajax request here for details. Click the image again to return it.
  • I added a scatter and a reconstruction effect.
  • Different browsers scale the images differently. Looks awesome in firefox, but not great in ie 7. Not sure what to do there.

The following options for the container are:
  • left, top: the usual, corresponds to the cube.
  • handle: the thing to manipulate.
  • polyType : a class that describes the shape.
The following options for the polyhedra are:
  • counterSpin: moves like a pilot joytick or moves naturally.
  • rotateSpeed: correlates a mousemove with a cube turn. Really its supposed to make it faster or slower, but this is so CPU intensive that its hard to notice at low numbers
  • opacity: the starting opacity.
  • spacing: space between the faces.
  • hideCenter : hides the center object if you want.
  • contents : img or text (if not images, you automatically get spans)
  • scale: Creates an arrangement of the polyFaces to the polySize overall. When you change this, you should change the default picture size, if there. Leave it at the default if your not sure
  • height, width: The usual, corresponds to each polyFace.
  • root: where to grab the images. If your showing text, use "".
  • notFound: the image or text to show if a slot is empty (I left a few).
  • data: an array of image names or text. Anything you want.

About other shapes:
This is not limited to regular shapes. Again, provided you know where to place the points, you can show and rotate any shape.

To do: make text color configurable, add example of non-regular shapes, show multiple.

Also:
The algorithm for the cube in 3 dimensions was inspired by: Thiemo Mättig. The "text" version is an emulation of his work, but in this new code. This uses very little of the original code, which serves as more of an inspiration, but that core logic does drive this.

Thanks to my colleague Sualeh for his help on extending the OOP concept.


Friday, October 3, 2008

Huge performance boost during iterations

I've improved my code to the point during this latest round of optimizations, that I can get Safari 3.1 to load 4,356 divs (each with a few attached events, and 5 attributes, including styling) - to load in 657 milliseconds (.15 ms per item)

The status of my system at the time was a 50% memory allocation (of 1 GB), iTunes running, a text editor running, and the one browser. No heavy background services were running (polling).

Give it a try below and help me test! Please send me your results... :) To be fair, try closing down your apps and let ONE browser test at a time, with ONE tab. Don't let other processes get in the way. This will be the cleanest number.

If you get different results than me, or if the documentFragment results are slower, then don't freak out. Although its unlikely that this would happen, remember there is an entire world of variables to consider that make this kind of testing tough to streamline. Thats why I am exposing this and want your help.

FYI: The mouseover event does exist in both tests, and is being allocated, even if the addOption test doesn't mouseover with any visual changes. These samples are both in their own iframes, to ensure no preference.







  • You can perform two kinds of tests with both these boxes.
  1. The first is to put a large number into the box and run each. Then jot the results and refresh. (The first test simulates how a user would feel when a page loads and they have to wait for contents to render.)
  2. The second test is one where you load the boxes with a reasonable number, (say 100), then keep loading over and over again without refreshing. (The second test simulates an interface where a user is tasked with "adding elements" throughout their session. How does the box respond?)

Safari is by far the fastest rendering engine (all rendering engines are not the same! Google Chrome has added elements to their rendering engine, alongside webkit, that make it just slightly slower than Safari in the current 0.2. At least in my tests).

With this test I am creating divs that emulate a select box set of options. The top is my current performance, and the bottom is simply using addOption() to append native options to a select box in the DOM (I've tested this a dozen times in all, 10 times each browser (120 tests), ask me for the complete results and I will send.):

504 items:

Firefox 3 (me) : 198 ms (.4 ms per item avg)
Firefox 3 (native addOption) : 358 ms (.7 ms per item avg)

Opera 9.5 (me) : 177 ms (.4 ms per div)
Opera 9.5 (native addOption) : 136 ms (.3 ms per div avg)

Safari 3.1 (me) : 78 ms (.2 ms per item)
Safari 3.1 (native addOption) : 50 ms (.1 ms per item avg)

Chrome .2 (me) : 203 ms (.4 ms per item)
Chrome .2 (native addOption) : 416 ms (.8 ms per item avg)

Ie7 (me) : 359 ms (.7 ms per item)
Ie7 (native addOption) : 382 ms (.8 ms per item avg)


The key to all of this is when and where. If you write your methods correctly, you can massage where code is invoked, and when to push your performance over the edge.

1. When it comes to large scale iterations, don't use the toolkit DOM methods to create and insert the bulk of your objects.
  • I know, it's crazy. But hear me out. There are pain points somewhere in your application where things slow down when using a toolkit. What I am saying here is that during the page load, or when returning an ajax response of 150 divs (e.g.) - don't use the toolkit to wrap these up just to get special features.
  • You can use the toolkit to iterate, but during that iteration, separate out the methods to help minimize the dirtiness. Inside those methods, get dirty and write plain old javascript for a huge performance boost* (see below to see more of what I mean here).
  • Meaning, don't use the append, create or addevent (bind) methods (see more detail below), but use everything else (createElement, onmouseover = function, setAttribute). You can certainly use these for most of your operations, sure, and that's the point of a toolkit. But not for these heavy iterations!
  • Create your own internal "shadow" array in memory of these objects with its base styling and base events, then, when the DOM has rendered, and for the rest of your application, wrap jQuery (or new Elements $) around your items and proceed as normal to style and add effects - or wait for just the right time to invoke these styles and events.
2. Use a documentFragment when creating elements.
  • documentFragment is your #1 friend. Instead of appending to the DOM at the end of every iteration, append to a documentFragment Element, then lastly, append the documentFragment element to the DOM.
  • This works 3x faster because it is all kept in memory - not in the DOM, where rendering is slow. It is added to the DOM later and without any effort on your part, the documentFragment melts away.
  • This has been around for years and supported by all browsers.
3. Use very short custom attribute names.
  • Except for id, className and title, make your custom attribute names be 2 characters or less. This will shave many milliseconds off your rendering time.
4. Bind Events within Events
  • Did you know that if you really plan, that you only have to bind one event to an object? Yes, the mouseover event! Inside that event, bind the rest of your events conditionally. Why pay that price over and over again during iterations?
5. Add classes and styling carefully. (IE can choke)
  • Here is where things get hairy in IE, but if you must make ie run faster, then do this: Set the className (or "class" in non-ie browsers) by hand using setAttribute. Faster than any toolkits "addclass". Don't even use the item.className = class. Just use setAttribute.
6. For ie, use innerText, not createTextNode
  • to inject text. I saved 30-80 ms per item doing this
7. Use Native events like onmouseover = function()
  • ...not toolkit addEvents or bind. Except for the little trick I mentioned above, don't bind or addEvent() during heavy iterations.
*What I mean by dirty: I mean writing plain old javascript. And by "inside these methods" I assume that you are using some kind of OOP-like style and have separated your methods too keep them small and to make sure they do one thing well. That being the case, I assume that during an iteration, you have one method for just attaching events. Its in here that you "get dirty" and write plain old javascript.

Also...
  • Aside from the two types of tests, you need to test both types of boxes. One uses documentFragment technique, and one uses addOption by comparison.
  • Note: if you get the "browser / script is running too error" let me know. The documentFragment test works differently in different browsers, but the addOption test can't handle large numbers! Another testament to using documentFragment.
  • Note: if you want to see how I did the tests, please grab the js file in the source. If you want an explanation of what I did, please comment and I will give much more detail (probably not necessary for this).
If you want to see examples I will definitely share!