[Pharo-users] Morphic is super slow

Dimitris Chloupis kilon.alios at gmail.com
Sat Jan 16 07:43:25 EST 2016


Hello Ben with your help and some more digging around I managed to solve my
issues and I am now also able to update the taskbar button moprh without
any major hit to my cpu. Pharo is now at 6-7% of one core which is not bad
at all.

So what you mention does not seem to affect my performance. However there
is still the problem of ChronoManager taking over a second to open its gui
.

The problem with the loading of image is purely on how PNG are loaded.

For example if I profile

ChronosManager open

I get 1241 for my the initialisation of the class out of this 800ms are
wasted just loading the pngs with this summary

48.8% {794ms} Form class>>fromFileNamed: | 47.9% {779ms} Form
class>>fromBinaryStream: | 47.9% {779ms} ImageReadWriter
class>>formFromStream: | 47.9% {779ms} PNGReadWriter>>nextImage | 47.6%
{774ms} PNGReadWriter>>processIDATChunk | 47.2% {768ms}
PNGReadWriter>>processNonInterlaced | 32.9% {536ms}
PNGReadWriter>>filterScanline:count: | |28.0% {456ms}
PNGReadWriter>>filterPaeth: | | |15.4% {250ms}
PNGReadWriter>>paethPredictLeft:above:aboveLeft: | | |12.6% {205ms}
primitives

Reading the full report is clear that a major hit is how pharo tries to
compile the png to a form.

full report

1624 tallies, 1626 msec. **Tree** -------------------------------- Process:
other processes -------------------------------- 15.1% {246ms}
InputEventFetcher>>eventLoop 15.1% {246ms} InputEventFetcher>>waitForInput
8.6% {139ms} SoundPlayer class>>playLoop 8.3% {134ms} primitives
-------------------------------- Process: (40s) Morphic UI Process: nil
-------------------------------- 76.3% {1241ms} ChronosManager
class(Behavior)>>new 76.3% {1241ms} ChronosManager>>initialize 76.3%
{1241ms} ChrGUIMorph class(Behavior)>>new 76.3% {1241ms}
ChrGUIMorph>>initialize 48.8% {794ms} ChrGUIMorph>>secondsTimerForm |48.8%
{794ms} Form class>>fromFileNamed: | 47.9% {779ms} Form
class>>fromBinaryStream: | 47.9% {779ms} ImageReadWriter
class>>formFromStream: | 47.9% {779ms} PNGReadWriter>>nextImage | 47.6%
{774ms} PNGReadWriter>>processIDATChunk | 47.2% {768ms}
PNGReadWriter>>processNonInterlaced | 32.9% {536ms}
PNGReadWriter>>filterScanline:count: | |28.0% {456ms}
PNGReadWriter>>filterPaeth: | | |15.4% {250ms}
PNGReadWriter>>paethPredictLeft:above:aboveLeft: | | |12.6% {205ms}
primitives | |2.8% {46ms} PNGReadWriter>>filterHorizontal: | |1.6% {26ms}
PNGReadWriter>>filterVertical: | 7.0% {113ms}
ZLibReadStream(InflateStream)>>next:into:startingAt: | |7.0% {113ms}
ZLibReadStream(InflateStream)>>next | | 7.0% {113ms}
ZLibReadStream(InflateStream)>>pastEndRead | | 4.7% {76ms}
ZLibReadStream(InflateStream)>>next | | 1.9% {31ms}
ZLibReadStream(InflateStream)>>bitPosition | 3.0% {49ms} primitives | 2.6%
{43ms} ZLibReadStream(InflateStream)>>next | |2.6% {43ms}
ZLibReadStream(InflateStream)>>pastEndRead | | 1.7% {28ms}
ZLibReadStream(InflateStream)>>next | 1.2% {19ms}
PNGReadWriter>>copyPixelsRGBA: 21.1% {342ms} ChrStopwatchPanelM
class(Behavior)>>new |21.1% {342ms} ChrStopwatchPanelM>>initialize | 3.0%
{48ms} ChrStopwatchSettingsPNG>>stopwatchSecondaryPanelIcon | |2.3% {37ms}
Form class>>fromBinaryStream: | | 2.3% {37ms} ImageReadWriter
class>>formFromStream: | | 2.3% {37ms} PNGReadWriter>>nextImage | | 2.3%
{37ms} PNGReadWriter>>processIDATChunk | | 2.3% {37ms}
PNGReadWriter>>processNonInterlaced | | 2.0% {33ms}
PNGReadWriter>>filterScanline:count: | | 1.9% {31ms}
PNGReadWriter>>filterPaeth: | | 1.1% {18ms} primitives | 2.8% {46ms}
ChrStopwatchPanelM>>prepareStopwatchButton | |2.7% {44ms} ChrSwitchButtonM
class>>createWithAction: | | 2.7% {44ms} ChrSwitchButtonM
class(Behavior)>>new | | 2.7% {44ms} ChrSwitchButtonM>>initialize | | 2.6%
{42ms} ChronosManager class>>defaultFont | | 2.6% {42ms} ChronosManager
class>>defaultFontPointSize: | | 2.3% {38ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.3% {38ms}
SelectVisitor class>>breadthFirst:select: | | 2.3% {38ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.3% {38ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.3% {38ms}
BreadthFirstGuide>>show: | | 2.3% {38ms} BreadthFirstGuide>>visitNextEntry:
| | 2.3% {38ms} FileReference>>entries | | 2.3% {38ms}
FileSystem>>entriesAt: | | 2.3% {38ms} FileSystem>>entriesAt:do: | | 2.3%
{38ms} FileSystem>>entriesAt:ifAbsent:do: | | 2.0% {33ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 1.8% {29ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | | 1.6% {26ms} primitives |
2.8% {45ms} ChrStopwatchPanelM>>prepareHelpLabel | |2.8% {45ms}
ChronosManager class>>defaultFontPointSize: | | 2.3% {38ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.3% {38ms}
SelectVisitor class>>breadthFirst:select: | | 2.3% {38ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.3% {38ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.3% {38ms}
BreadthFirstGuide>>show: | | 2.3% {38ms} BreadthFirstGuide>>visitNextEntry:
| | 2.3% {38ms} FileReference>>entries | | 2.3% {38ms}
FileSystem>>entriesAt: | | 2.3% {38ms} FileSystem>>entriesAt:do: | | 2.3%
{38ms} FileSystem>>entriesAt:ifAbsent:do: | | 2.0% {32ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 2.0% {32ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | | 1.1% {18ms} primitives |
2.8% {45ms} ChrStopwatchPanelM>>prepareBrakelimitLabel | |2.8% {45ms}
ChronosManager class>>defaultFontPointSize: | | 2.5% {40ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.5% {40ms}
SelectVisitor class>>breadthFirst:select: | | 2.5% {40ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.5% {40ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.5% {40ms}
BreadthFirstGuide>>show: | | 2.5% {40ms} BreadthFirstGuide>>visitNextEntry:
| | 2.5% {40ms} FileReference>>entries | | 2.5% {40ms}
FileSystem>>entriesAt: | | 2.5% {40ms} FileSystem>>entriesAt:do: | | 2.5%
{40ms} FileSystem>>entriesAt:ifAbsent:do: | | 2.3% {38ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 1.9% {31ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | | 1.3% {21ms} primitives |
2.6% {42ms} ChrStopwatchPanelM>>prepareTimelimitLabel | |2.6% {42ms}
ChronosManager class>>defaultFontPointSize: | | 2.3% {37ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.3% {37ms}
SelectVisitor class>>breadthFirst:select: | | 2.3% {37ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.3% {37ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.3% {37ms}
BreadthFirstGuide>>show: | | 2.3% {37ms} BreadthFirstGuide>>visitNextEntry:
| | 2.3% {37ms} FileReference>>entries | | 2.3% {37ms}
FileSystem>>entriesAt: | | 2.3% {37ms} FileSystem>>entriesAt:do: | | 2.3%
{37ms} FileSystem>>entriesAt:ifAbsent:do: | | 1.5% {25ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 1.1% {18ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | 2.6% {42ms}
ChrStopwatchPanelM>>prepareTimerButton | |2.4% {39ms} ChrSwitchButtonM
class>>createWithAction: | | 2.4% {39ms} ChrSwitchButtonM
class(Behavior)>>new | | 2.4% {39ms} ChrSwitchButtonM>>initialize | | 2.2%
{36ms} ChronosManager class>>defaultFont | | 2.2% {36ms} ChronosManager
class>>defaultFontPointSize: | | 2.2% {36ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.2% {36ms}
SelectVisitor class>>breadthFirst:select: | | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.2% {36ms}
BreadthFirstGuide>>show: | | 2.2% {36ms} BreadthFirstGuide>>visitNextEntry:
| | 2.2% {36ms} FileReference>>entries | | 2.2% {36ms}
FileSystem>>entriesAt: | | 2.2% {36ms} FileSystem>>entriesAt:do: | | 2.2%
{36ms} FileSystem>>entriesAt:ifAbsent:do: | | 1.5% {24ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 1.5% {24ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | 2.2% {36ms}
ChrStopwatchPanelM>>prepareDailygoalLabel | |2.2% {36ms} ChronosManager
class>>defaultFontPointSize: | | 2.2% {36ms}
FileReference(AbstractFileReference)>>allDirectories | | 2.2% {36ms}
SelectVisitor class>>breadthFirst:select: | | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | | 2.2% {36ms}
BreadthFirstGuide>>show: | | 2.2% {36ms} BreadthFirstGuide>>visitNextEntry:
| | 2.0% {32ms} FileReference>>entries | | 2.0% {32ms}
FileSystem>>entriesAt: | | 2.0% {32ms} FileSystem>>entriesAt:do: | | 2.0%
{32ms} FileSystem>>entriesAt:ifAbsent:do: | | 1.8% {30ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | | 1.7% {28ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | | 1.2% {19ms} primitives |
2.2% {36ms} ChrStopwatchPanelM>>prepareStopwatchLabel | 2.2% {36ms}
ChronosManager class>>defaultFontPointSize: | 2.2% {36ms}
FileReference(AbstractFileReference)>>allDirectories | 2.2% {36ms}
SelectVisitor class>>breadthFirst:select: | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | 2.2% {36ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | 2.2% {36ms}
BreadthFirstGuide>>show: | 2.2% {36ms} BreadthFirstGuide>>visitNextEntry: |
2.2% {36ms} FileReference>>entries | 2.2% {36ms} FileSystem>>entriesAt: |
2.2% {36ms} FileSystem>>entriesAt:do: | 2.2% {36ms}
FileSystem>>entriesAt:ifAbsent:do: | 2.1% {34ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | 1.9% {31ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | 1.0% {17ms} primitives 4.7%
{77ms} ChronosManager class>>defaultFontPointSize: |4.4% {71ms}
FileReference(AbstractFileReference)>>allDirectories | 4.4% {71ms}
SelectVisitor class>>breadthFirst:select: | 4.4% {71ms}
SelectVisitor(AbstractEnumerationVisitor)>>breadthFirst: | 4.4% {71ms}
SelectVisitor(AbstractEnumerationVisitor)>>visit:with: | 4.4% {71ms}
BreadthFirstGuide>>show: | 4.4% {71ms} BreadthFirstGuide>>visitNextEntry: |
4.4% {71ms} FileReference>>entries | 4.4% {71ms} FileSystem>>entriesAt: |
4.4% {71ms} FileSystem>>entriesAt:do: | 4.4% {71ms}
FileSystem>>entriesAt:ifAbsent:do: | 3.5% {57ms}
MacStore(FileSystemStore)>>directoryAt:ifAbsent:nodesDo: | 3.1% {51ms}
MacStore(DiskStore)>>basicEntry:path:nodesDo: | 2.0% {32ms} primitives 1.4%
{22ms} ChrGUIMorph>>centralGUIform 1.4% {22ms} Form class>>fromFileNamed:
1.4% {22ms} Form class>>fromBinaryStream: 1.4% {22ms} ImageReadWriter
class>>formFromStream: 1.4% {22ms} PNGReadWriter>>nextImage 1.2% {20ms}
PNGReadWriter>>processIDATChunk 1.2% {20ms}
PNGReadWriter>>processNonInterlaced **Leaves** 16.4% {267ms}
PNGReadWriter>>paethPredictLeft:above:aboveLeft: 15.1% {246ms}
InputEventFetcher>>waitForInput 14.3% {232ms} PNGReadWriter>>filterPaeth:
9.9% {160ms} MacStore(DiskStore)>>basicEntry:path:nodesDo: 8.3% {134ms}
SoundPlayer class>>playLoop 6.4% {104ms}
ZLibReadStream(InflateStream)>>next 3.8% {61ms}
Array(SequenceableCollection)>>= 3.1% {51ms}
PNGReadWriter>>processNonInterlaced 3.1% {51ms}
PNGReadWriter>>filterHorizontal: 2.8% {46ms}
ZLibReadStream(InflateStream)>>bitPosition 2.2% {35ms}
PNGReadWriter>>filterVertical: 1.7% {27ms} FreeTypeFace
class>>fromFile:index: 1.6% {26ms} False>>ifTrue:ifFalse: **Memory** old
+20,113,604 bytes young +74,448 bytes used +20,188,052 bytes free +601,016
bytes **GCs** full 5 totalling 294ms (18.0% uptime), avg 59.0ms incr 34
totalling 13ms (1.0% uptime), avg 0.0ms tenures 4 (avg 8 GCs/tenure) root
table 0 overflows

On Sat, Jan 16, 2016 at 2:31 PM Ben Coman <btc at openinworld.com> wrote:

> On Sat, Jan 16, 2016 at 4:03 PM, Dimitris Chloupis
> <kilon.alios at gmail.com> wrote:
> > Hello Ben :)
> >
> > First thanks for investing 1 hour of your precious time to look through
> my
> > code. I really appreciate it . If there is one thing I love about pharo
> is
> > the super amazing community.
> >
> > Yes definetly my fault as always :D
> >
> > I did not know about this profiling, I used it for the creating of my
> Morphs
> > but it did not occur to me to google like you did and go with profiling
> all
> > processes.
> >
> > A huge thank you to latsaben and the rest of you guys. I downgraded down
> to
> > Pharo 4 and again I agree 5 is the unstable version anyway but I loved
> using
> > the new tools and could not wait till April for the Pharo 5 release :)
> >
> > The problem is battling without documentation is very hard and I have
> spent
> > at least 10 hours trying to understand how taskbar works and I still
> fail.
> >
> > Your code solves my problem , apart from 1. Loading those pngs is
> sloowwwww,
> > one would expect that accesing the files themselves to get the binary
> data
> > is what is slow for pharo but no, thats is fast enough. What is slow ,
> > because I profiled this is converting the binary data to  form is
> consuming
> > a ton of time. The result is that to load a GUI with only 1 mb of pngs
> takes
> > 1 second.
>
> What is the practical effect of this that is most concerning?
> 1. The time taken loading it the first time after installing from
> Catalog Browser ?
> 2. The time taken to start it *each* time from World > Chronos
> Manager, if you open/close it a lot?
> 3. Distributing Chronos Manager as a packaged application?
> each time you want to open it
>
> Not sure if the following will suit your needs, but in the definition
> of ChrGUIMorph try moving 'secondsTimerFormCollection' from
> instanceVariableNames: to classVariableNames:
>
> then test by opening/closing the app a few times.  After taking a
> while opening the first time, subsequent openings seem to be faster.
>
> >
> > Also with your solution I lose the ability to update my taskbar icon, by
> the
> > way the non optimised code I was using is a direct copy from SystemWindow
> > class, which means that class should be optimised too.
>
> So consider that solution just a problem-isolation step on the way ;).
> I had another look because I was intrigued that even though you used
> LRUCache for the /icons/ variable, the ifAbsent: block in
> #logotinyIcon was being executed anyway.  I thought that must be cache
> size too small and items bumped out, so as a test I replace LRUCache
> with Dictionary, but the the ifAbsent: block was *still* getting
> executed (note, this is at steady-state, not startup).  So I thought
> /icons/ must be getting reset. Investigating by putting the following
> at the top of #initializeIcons...
>
>    Transcript crShow: thisContext printString ,
>            ' <-- ', thisContext sender printString,
>            ' <-- ', thisContext sender sender printString.
>
> So now this executes every time I open the World menu.  And after
> ChronosManager is opened, the Transcript scroll rapidly. So everywhere
> you do something like this...
>        icon: ChrStopwatchSettingsPNG new logotinyIcon
> where logotinyIcon returns an object, the object created by #new is
> thrown away.  Worse, #new invokes #initialize and hence
> #initializeIcons wipes out your cache every cycle.
>
> Try making /icons/ a class variable and moving methods like
> logotinyIcon to the class side, so instead you do...
>      icon: ChrStopwatchSettingsPNG logotinyIcon
>
> Also move #initializeIcons to class side and add a class side
> #initialize method to call it.  Note that will only be executed when
> the class is first loaded, so you will probably need to execute it
> manually in your image.
>
> One thing I'm interested in the choice of LRUCache over Dictionary for
> the cache?  Did you find Dictionary was taking too much space?
>
> I didn't try implementing the above yet, but I guessing you'll get the
> performance fix while maintaining the flexibility you want.
>
> cheers -ben
>
> > Another problem is the scaling of images, really bad with morphic though
> > Athens must be better.
> >
> > Auto completion problems are well knows, sometimes autocompletion does
> not
> > show all methods, sometimes autocompletion shows every character you
> type so
> > for example ChronosManager open will show
> > ChronosManager o
> > ChronosManager op
> > ChronosManager ope
> > ChronosManager open
> >
> > Sometimes it shows methods that do not even belong to the class forcing
> me
> > to use Spotter to find the correct method. Its a a mess.
> >
> > I am not abandoning pharo, I love it even with its flaws. But I try to
> > outsource as much as I can my workflow from pharo to external libraries
> and
> > apps that are way more mature and efficient for what I am trying to do.
> >
> > I always inteded to make Blender work with Pharo, I accomplished that
> now I
> > want to make Unreal engine to work with pharo, so I can make a triangle
> of
> > love, Blender for asset creation, Unreal for real time rendering of 2d
> and
> > 3d graphics , Pharo as the brain of the logic and advanced scripting of
> the
> > other 2. This is was my dream and goal all along. So I will be moving my
> GUI
> > to Unreal that will allow me much more advanced and performance
> orientated
> > features that mix 2d with 3d but I will keeping my main logic in pharo.
> >
> >
> > On Sat, Jan 16, 2016 at 6:35 AM Ben Coman <btc at openinworld.com> wrote:
> >>
> >> On Sat, Jan 16, 2016 at 7:28 AM, Dimitris Chloupis
> >> <kilon.alios at gmail.com> wrote:
> >> > taskbarTask (self valueOfProperty: #noTaskbarTask ifAbsent: [ false ])
> >> > ifTrue: [ ^ nil ]. ^ nil "TaskbarTask morph: self state: self
> >> > taskbarState
> >> > icon: self taskbarIcon label: self taskbarLabel"
> >> >
> >> > The uncommented part was the one that was slowing me down, its a copy
> >> > from
> >> > SystemWindow, on a new image of Pharo consumption drops to 15% but
> still
> >> > have issues with Nautilus etc.
> >> >
> >> > latsabben at Slack also recommended caching which helped also
> >> >
> >> > taskbarTask "myTask := nil." myTask ifNil: [ myTask := TaskbarTask
> >> > morph:
> >> > self state: self taskbarState icon: self taskbarIcon label: self
> >> > taskbarLabel ]. myTask label: self taskbarLabel. ^myTask
> >> >
> >> > Anyway I decided to port my project to C++ and Unreal Engine because I
> >> > have
> >> > many issues with Pharo speed wise
> >>
> >> See my other post, in about an hour I moved your App from 55% cpu
> >> usage on my machine to 7%, only 1% above our 6% idle.   We *do* need
> >> to address that minimum idle, but its at the VM level since in-Image
> >> profiling shows 90% time in ProcessorScheduler class>>idleProcess.  So
> >> to me the Image seems not too bad performance wise
> >>
> >> > and stability wise with Pharo 5.
> >>
> >> Well, you are talking about bleeding edge alpha software.
> >>
> >> > Plus many IDE features I miss like proper auto completion etc.
> >>
> >> You've probably mentioned this somewhere previously, but in another
> >> thread could you leave us with a summary of what is missing from auto
> >> completion.
> >>
> >> > To be fair I tried to make custom gui with python and it was even
> slower
> >> > in
> >> > the past.
> >> >
> >> > So its clear I need a high performance language + API, because I will
> be
> >> > building a very heavy GUI (many more animations)  and I would like
> also
> >> > some
> >> > fast 3d functionality too.
> >>
> >> good luck with it.
> >> cheers -ben
> >>
> >>
> >> >
> >> > On Sat, Jan 16, 2016 at 1:07 AM Sven Van Caekenberghe <sven at stfx.eu>
> >> > wrote:
> >> >>
> >> >>
> >> >> > On 15 Jan 2016, at 23:30, Dimitris Chloupis <kilon.alios at gmail.com
> >
> >> >> > wrote:
> >> >> >
> >> >> > taskbar was the problem, damn pharo gui is a huge pain in the hat.
> >> >>
> >> >> How so ?
> >> >>
> >> >> > On Fri, Jan 15, 2016 at 11:32 PM Dimitris Chloupis
> >> >> > <kilon.alios at gmail.com> wrote:
> >> >> > ITs not the step, I removed the step as I said in my first post.
> >> >> > Still
> >> >> > 30% cpu consumption
> >> >> >
> >> >> > The images are PNGs and RGBA , 8bit
> >> >> >
> >> >> >
> >> >> > On Fri, Jan 15, 2016 at 10:54 PM Hilaire <hilaire at drgeo.eu> wrote:
> >> >> > It depends on what you are doing in a step, but 1s step should not
> >> >> > hurt.
> >> >> > May be the problem is somewhere else.
> >> >> > With DrGeo, I noted Athens is faster to BitBlt with bitmap
> operations
> >> >> > (in my case, only scaling and displaying a From in a DrGeo canvas).
> >> >> > Also, do your bitmaps come with 32 bits depth?
> >> >> >
> >> >> > --
> >> >> > Dr. Geo
> >> >> > http://drgeo.eu
> >> >> >
> >> >> >
> >> >> >
> >> >>
> >> >>
> >> >
> >>
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/attachments/20160116/cf12fc53/attachment.html>


More information about the Pharo-users mailing list