Архив Ноябрь 2010

Stirling engine на соленоиде

Этот пост совсем не про флекс :)Stirling engine

Дви́гатель Сти́рлинга — тепловая машина, в которой жидкое или газообразное рабочее тело движется в замкнутом объёме, разновидность двигателя внешнего сгорания. Основан на периодическом нагреве и охлаждении рабочего тела с извлечением энергии из возникающего при этом изменения объёма рабочего тела. Может работать не только от сжигания топлива, но и от любого источника тепла. (wiki)

Просмотрев на ютьюбе десяток видео (например это) я наткнулся на вариант, где вместо тепла, цилиндра и воздуха используется обычный электромагнит — соленоид (или даже два). Воспользовавшись останками железного конструктора, магнитофона «Весна», китайского кассетного плеера и парой скрепок, я сделал свой работающий двигатель.

Несмотря на то, что это совсем не двигатель Стирлинга, вещица забавная, если будет время сделаю еще одну с магитами помощнее.

Реклама

, ,

Оставить комментарий

Имплементация WeakReference

Часто возникает необходимость хранить ссылку на объект, не препятствуя при этом сборке этого объекта сборщиком мусора. Для этого уже есть примеры, но тем не менее :). Рассмотрим сначала самый важный класс — WeakReferenceHolder (непосредственно имплементация WeakReference):

package
{
    import flash.utils.Dictionary;

    public class WeakReferenceHolder
    {
        private var profiledObject:ProfiledObject; //stores object reference info
        private var ref:Dictionary = new Dictionary(true);//weak keys enabled

        public function WeakReferenceHolder(reference:Object = null, description:String = null)
        {
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description);
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function set(reference:Object, description:String = null):void
        {
            clear();
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description ? description : "");
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function clear():void
        {
            if (this.reference)
            {
                for (var reference:Object in ref)
                {
                    delete ref[reference];
                }
                trace("Free reference " + profiledObject.toString());
                profiledObject = null;
            }
        }

        public function get():Object
        {
            if(reference)
            {
                return reference;
            }
            else if(profiledObject != null)
            {
                trace("GC collected: " + profiledObject.toString());
                profiledObject = null;
            }
            return null;
        }

        private function get reference():Object
        {
            var result:Object;
            for (var reference:Object in ref)
            {
                result = reference;
            }
            return result;
        }
    }
}

Идея вобщем-то довольна проста: использовать встроенную возможность flash.utils.Dictionary — weakKeys («If the only reference to an object is in the specified Dictionary object, the key is eligible for garbage collection and is removed from the table when the object is collected.»). Дополнительная возможность — логирование информации об объекте, а также интеграция(в будущем) с профайлером памяти). Для этого понадобятся два дополнительных класса: ProfiledObject — value-объект, и ProfileObjectFactory — конструктор объектов типа ProfiledObject.
ProfiledObject:

package
{
    public class ProfiledObject
    {
        public var name:String;
        public var size:Number;
        public var currentSize:Number;
        public var watchStack:Object;
        public var clazz:String;
        public var superClazz:String;
        public var date:String;

        public var description:Object;

        public function ProfiledObject(name:String, size:Number, allocationStack:Object, clazz:String, superClazz:String)
        {
            this.name = name;
            this.size = size;
            this.watchStack = allocationStack;
            this.clazz = clazz;
            this.superClazz = superClazz;
        }

        public function toString():String
        {
            return String(date) + "\t" + String(clazz) + "\t" + String(clazz) + "\t"  + String(description) + "\t" + String(size);
        }
    }
}

ProfileObjectFactory:

package
{
    import flash.sampler.getSize;
    import flash.utils.getQualifiedClassName;
    import flash.utils.getQualifiedSuperclassName;

    import mx.collections.ArrayCollection;
    import mx.formatters.DateFormatter;
    import mx.utils.UIDUtil;

    public class ProfileObjectFactory
    {
        private static var dateFormatter:DateFormatter = new DateFormatter();

        private static var _instance:ProfileObjectFactory;

        public function ProfileObjectFactory()
        {
        }

        public static function get instance():ProfileObjectFactory
        {
            if (!_instance)
            {
                dateFormatter.formatString = "JJ:NN:SS";
                _instance = new ProfileObjectFactory();
            }
            return _instance;
        }

        public function create(obj:Object, description:String = ""):ProfiledObject
        {
            var profiledObject:ProfiledObject = new ProfiledObject(null, 0, getStack(), null, null);
            if (obj != null)
            {
                profiledObject.name = UIDUtil.createUID();
                profiledObject.description = (description && description.length > 0 ? description : "") + " " + obj.toString();
                profiledObject.size = getSize(obj);
                profiledObject.clazz = getQualifiedClassName(obj);
                profiledObject.superClazz = getQualifiedSuperclassName(obj);
                var date:Date = new Date();
                profiledObject.date = dateFormatter.format(date) + "." + date.getMilliseconds();
                return profiledObject;
            }
            else
            {
                return null;
            }
        }

        private function getStack():Object
        {
            var result:ArrayCollection = new ArrayCollection();
            try
            {
                throw new Error("Allocation trace:");
            }
            catch(e:Error)
            {
                var stack:String = e.getStackTrace();
                if (stack != null && stack.length > 0)
                {
                    result = new ArrayCollection(escape(stack).split("\n"));
                }
            }

            var obj:Object = {};
            var k:int = 1;
            for (var i:int = 0; i  3)
                {
                    obj[addLeadingZero(k)] = result.getItemAt(i);
                    k++;
                }
            }
            return obj;
        }

        public function addLeadingZero(n:Number):String
        {
            var out:String = String(n);

            if (n > -1 && n < 10)
            {
                out = "0" + out;
            }

            return out;
        }

        private function escape(str:String):String
        {
            var result:String = "";
            var instancePattern1:RegExp = /(\\)/gmi;
            result = str.replace(instancePattern1, "/");
            return result;
        }
    }
}

Использовать можно примерно так:

var control:WeakReferenceHolder = new WeakReferenceHolder(object, object ? "ServiceFrontend: " + object.toString() : null);
trace(control.get());

Пока времени больше нет, но материала еще много, продолжим тему чуть позже.

, , ,

2 комментария

Профайлеры: Flex Builder 3 vs Fash Builder 4

Adobe Flash Builder 4До недавнего времени я пользовался только профайлером из Flex Builder 3. Для большого приложения ему нехватает производительности, приходиться тратить много времени, чтобы прокликать нужный функционал.

И вот скачал триальную 60-дневную версию Flash Builder 4. И что же вы думаете? Разочарование. В принципе функционал один и тот же, даже в чем-то хуже, чем у предшественника. Связано это с казалось бы благой целью — исключить из списка циклические ссылки, те что в третьей версии заставляли ходить по кругу. Проблема заключается в том, что компоненты имеющие циклические ссылки собираются гораздо хуже, и ничего, впрочем, не мешает эти ссылки обнулить, ускорив в разы сборку мусора.

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

Работает новый профайлер медленне существенно, и потребляет больше памяти (словил пару раз OutOfMemory). Больше никаких достоинств замечено не было.
Таким образом, имеет смысл пользоваться профайлером из Flex Builder 3, если нет других объективных причин этого не делать.

, , , ,

Оставить комментарий

Правильно выбрасываем мусор: Tree и TreeItemRenderer

TreeОснованные на ListBase компоненты фреймворка имеют много проблем и по моему опыту с трудом освобождают память, возможно для этого нужно выполнение какого-то хитрого условия внутри сборщика мусора, но мне такого видеть не доводилось. Профайлер обычно показывал кучу ссылок на рендереры, промежуточные объекты и прочее (все это оставалось в памяти), и не было очевидно сможет ли компонент быть утилизирован или нет. Решено было очистить все, что можно, и вот что получилось.

Рассмотрим оптимизированные для сборщика мусора DisposableTree и, соответственно, DisposableItemRenderer (см. IDisposable). Я выкладываю исходники прямо в блог, чтобы можно было быстро ознакомиться.

DisposableTree:

package gc
{
    import flash.display.DisplayObject;
    import flash.display.Shape;
    import flash.display.Sprite;

    import mx.containers.Canvas;
    import mx.controls.Tree;
    import mx.controls.listClasses.ListBaseContentHolder;
    import mx.core.FlexSprite;
    import mx.core.UIComponent;
    import mx.core.mx_internal;
    import mx.skins.halo.HaloBorder;

    /**
     * Tree optimized for garbage collection.
     */
    public class DisposableTree extends Tree implements IDisposable
    {
        protected var disposed:Boolean;

        public function DisposableTree()
        {
            super();
            this.itemRenderer = new DisposableItemRenderer(); //use optimized renderer
        }

        public function dispose():void
        {
            if (disposed)
                return;
            if (listContent)
            {
                if (listContent.visibleData) //clear dynamic object
                {
                    for (var prop0:String in listContent.visibleData)
                    {
                        clearRenderer(listContent.visibleData[prop0]);
                        delete listContent.visibleData[prop0];
                    }
                }
                if (listContent.listItems) //clear array of itemRenderers
                {
                    while (listContent.listItems.length > 0)
                    {
                        clearRenderer(listContent.listItems.pop());
                    }
                }
                if (listContent.rowInfo) //clear array of ListRowInfo objects
                {
                    while (listContent.rowInfo.length > 0)
                    {
                        listContent.rowInfo.pop();
                    }
                }

                if (freeItemRenderers) //clear array of itemRenderers
                {
                    while (freeItemRenderers.length > 0)
                    {
                        var renderer0:Object = freeItemRenderers.pop();
                        clearRenderer(renderer0);
                    }
                }

                if (reservedItemRenderers) //clear array of itemRenderers
                {
                    for (var prop1:String in reservedItemRenderers)
                    {
                        clearRenderer(reservedItemRenderers[prop1]);
                        delete reservedItemRenderers[prop1];
                    }
                }

                if (unconstrainedRenderers) //clear array of itemRenderers
                {
                    for (var renderer1:Object in unconstrainedRenderers)
                    {
                        clearRenderer(renderer1);
                        delete unconstrainedRenderers[renderer1];
                    }
                }

                listContent.selectionLayer = new FlexSprite(); //clear selectionLayer property with empty FlexSprite
                listContent.mx_internal::_parent = null; //clear evil mx_internal::_parent references
                listContent.styleName = null; //styleName can be String or any Object so it must be reset
                listContent = new ListBaseContentHolder(null); //listContent must be also reset as DisplayObject reference
            }

            //clear dynamic object
            for (var prop2:String in selectionIndicators)
            {
                delete selectionIndicators[prop2];
            }

            //clear renderer factories from Dictionary with strong references
            for (var factory0:Object in factoryMap)
            {
                clearRenderer(factory0);
                delete factoryMap[factory0];
            }

            //clear renderers from Dictionary with strong references
            for (var renderer2:Object in measuringObjects)
            {
                clearRenderer(renderer2);
                delete measuringObjects[renderer2];
            }

            //clear renderers from dynamic object
            for (var renderer3:String in rowMap)
            {
                clearRenderer(rowMap[renderer3]);
                delete rowMap[renderer3];
            }

            highlightIndicator = null; //reset DisplayObject reference
            caretItemRenderer = null; //reset DisplayObject reference

            //clear border reference
            if (border)
            {
                if (border.parent == this)
                    this.removeChild(DisplayObject(border));
                //assume HaloBorder is used, change it in other case
                HaloBorder(border).styleName = null;
                border = null;
            }

            this.labelFunction = null;//Function reference MUST be cleared
            this.iconFunction = null;//Function reference MUST be cleared

            this.mx_internal::lastHighlightItemRenderer = null;
            this.mx_internal::lastHighlightItemRendererAtIndices = null;

            if (itemRenderer is IDisposable)
            {
                //clear all renderers created by itemRenderer.newInstance(), assume DisposableItemRenderer is used
                IDisposable(itemRenderer).dispose();
            }

            itemRenderer = null; //clear factory references
            itemEditor = null; //clear factory references, probably DisposableItemEditor must be used
            dataDescriptor = null;

            iterator = null;
            collection = null;
            this.mx_internal::wrappedCollection = null;
            this.mx_internal::collectionIterator = null;

            //reset DisplayObject reference
            if (maskShape)
            {
                if (maskShape.parent == this)
                {
                    removeChild(maskShape);
                }
                maskShape = new Shape();
            }
            //reset DisplayObject reference
            if (selectionLayer)
            {
                if (selectionLayer.parent == this)
                {
                    removeChild(selectionLayer);
                }
                selectionLayer = new Sprite();
            }

            disposed = true;
        }

        public function isDisposed():Boolean
        {
            return disposed;
        }

        protected function clearRenderer(obj:Object):void
        {
            if (obj is UIComponent)
            {
                UIComponent(obj).owner = null; //clear possible parent DisplayObject reference
                UIComponent(obj).mx_internal::_parent = null;//clear evil mx_internal::_parent refrernces
                UIComponent(obj).mx_internal::_document = null;//clear evil mx_internal::_document refrernces
                UIComponent(obj).styleName = null;//styleName can be String or any Object so it must be reset

                if (obj is Canvas && Canvas(obj).mx_internal::contentPane)
                {
                    Canvas(obj).mx_internal::contentPane = null; //clear Canvas internal container reference.
                }

                if (obj is IDisposable && !(IDisposable(obj).isDisposed()))
                {
                    IDisposable(obj).dispose();
                }
            }
        }

        /**
         * Override to prevent callLater errors (fired by method executed using UIComponent.callLater())
         * @private
         */
        protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            if (!disposed)
                super.updateDisplayList(unscaledWidth, unscaledHeight);
        }
    }
}

Как видно, в дереве используется много динамических объектов, словарей с сильными ссылками, массивов, ссылок на дочерние и родительские объекты дисплейлиста, все это ставит сборщик мусора в тупик. Обратите внимание на перекрытый метод updateDisplayList, это сделано для того чтобы избежать ошибок при выполнении методов(в нашем случае это только updateDisplayList, но могут быть и другие) через механизм callLater. Об этих ошибках я может быть расскажу подробнее, в целом ситуация такова, что try/catch в этом случае не помогут (даже где-то есть официальный баг).

DisposableItemRenderer:

package gc
{
    import flash.display.DisplayObject;
    import flash.utils.Dictionary;

    import mx.controls.listClasses.BaseListData;
    import mx.controls.treeClasses.TreeItemRenderer;
    import mx.controls.treeClasses.TreeListData;
    import mx.core.IFactory;
    import mx.core.UIComponent;
    import mx.core.mx_internal;

    /**
     * TreeItemRenderer optimized for garbage collection.
     */
    public class DisposableItemRenderer extends TreeItemRenderer implements IDisposable, IFactory
    {
        /**
         * Created renderer instances, weakKeys=true
         */
        protected var factoryCache:Dictionary = new Dictionary(true);

        /**
         * Created ListData, weakKeys=true
         */
        protected var treeDataCache:Dictionary = new Dictionary(true);

        protected var disposed:Boolean;

        public function dispose():void
        {
            //force destroy private renderer listOwner property
            var treeListData:TreeListData = new TreeListData("", "", null);
            treeListData.disclosureIcon = null;
            treeListData.icon = null;
            this.data = {};
            super.listData = treeListData;

            //clear icon reference
            if (icon)
            {
                clearRenderer(icon);
                if(this.contains(DisplayObject(icon)))
                    removeChild(DisplayObject(icon));
                icon = null;
            }
            //clear icon reference
            if (disclosureIcon)
            {
                clearRenderer(disclosureIcon);
                if(this.contains(DisplayObject(disclosureIcon)))
                    removeChild(DisplayObject(disclosureIcon));
                disclosureIcon = null;
            }
            //clear textfield reference
            if (label != null)
            {
                clearRenderer(label);
                if(this.contains(DisplayObject(label)))
                    removeChild(DisplayObject(label));
                label = null;
            }

            //the only way to get private listOwner clean
            try
            {
                commitProperties();
            }
            catch(e:Error)
            {
                //ignore error
            }

            //clear created renderers
            for (var row:Object in factoryCache)
            {
                clearRenderer(row);
                if (row is IDisposable)
                {
                    IDisposable(row).dispose();
                }
                delete factoryCache[row];
            }

            //clear created ListData objects
            for (var data:Object in treeDataCache)
            {
                TreeListData(data).disclosureIcon = null;//clear icon reference
                TreeListData(data).icon = null;//clear icon reference
                TreeListData(data).item = null;
                TreeListData(data).owner = null;//clear possible parent reference
                delete treeDataCache[data];
            }
            clearRenderer(this);
            disposed = true;
        }

        /**
         * Override to save reference to TreeListData as a weak key in treeDataCache
         * @private
         */
        public override function set listData(value:BaseListData):void
        {
            treeDataCache[TreeListData(value)] = "added";
            super.listData = TreeListData(value);
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function measure():void
        {
            if (!disposed)
            {
                super.measure();
            }
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            if (!disposed)
            {
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }
        }

        /**
         * Override to prevent callLater errors
         */
        protected override function commitProperties():void
        {
            if (!disposed)
            {
                super.commitProperties();
            }
        }

        public function isDisposed():Boolean
        {
            return disposed;
        }

        protected function clearRenderer(obj:Object):void
        {
            if (obj is UIComponent)
            {
                UIComponent(obj).owner = null; //clear possible parent DisplayObject reference
                UIComponent(obj).mx_internal::_parent = null;//clear evil mx_internal::_parent refrernces
                UIComponent(obj).mx_internal::_document = null;//clear evil mx_internal::_document refrernces
                UIComponent(obj).styleName = null;//styleName can be String or any Object so it must be reset

                if (obj is Canvas && Canvas(obj).mx_internal::contentPane)
                {
                    Canvas(obj).mx_internal::contentPane = null; //clear Canvas internal container reference.
                }

                if (obj is IDisposable && !(IDisposable(obj).isDisposed()))
                {
                    IDisposable(obj).dispose();
                }
            }
        }

        /**
         * Creates an instance of some class (determined by the class that
	     * implements IFactory).
         * Also stores reference to created object as a weak key in factoryCache.
         * @return The newly created instance.
         */
        public function newInstance():*
        {
            var treeItemRenderer:DisposableItemRenderer = new DisposableItemRenderer();
            factoryCache[treeItemRenderer] = "added";
            return treeItemRenderer;
        }
    }
}

Такая же ситуация и с рендерером, наибольшую проблему представляет ссылка на дерево — listOwner, ее очищаем фактически через хак. Создается большое количество объектов TreeListData, которые содержат паразитные ссылки. Так же 3 метода надо перекрыть чтобы предотвратить ошибки во время выполнения их через механизм callLater. Используем кэш со слабыми ссылками для того, чтобы очистить все созданные методом newInstance():* объекты.
Когда работа с деревом завершена просто вызываем tree.dispose() и все будет очищено. Подобные вещи придется проделать и с другими классами наследующими ListBase. Проверка профайлером показала, что DisposableTree собирается как надо.

, , , ,

Оставить комментарий