czwartek, 29 maja 2008

Szybkie wektory

Wprowadzenie typoów parametrycznych albo jak kto woli generycznych (z C# 2 czy Java 1.5) miało na celu umożliwienie tworzenia tablic danego typu na poziomie języka programowania. Pewien Japończyk zrobił testy co to daje w kontekście wydajnościowym. Uprzedzam czytelników że ten typ danych ułatwia działania na API do rysowania ale nie przyśpiesza jego działania jeżeli chodzi o wydajność z tradycyjnym sposobem rysowania.
Zmodyfikuję kod do testów ale na początek zamieszczę kod do "okienka" w którym będziemy mogli podziwiać wyniki zamiast tradycyjnego trace(). Kod pochodzi z tej strony.


package com.senocular.utils {
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.Stage;
import flash.display.GradientType;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Matrix;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import flash.text.TextFieldAutoSize;

/**
* Creates a pseudo Output panel in a publish
* swf for displaying trace statements.
* For the output panel to capture trace
* statements, you must use Output.trace()
* and add an instance to the stage:
* stage.addChild(new Output());
*
* Note: You may want to place Output in an
* unnamed package to make it easier to
* trace within your classes without having
* to import com.senocular.utils.Output.
*/
public class Output extends Sprite {
private var output_txt:TextField;
private var titleBar:Sprite;
private static var instance:Output;
private static var autoExpand:Boolean = true;
private static var maxLength:int = 1000;

public function Output(outputHeight:uint = 150){
if (instance && instance.parent){
instance.parent.removeChild(this);
}

instance = this;
addChild(newOutputField(outputHeight));
addChild(newTitleBar());

addEventListener(Event.ADDED, added);
addEventListener(Event.REMOVED, removed);
}

// public methods
public static function trace(str:*):void {
if (!instance) return;
instance.output_txt.appendText(str+"\n");
if (instance.output_txt.length > maxLength) {
instance.output_txt.text = instance.output_txt.text.slice(-maxLength);
}
instance.output_txt.scrollV = instance.output_txt.maxScrollV;
if (autoExpand && !instance.output_txt.visible) instance.toggleCollapse();
}
public static function clear():void {
if (!instance) return;
instance.output_txt.text = "";
}

private function newOutputField(outputHeight:uint):TextField {
output_txt = new TextField();
output_txt.type = TextFieldType.INPUT;
output_txt.border = true;
output_txt.borderColor = 0;
output_txt.background = true;
output_txt.backgroundColor = 0xFFFFFF;
output_txt.height = outputHeight;
var format:TextFormat = output_txt.getTextFormat();
format.font = "_typewriter";
output_txt.setTextFormat(format);
output_txt.defaultTextFormat = format;
return output_txt;
}
private function newTitleBar():Sprite {
var barGraphics:Shape = new Shape();
barGraphics.name = "bar";
var colors:Array = new Array(0xE0E0F0, 0xB0C0D0, 0xE0E0F0);
var alphas:Array = new Array(1, 1, 1);
var ratios:Array = new Array(0, 50, 255);
var gradientMatrix:Matrix = new Matrix();
gradientMatrix.createGradientBox(18, 18, Math.PI/2, 0, 0);
barGraphics.graphics.lineStyle(0);
barGraphics.graphics.beginGradientFill(GradientType.LINEAR, colors, alphas, ratios, gradientMatrix);
barGraphics.graphics.drawRect(0, 0, 18, 18);

var barLabel:TextField = new TextField();
barLabel.autoSize = TextFieldAutoSize.LEFT;
barLabel.selectable = false;
barLabel.text = "Output";
var format:TextFormat = barLabel.getTextFormat();
format.font = “_sans”;
barLabel.setTextFormat(format);

titleBar = new Sprite();
titleBar.addChild(barGraphics);
titleBar.addChild(barLabel);
return titleBar;
}

// Event handlers
private function added(evt:Event):void {
stage.addEventListener(Event.RESIZE, fitToStage);
titleBar.addEventListener(MouseEvent.CLICK, toggleCollapse);
fitToStage();
toggleCollapse();
}
private function removed(evt:Event):void {
stage.removeEventListener(Event.RESIZE, fitToStage);
titleBar.removeEventListener(MouseEvent.CLICK, toggleCollapse);
}
private function toggleCollapse(evt:Event = null):void {
if (!instance) return;
output_txt.visible = !output_txt.visible;
fitToStage(evt);
}
private function fitToStage(evt:Event = null):void {
if (!stage) return;
output_txt.width = stage.stageWidth;
output_txt.y = stage.stageHeight - output_txt.height;
titleBar.y = (output_txt.visible) ? output_txt.y - titleBar.height : stage.stageHeight - titleBar.height;
titleBar.getChildByName("bar").width = stage.stageWidth;
}
}
}


Mając taki kod możemy przeprowadzić testy. Test Japończyka był prosty: chciał połączyć dane z tablicy za pomocą metody concat. Aby się upewnić jak szybko za działa łączenie na wartościach trzeba było przyjąć pewne założenia: operujemy na wartościach liczb 16, bo w ten sposób zapisane są kolory. Ponieważ Pixel Bender operuje na wartościach liczb przedstawiających kolory więc naturalną rzeczą będzie obsługa danych na kolorach. Oto kod operujący na typie Array.

package
{
import flash.display.Sprite;
import flash.utils.*;
import com.senocular.utils.Output;

[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcArray extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;

public function CalcArray():void
{
stage.addChild(new Output());
var array:Array = [];
for (var h:int = 0; h < SIZE; h++)
{
for (var w:int = 0; w < SIZE; w++)
{
array.push(0xFF000000);
}
}
this.startTime=getTimer();
var i:int;
var l:int;
for (i = 0, l = array.length; i < l; i++)
{
array[i] += 0x00FF0000;
}
var da:Array = array.concat();
this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');
l = SIZE * SIZE - 1;
Output.trace("Array => 0x" + da[l].toString(16).toUpperCase());
}
}
}


Może łączenie tablic nie jest takie interesujące ale wyraźnie pokazuje na korzyść stosowania danych parametrycznych. Oto kod z wektorami w pliku CalcVector.as :

package
{
import flash.display.Sprite;
import flash.utils.*;
import com.senocular.utils.Output;
[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcVector extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;


public function CalcVector():void
{
stage.addChild(new Output());
var vector:Vector.< uint > = new Vector.();
for (var h:int = 0; h < SIZE; h++)
{
for (var w:int = 0; w < SIZE; w++)
{
vector.push(0xFF000000);
}
}
this.startTime=getTimer();
var i:int;
var l:int;
for (i = 0, l = vector.length; i < l; i++)
{
vector[i] += 0x00FF0000;
}
var dv:Vector. = Vector.< uint >(vector.concat());
this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');
l = SIZE * SIZE - 1;
Output.trace("Vector => 0x" + dv[l].toString(16).toUpperCase());
}
}
}


Na dość wolnym komputerze wynik dla Array wyniósł mi 512 ms, a dla Vector 98 ms.

Skoro mam wolny komputer to wykonywanie Pixel Bender może spowolnić m.in dlatego że nie mam wielordzeniowego procesora. A jak ktoś ma nowszy procesor to nawet pójdzie szybciej. Ale zobaczmy kod Japończyka dla pliku Calc.pbk.

<languageVersion : 1.0;>
kernel ColorFilter
<
namespace : "muta";
vendor : "muta";
version : 1;
description : "This filter Fill color.";
>
{
parameter pixel4 color
<
defaultValue : pixel4(0, 0, 0, 1);
description : "color";
>;
input image4 src;
output pixel4 dst;
void evaluatePixel()
{
pixel4 tmp = sampleNearest(src, outCoord());
tmp.rgba += color;
dst = tmp;
}
}

A kod w pliku ShaderCalc.as jest taki

package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Shader;
import flash.display.Sprite;
import flash.filters.ShaderFilter;
import flash.geom.Point;
import flash.utils.ByteArray;
import flash.utils.*;
import com.senocular.utils.Output;

[SWF(width=1024, height=1024, backgroundColor=0xFFFFFF, frameRate=30)]
public class CalcShader extends Sprite
{
private static const SIZE:uint = 1024;
private var startTime:Number;
private var endTime:Number;

[Embed(source="calc.pbj", mimeType="application/octet-stream")]
private var shaderData:Class;

public function CalcShader():void
{
stage.addChild(new Output());

var vector:Vector. = new Vector.();

for (var h:int = 0; h < int =" 0;" bitmapdata =" new" bitmapdata =" new" point =" new" shader =" new" shaderfilter =" new" starttime="getTimer();" value =" [1,"> = dst.getVector(dst.rect);

this.endTime=getTimer();
Output.trace(this.endTime-this.startTime +' ms');

l = SIZE * SIZE - 1;

Output.trace("Shader + Vector => 0x" + ds[l].toString(16).toUpperCase());
addChild(new Bitmap(dst));
}
}
}

Dla mnie wynik wyniósł w działaniu powyższego kodu 160 ms. co i tak na słabym komuterze daje całkiem niezły wynik.

Brak komentarzy: