Tales from the Smart Client

John Gossman's observations on Avalon development

  • Silverlight: the art of subsetting

    I am an architect of WPF, both the full desktop version and the subset that provides the UI framework for Silverlight.  This subsetting exercise raises challenges I've not run into before when designing software.  Simply put, how do we keep the framework small without completely changing the programming model?  Keeping it small is relatively easy:  just take out classes and APIs.  You of course lose functionality in the process, but almost always when designing software you start out with many features and then must prune back in order to ship.  Subsetting also makes it hard for users to move substantial bodies of code from the big system to the small system.  Not only features, but individual APIs that we decided to cut "because they weren't critical" are naturally called all the time in the application code, and there is no replacement.  Worse, it is easy to get confused reading books about desktop .NET or looking at blog articles about XAML and not be able to tell what is in Silverlight and what isn't.  There was a simple question in the Silverlight forums this morning about how to set the Name of a Framework element.  The user had seen code that simply said fe.Name="foo" and it wasn't working for him.  Well, that code works on desktop WPF and it is highly unlikely the place he saw the example noted that this wasn't available in Silverlight.

    To keep somewhat sane, we choose to design around making it possible to port code *from* Silverlight *to* the desktop.  As long as the Silverlight APIs are a subset (with expected differences for application model) of the desktop ones, code should all just compile and run when moved over.  But this is where it gets trickier than we originally imagined.

     With the full desktop frameworks there are usually several ways of doing the same thing.  In Silverlight we eliminate many of these variations in the search for the smallest possible code and surface area.  However, what happens when we eliminate a path that is a best practice for a particular scenario?  For example, there are a number of complex APIs in desktop WPF that are only used if you want the best possible performance...usually with some tradeoff in flexibility.  The System.Windows.Media.Drawing class and its descendents are examples.  These allow you draw shapes into a single visual and render more efficiently.  Not strictly necessary, but they make for much better performance in some scenarios.  These scenarios are still possible in Silverlight, but if you now port your code to desktop, you will be well served to rewrite a big chunk of code. 

    The above example is a bit of a special case, and I don't feel bad about it at all.  A much greater struggle that hits a very large number of users is how we do Control templating.  All of the APIs we use in Silverlight's Beta1 control templating model exist in desktop WPF, and yet the templates are completely different.  By subsetting the APIs we ended up changing not just the best practices, but the essential model. 

    Let me be clear.  If you write your own custom control in Silverlight, using the Silverlight model, you can create a template for it and take your custom control and its template and run them on desktop WPF.  The problem is, we do not provide Triggers in Silverlight and many of the properties (IsMouseOver, IsPressed) that Triggers depend on to work.  There are technical reasons Triggers were hard to add in Silverlight 2 (though we will add them in the future), but even greater was the test and development cost of adding all the additional properties necessary to make them work.  So, we decided to make the controls have to "trigger" the state changes themselves in code.  All this code works on desktop WPF.  But existing controls, written for the desktop, don't contain the code to "trigger" the state changes, so the control templates don't do anything when applied (they create their children, but don't react to events).

    I should add at this point, that we also think there are some advantages for designers and tools of the Silverlight templating model.  The Parts and States model (which is just a pattern using existing APIs) adds structure to a template that makes it easier to understand.  And in the future, when we add Triggers into Silverlight they are completely compatible with the Parts and States model...providing the best of the two models.  Finally, the Beta1 Parts and States model is going to be considerably enhanced in the near future in ways that really show off the advantages and simplify the coding. 

    Sorry for the tease about future stuff (watch this space), but this posting is about the essential subsetting problem, and the control templating model has been our greatest struggle in this area.  I hope this helps provide insights into the Silverlight programming model and some of the decisions we've made, which may seem arbitrary and patternless otherwise.

  • Finding memory leaks

    I spent a quite a bit of time late in Blend tracking down memory leaks.  Jossef has written the article I wish I had read then:  http://blogs.msdn.com/jgoldb/archive/2008/02/04/finding-memory-leaks-in-wpf-based-applications.aspx
  • More on MVC and M-V-VM

    There have been a raft of interesting articles and discussions of MVC patterns and WPF.  A practical introduction is here:

    http://www.codeproject.com/KB/WPF/MVCtoUnitTestinWPF.aspx

    I love the article, but must add I have always struggled with RoutedCommands and CommandBindings.  I think the APIs are too complex for what they do, but more importantly I prefer to route commands through my application model, not through WPF's element tree.  The Blend architecture contains a creature called CommandManager which makes sure commands get to their destination, and we tend to Databind directly to properties of type ICommand (I love ICommand, just not its implementation in RoutedCommand).

    Meanwhile, Dr. WPF aptly summarizes the controversy:  http://www.drwpf.com/blog/Home/tabid/36/EntryID/27/Default.aspx

    Basically, I have found everyone agrees you should separate your Model and your View, but the details of the rest depend on technology and personal preference. 

  • New role

    We recently shipped Expression Blend, the product I had been working on for the last few years.  I love the product and the team, but it was the longest project I've ever worked on, and I was offered a great new opportunity:  to work on WPF, both the desktop version we shipped with Vista and the new Silverlight component.  The work isn't that vastly different.  From many years working with the WPF team, I know most people very well already.  I go to many of the same meetings, just sit on a different side of the room.  But of course there are still those vast areas, little things about the new team, of which I am completely ignorant.  Which is what I wanted:  to learn a lot of new stuff.  Be careful what you ask for, as they say.
  • The source for information about WPF performance

    Kiran just posted the definitive stuff on WPF perf.  Get it here
  • More on basic performance

    C# and the .NET frameworks allow you to be incredibly productive and write elegant, maintainable code.  However, some of these productivity features can just make you productive at writing slow code.  “Sparkle” has an internal DOM for working with XAML.  We have an XPath-like data structure that lets us make references in that DOM.  This path data structure looks a little like a list:  it is basically an array of steps, though the operations are quite different so it the OM looks nothing like a list.  However, when doing performance analysis I discovered we were allocating and freeing huge numbers of these arrays of steps.  I traced it to an implementation that looked something like this:

     

     

          class Path

          {

                private Step[] steps;

     

                public Path(params Step[] steps)

                      : this((ICollection)steps)

                {

                }

     

                public Path(ICollection steps)

                {

                      this.steps = new Step[steps.Count];

                      int i = 0;

                      foreach (Step step in steps)

                      {

                            this.steps[i++] = step;

                      }

                }

     

                public Path Append(Step step)

                {

                      List<Step> newSteps = new List<Step>(this.steps);

                      newSteps.Add(step);

                      return new Path(newSteps);

                }

          }

     

    Very commonly we initialized a Path that contained only a single step and then appended new steps.  Path is immutable, so you always allocate new ones, as the Append method demonstrates.

     

    Seems like nice simple code.  What could possible go wrong?  Well consider an example like this:

     

                      Step step1 = new Step();

                      Step step2 = new Step();

                      step1.StepName = "FirstStep";

                      step2.StepName = "SecondStep";

     

                      Path path1 = new Path(step1);

                      Path path2 = path1.Append(step2);

     

    Just how much memory do we allocate in this case?

    It seems like we should be allocating 2 Steps, 2 Paths and 2 Step[]s, since that is what we want at the end. 

     

    But really we allocate 2 Steps, 2 Paths, 6 Step[]s, 2 ArraySZEnumerators, a List<Step> and a List<Step> enumerator.  Most of these are short-lived, but in large scenarios they were adding up to megabytes, trashing the caches and making the garbage collector work overtime.  There are three problems: 

     

    1)        the params array is being automatically constructed for us by the compiler and then thrown away (need it be pointed out that a single item array is more expensive than the single item it contains?)

     

    2)        By casting to ICollection the compiler has lost the specific type, so it must call GetEnumerator in order to make the foreach work in the second ctor.  Even if you’ve implemented your Enumerator correctly as a struct, since it is going through the ICollection interface that returns a general type, it must box it. 

     

    3)        The Append was allocating an additional List, then the Path ctor was allocating and copying the contents.

     

    All these “features” made the original code easy to write and understand, but slower in a non-obvious way. 

     

    Here’s the rewritten, more efficient Path:

     

          class Path

          {

                private Step[] steps;

     

                public Path(Step step)

                {

                      this.steps = new Step[1];

                      this.steps[0] = step;

                }

     

                public Path(Step step1, Step step2)

                {

                      this.steps = new Step[2];

                      this.steps[0] = step1;

                      this.steps[1] = step2;

                }

     

                private Path(Step[] steps)

                {

                      this.steps = steps;

                }

     

     

                public Path Append(Step step)

                {

                      Step[] newSteps = new Step[this.steps.Length + 1];

                      for (int i = 0; i < this.steps.Length; i++)

                      {

                            newSteps[i] = this.steps[i];

                      }

                      newSteps[newSteps.Length - 1] = step;

                      return new Path(newSteps);

                }

          }

     

    Basically the first two constructors handle the most common use cases in our codebase.  I additionally have a couple more constructors that handle conversion from other internal data structures we use a lot.  Append now works by allocating an array, adding to it and handing what we just allocated over to the Path ctor instead of making another copy.  The same example as before allocates less than half as much memory, makes fewer copies and is noticeably faster in our actual scenarios.

     

    Don’t get my advice wrong.  Don’t stop using the collection classes in .NET.  My second version of Path is trickier and more likely to have bugs of the off-by-one type.  However, if you find yourself allocating a lot of memory, take a look at things like these as potential culprits.

  • Stories from the Perf lab: Part I

    During the past year, we’ve found and fixed a lot of perf issues in Expression and WPF.  I’d like to relate a few of them, not so much because you’ll have the exact same problems, but that the pattern of finding and resolving the bugs may be helpful experiences.  To start:

     

    Expression uses a tree data structure to keep track of the XAML source code.  Each node in the tree has a property called Children that returns a List of all its child nodes. For leaf nodes this collection was always empty, but even so a new collection object was always allocated. In some scenarios, this resulted in megabytes of allocations.  To fix this, we implemented a singleton EmptyList class.  Leaf nodes always return the same instance of this collection, removing all the allocation costs. 

    There are two implementation patterns here.  The simplest is to have a static EmptyList, like this:

     

    public static List<Node> EmptyList = new List<Node>();

     

    and just always return that.  This will get you most of the performance you want.  We went a little further and implemented a whole class, giving us greater correctness and even slightly better perf.  Here for example is the implementation of the ICollection members on EmptyList:

     

                public void Add(T item)

                {

                      throw new NotSupportedException();

                }

     

                public void Clear()

                {

                }

     

                public bool Contains(T item)

                {

                      return false;

                }

     

                public void CopyTo(T[] array, int arrayIndex)

                {

                }

     

                public int Count

                {

                      get { return 0; }

                }

     

                public bool IsReadOnly

                {

                      get { return true; }

                }

     

                public bool Remove(T item)

                {

                      return false;

                }

    In general, collection classes are a good place for teams to look at optimization: Expression, WPF and other Microsoft teams have all implemented specialized collections for degenerate cases and other cases where the BCL collections are overly general or expensive.

  • Snoop: An essential tool for debugging and perf work with WPF

    My colleague Pete has it up on his site:  http://www.blois.us/Snoop/

    This developed out of internal tools originally built into Expression that help us inspect our visual tree.  Originally we dumped the tree to a text file, then Pete put some UI on it, then Kenny incorporated the UI into Expression, and since then Pete has raised the stakes repeatedly making it standalone and adding features such as being able to inspect and even set values in the tree, and to be able to set breakpoints on data changes. 

    Yes, you can debug databinding in WPF using Snoop.  Check out the link for more.

    Highly, highly recommended!

  • Profiler as the Debugger of Performance

    I've been talking to a lot of people internally and externally about performance, and have observed something very interesting:  while people run the debugger over their code every day, stepping through the code to see if it works even when they don't have a bug, very few people profile regularly.  If the code crashes, people will usually restart it in a debugger rather than try to figure out what is wrong with the code by inspection.  But if the code is slow, people still hesitate to profile and prefer to make guesses as to what is slow.

    But guessing why something is slow (or fast) rarely works.  Modern software and hardware is too complex, and what is going on in the caches or in a large framework like .NET is often counterintuitive.  This weekend I was agonizing over how to write a new chunk of code the most optimal way...and decided before I proceeded to profile the simplest, least performant option.  It turned out that code was so fast it was hard to find either the CPU time it was taking or the memory it was allocating.  On the flip side, I recently code reviewed a checkin, noticed nothing that made me question its performance and the next day the performance tests showed a major regression.  The coder who wrote the code is another person from our performance virtual team...so between us you'd think we might have noticed it. 

    So here's my rule:  The profiler is the debugger for performance.  If the program crashes, look at it in the debugger.  If it is slow, profile.  I guarantee without this, NOTHING else will make your code perform.

  • UML diagram of Model-View-ViewModel pattern

    Conceptually there isn't much to M-V-VM that can be expressed in UML...but if the GoF authors can use UML to describe Singleton (one object), I guess can make a 4 box UML diagram for M-V-VM. 

  • More on what to use WPF for, and what other graphics technologies you may want to use

    Pablo is one the most senior people on the WPF team and has been a key decision maker in deciding what scenarios WPF would address.

     

    http://www.fernicola.org/loquitor/index.php?/archives/19-When-to-use-WPF-and-when-to-use-other-technologies.html

    I'm going to call out one point in particular, because I see a lot of people, including internally confused about WPF 3D. 

    if you already have domain knowledge in Direct3D (or OpenGL), you should stick with using Direct3D for WPF v1.

    Now, to provide more context on my guidance. We make 3D a lot easier to use in WPF, and by a much larger developer audience than the one that uses 3D today. We also make it usable on the Web (in a xbap), we make it print, we make it remote, and we make it easier to integrate 3D with 2D, UI controls, and Documents (including as part of XAML). But, in using WPF, you give up a bit of the flexibility and direct hardware control that you get with the lower level APIs.  ....

    We did not optimize our architecture in this initial version for twitch games or high end scientific vizualization scenarios, so if you want to tackle something along those lines, give it a try and see if it meets your needs.

  • What level of WPF graphics API should I use?

    I was going to post something about the various levels you can use in WPF for graphics, but Pablo Fernicola beat me to it:  http://www.fernicola.org/loquitor/index.php?/archives/17-WPF-Pick-Your-API-Abstraction.html
  • Why people hate frameworks

    Great post on a discussion thread off Joel On Software.  Lutz pointed it out...hilarious:

    http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12

    I happen to love frameworks, but I understand the sentiment in the posting.

  • CollectionView

    CollectionView is a very interesting construct.  After including it in my ViewModel I meant to quickly blog about the usage...but when I started thinking about it and reading a bit more about it, it becomes even more interesting.  Lots of good blogging.  However, while I think about it, let me quickly make my original point.

    The Model has a simple List.  CollectionView wraps a Collection and adds concepts like selection, sorting and filtering to it and supports the INotify... goodness that data binding wants in order to do a really good job.  So, CollectionView is a construct that wraps a Model class and adapts it for data binding from the UI.  Uh...sounds like a ViewModel!

    WPF does not use the terminology of M-V-VM, but it repeatedly uses the pattern.  CollectionView is a fine class name, but in very strict terms it isn't part of the View!  A CollectionView implies it has UI. 

    If you really want to understand the M-V-VM pattern, there is no better place to start than to study how CollectionView adapts a Collection to be presented in the UI using data binding. 

    Going to visit the in-laws, so blogging will be sporatic for awhile.

  • Advantages and disadvantages of M-V-VM

    I've had several questions about when and why to use M-V-VM versus other approaches.   The obvious purpose is abstraction of the View, reducing the amount of business logic or glue code stuck in code-behind.  All tasty goodness abstractly, but here's another concrete advantage:  the ViewModel is easier to unit test than code-behind or event driven code.  The ViewModel, though it sounds View-ish is really more Model-ish, and that means you can test it without awkward UI automation and interaction.  If you've ever tried to unit test UI code, you know how hard that can be.

    Disadvantages?  For simple UI, M-V-VM can be overkill.  In bigger cases, it can be hard to design the ViewModel up front in order to get the right amount of generality.  Data-binding for all its wonders is declarative and harder to debug than nice imperative stuff where you just set breakpoints (though if you have lots of events running around, it may not be much different). 

    Data-binding performance is quite good, but it does tend to create a lot of general book-keeping data around.  For awhile, we were adding a MultiBinding to every object we created.  Loading a large file this meant 50,000 of them.  In the WPF build we were using they were nearly 2K per...meaning the Bindings were heavier than the objects being bound.  In this particular case I replaced the all the Bindings with a single static callback and saved nearly 100MB...!  Normal UI won't create nearly so many bindings, but the perf is something to keep an eye on.


© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker