Skip to main content

Grid Graphics

Comments

35 comments

  • Dapfor Team

    Hello Deepak,

    We didn't find any exeptional in the provided code. However you should check the highlighting settings and turn off the fading effect.

    There is other possible way to improve the performance: one of our customers remarked that if the grid is notified by an object that implements INotifyPropertyChanged, and it does not contain a column with the corresponding field, the entire row is redrawn. In few days we'll release a new version of the grid fixing this problem.

    Best regards,

    Dapfor

     

     

     

     

    0
  • Michael Sage
    Thanks for your response. Does the entire row get updated when an event gets raised for an unknown column or does it happen even when valid hidden columns are updated.
    0
  • Dapfor Team

    Hello,

    This happens in both cases and have negative impact on the performance. We'll fix this problem in the next version of the grid.

    Best regards,

    Dapfor

     

    0
  • Michael Sage

    Is there an ETA for when the next version will be available ?

    0
  • Dapfor Team

    Hello,

    It will be available the next week .

     

    Regards,

    Dapfor

     

    0
  • Michael Sage

    Thanks for the Update, I am seeing some slowness in my grid though and I'm not sure the problems listed above are causing slowness on my end. I was hoping that you would be able to help me out.

    Let me give you an overview of the architecture and it would be great if you can tell if there is a better way of doing it. My application basically listens to market data and updates grids.  I have three listeners for market data, each listening to different kinds of data, the data flows into a queue which is serviced by a thread. This thread distributes the data to each of the grids based on the data itself. Each of the forms containing the grid has  a queue and a thread servicing the thread . So once the queue is populated, the thread picks it up and then updates the model which implements the INotifyPropertyChanged Interface.  Currently each grid has about 40 columns , of which 10 are actively updated ( > 10 times a second ), but the rest are sparingly updated. The grids itself have 30 - 40 models(rows) each. I have noticed that when I have two grids up at the same time, there are times when the updates are slow,especially when there are bursts of data. Also, as I increase the number of actively updated columns, the grids get slower. Right now there are about 2000 updates per second on each grid. 

    Can you tell me if there is something different that I should be doing or if there are improvements that I can make.

     

    Thanks, I really appreciate your help. 

    Deepak

    0
  • Dapfor Team

    Hello,

    Working with similar applications we’ve found some non-obvious causes of application slowness.

    1. Thread synchronization (especially with the UI thread) is very expensive. Microsoft provides two synchronization methods:  Control.Invoke / Control.BeginInvoke. The performance of these methods is very poor: about 20 000 calls per second and per application (with 100% of CPU load). The single solution – is to group close updates. We have implemented more efficient dispatcher GuiDispatcher used by the Grid. This dispatcher minimizes number of system calls (in other words SendMessage/PostMessage() methods. You can also use this dispatcher instead of Control.BeginInvoke() calls. The preferable solution is to use single dispatcher per application.
    2. Logging system: Applications in 90 % of cases use the Log4Net library. It’s very powerful but logs information inside the calling thread. This can slow down the application. The best solution is to create a custom appender working in its own low-priority thread and writing data to files or other targets.
    3. Garbage collection: GC can collect time to time unused objects. While the objects collection all application threads are suspended for up to several seconds (generally 50-150 msec). The single solution – create new objects as little as possible. May be reuse frequently created objects if it’s possible.

     

    P.S

    Upcoming release can resolve a part of your problems if some columns don't match to IPropertyChanged identifiers because the current grid can redraw big surfaces and load CPU.

     

    Best regards,

    Dapfor

    0
  • Michael Sage

    Thanks, I will definitely try to incorporate these suggestions. I did notice something odd with the updates though. I have data flowing through to two grids. If I have both grids open at the same time, I see updates are slower, but If I have one of the grids out of view, then the updates are faster. I am updating data for both grids at all times on the same thread, do you see a reason why updates will be slower if the visible area of grids is increased.

    Deepak

    0
  • Dapfor Team

    It's easy to explain. Windows draws graphic primitives in two steps:

    1. Application calls the Control.Invalidate(Rectangle) method one or multiple times. This happens when you call Cell.Invalidate() or while reception INotifyPropertyChanged event. Then Windows cumulates a region of multiple rectangles. No immediate painting happens at this stage.
    2. Sometime later Windows generates a WM_PAINT message with cumulated region. If the window is partially hidden, the region will contain fewer rectangles to redraw.

    Note that the window message queue is a single queue in the application and all messages (WM_TIMER, thread synchronization etc…) are handled in sequence. If there is too much handling in the UI thread, frequency of WM_PAINT messages falls. The minimal interval between two near WM_PAINT messages is about 15 msec. In case of CPU load it can be > 100 – 200 msec. (Idem for the WM_TIMER messages !!!). If the application handles something in the UI thread for long time, the application freezes (i.e. no more WM_PAINT messages happen).

    From the other hand the grid does expensive painting in the WM_PAINT message. While the first grid draws its content, the second one waits for its own WM_PAINT message. So to improve the performance, the main task of grid suppliers is to minimize the drawing surface and minimize the drawing frequency.

     

    Regards,

    Dapfor

    0
  • Michael Sage

    Thanks for your comment. It was definitely helpful. The problem I'm having, is that my application needs to be able to handle more than 3000 updates per second on the grid and the requirements demand that the grid is visible at all times along with all the rows and most columns. It typically contains about 50 rows. I currently have implemented my model using the INotifyPropertyChanged interface. I'm obviously seeing lags at certain points especially when market data traffic is high. What in your opinion is the best way to handle this many updates ? Is there an alternate to implementing the INotifyPropertyChanged that might provide better performance ? 

    0
  • Dapfor Team

    Hello,

    There is no difference how you update grids: (there is an input from non-UI thread with 3000 updates/sec and they should be displayed in grids). INotifyPropertyChanged is a good solution because it can simplify the code and globally reduce the number of searching/foreach operations.

    Where do you loss the performance?

    1. Synchronization on Control.Invoke/Control.BeginInvoke with 20 000 calls max per second and per application (we think this is a one of main performance problems).
    2. Logging system
    3. New object creating/garbage collection
    4. Long operation in the UI thread

    Where do you gain?

    1. Non-visible rows/cells are not painted even Control.Invalidate(Rectangle) is called
    2. When the grid gets a notification it calls Control.Invalidate(). The painting happens later in the WM_PAINT message. If two near updates come for the same cell, it will be repainted only once with the latest value.
    3. Grid supports multiple cell invalidating and painting in a single WM_PAINT message as well as surface clipping.
    4. Load balancing mechanism: the higher CPU load, less often the Grid receives WM_PAINT enabling the application to perform other work.

    Where could you gain the performance?

    1. Use single shared GuiDispatcher between all grids and for other synchronization purposes. It will group synchronization requests and will reduce number of Control.Invoke/Control.BeginInvoke (or similar method) calls. The GuiDispatcher doesn’t use timers to avoid synchronization delays.
    2. Optimize logging system.
    3. Limit creation new objects where possible.
    4. Use the new version of the grid with fixed INotifyPropertyChanged issue.
    5. Ideally the number of threads in the application should be equal to number of CPU cores. Big number of threads == excessive context switches. Few threads == unused CPU resources. ThreadPool can help you optimize number of threads.

     

    Best regards,

    Dapfor

    0
  • Dapfor Team

    Dear Deepak,

    We've released the version 2.9.2. It's available on our site.

     

    Kind regards,

    Dapfor

     

    0
  • Michael Sage

    Thanks for the new release.  After I installed the latest version, I'm unable to use the Grid. It gives me a License expired version error. Can you please look into ?

    Thanks.

    0
  • Michael Sage

    Edit: It actually gives me an evaluation period expired error.

    0
  • Dapfor Team

    Dear Deepak,

    Normally the evaluation period is 30 days since the first installation of the Grid. Nevertheless, we are pleased to extend the evaluation period for 1 month. Please, use following license information to activate the product:

    NetSuite User license; Licensee: [Deepak Manian]; Expires 2013-09-03

    7C7EBF35481318E1-0F3FA70E3B82C400-236BA5FB0578820C-3F57AD3F8297BBAD-AB8263152F788623

     

    To install the license, use the installer Dapfor.LicenseInstaller.exe contained in the package. The information related to the license installation procedure is described here:

    http://doc.dapfor.com/net-suite/html/518488e7-3389-4208-ae8e-ea20ff6efaac.htm

    Best regards,

    Dapfor

     

    0
  • Michael Sage

    I had a question about one your earlier suggestions for improving performance. Currently I'm using the INotifiyPropertyChanged interface for updating the grid. One of your suggestions was to share a single gui dispatcher to group synchronizations. Currently I don't use the gui dispatcher and do not explicitly do a synchronisation with the gui thread. All my updates are done on an non-gui thread. I am assuming the grid will take care of the synchronization. I just want to make sure I'm not missing something here. Is there anything that I should be doing for this synchronization ? Please let me know, an example would be great.  

    Thanks.

    0
  • Dapfor Team

    Hello,

    The basic idea - is to reduce the total number of synchronizations with the graphical thread. If you use your business objects only in the grid, then nothing else to do. If you synchronize threads elsewhere for other controls, the total number of Control.Invoke() calls may be large.

    The following example demonstrates how to create a single dispatcher per application:

    static class Program
    {
        private static IDispatcher _dispatcher;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
           
            using (GuiDispatcher dispatcher = new GuiDispatcher())
            {
                _dispatcher = dispatcher;

                Application.Run(new Form1());
            }
        }
        
        public static IDispatcher Dispatcher
        {
            get { return _dispatcher; }
        }
    }

     

    The next code will demonstrate how to use it:

    //Asynchronous call (like Control.BeginInvoke)
    Program.Dispatcher.Dispatch(new DelegateTask(delegate
                                                 {
                                                     //DO something in the gui thread
                                                 }));

    //Synchronous call (like Control.Invoke)
    Program.Dispatcher.SyncDispatch(new DelegateTask(delegate
                                                 {
                                                     //DO something in the gui thread
                                                 }));

     

    Note: this dispatcher exists for the entire application life cycle instead of usual controls. You can also set this dispatcher in grids' constructors. In this case all grids and other application's code will share the same dispatcher. If in future you create a new more productive dispatcher, you'll have a single line to be modified.

    Best regards,

    Dapfor

     

    0
  • Michael Sage

    Thanks for all your help so far, it has been quite useful. Unfortunately, we are still seeing instances where there is a slight lag with the grid update. It is being used for a high frequency application where every millisecond matters. I would like to know if there is a way to find out how many times the Control.Invalidate method is called in a second and also the number of times the WM_PAINT message is called. We are trying to figure out where our application is getting slowed down and the best way to optimize it. I appreciate all your help.

     

    0
  • Dapfor Team

    Hello,

    To count the number of WM_PAINT messages, you can derive from the Grid and override the OnPaint() virtual method. To count the number of invalidates, you can override the OnInvalidated() method.

    Other possibility is to subscribe for Grid.RowUpdated event and check if the row is in visible bounds of the grid.

    Measure also the time between two near WM_PAINT messages and the number of garbage collections (note, all threads in application are suspended while garbage collection).

    Best regards,

    Dapfor

     

    0
  • Michael Sage

    Hi,

    I have a favor to ask of you. Can you please extend the evaluation period for version 2.9.2 for 30 more days. I know you extended it for us before, but we haven't been able to evaluate it fully. One of the reasons we haven't been able to evaluate is because we have to wait for days when market data is very volatile and there is high traffic before we can run our tests. Also some of our developers have been away on vacation during the past few weeks. We did buy your product last year and  the management would like to fully evaluate the new release before they make a decision on upgrading it. I would really appreciate it if you can extend it for us. Your support during the last few months has been phenomenal and we truly appreciate it.

    Thanks, 

    Deepak

    0
  • Michael Sage

    Thanks for the new license. I am having some problems with it though. I am able to compile the application and run it, it gives me no problems there. But I'm unable to view the control in the designer. I get a license expired error , I have attached a screenshot here. I'm also seeing some weird exceptions with the the grid serialization. The particular exception I get is System.InvalidOperation Exception.  There is an error in the XML document (7,10) . I have attached a file with the xml that I'm using. I believe this may be linked to the license issue also. Please let me know how I can fix this. 

     

    Thanks,

    Deepak




    license has expired.png
    fXML.txt
    0
  • Michael Sage

    Also, my registry keys do show the correct trial license and expiry, but every time I bring up the license installer, it shows me the old license. Don't know if it matters or not.

    0
  • Dapfor Team

    Hello,

    Clean the solution to purge the cache with already compiled license.

    Best regards,

    Dapfor

     

     

    0
  • Dapfor Team

    On the Windows 64bit the license is written in HKEY_LOCAL_MACHINE\SOFTWARE\Dapfor and HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Dapfor keys. Please check it.

    Best regards,

    Dapfor

    0
  • Michael Sage

    Hi,

    Is there any way we can bulk update the grid. My grid has a number of cells updating at any given point of time and each time a value is changed a property changed event is called. I understand every time this happens, the grid synchronizes with the gui thread. Is it possible to batch update these synchronizations so the message pump doesn't get overwhelmed with the synchronization messages. Please let me know.

    Thanks.

     

    0
  • Dapfor Team

    Dear Deepak,

    The architecture of the .Net Grid enables bulk updates from non-GUI threads. To do it, you should create and use your own dispatcher. It can be set in the grid's constructor. An example of such dispatcher is provided below:

     

    public sealed class BulkDispatcher : IDispatcher, IDisposable
    {
        private readonly GuiDispatcher _dispatcher = new GuiDispatcher();
        readonly List<ITask> _pendingTasks = new List<ITask>();
        readonly Timer _timer = new Timer();

        public BulkDispatcher()
        {
            _timer.Interval = 50; //50 msec
            _timer.Tick += delegate
            {
                List<ITask> tasks = null;
                lock(_pendingTasks)
                {
                    //Copy all pending tasks to local container
                    if(_pendingTasks.Count > 0)
                    {
                        tasks = new List<ITask>(_pendingTasks);
                        _pendingTasks.Clear();
                    }
                }

                if(tasks != null)
                {
                    foreach (ITask task in tasks)
                    {
                        task.Execute();
                    }
                }
            };

            _timer.Start();
        }

        public void Dispose()
        {
            _timer.Dispose();
        }

        public void Dispatch(ITask task)
        {
            //Add a task to list of pending tasks
            lock (_pendingTasks)
            {
                _pendingTasks.Add(task);
            }
        }

        public void SyncDispatch(ITask task)
        {
            _dispatcher.SyncDispatch(task);
        }

        public bool SynchronizationRequired
        {
            get
            {
                return _dispatcher.SynchronizationRequired;
            }
        }
    };

     

    To enable asynchronous updates in the grid, set  Grid.Threadsafety.AsynchronousUpdates property to true.

    Best regards,

    Dapfor

     

     

    0
  • Michael Sage

    Thanks for the code. I did have some questions about it though. I understand I have to provide the code I would like executed as an object that implements the ITask intererface. So, If I had an object that implements the INotifyPropertyChanged interface, I would wrap the property changed event call in an object that implements the ITask interface and call the Dispatch method above. Is that correct ?

    Another question is how does the SyncDispatch get called ?  It would be great if you can expand on the example a little to show me how I would fire off a set of events for a given databound object. Thanks a lot for your help.

     

    0
  • Michael Sage

     I have some example code here , please tell me if this is correct usage of the Bulk dispatcher, please let me know if this is correct.


    // Data bound object that implements INotifyPropertyChanged
    public class MarketDisplayModel : INotifyPropertyChanged
    {
    protected double _BidPrc;

    [Format(typeof(CustomFormat))]
    public double BidPrc
    {
    get { return _BidPrc; }
    set
    {
    if (_BidPrc == value)
    return;

    _BidPrc = value;
    this.SetBuyIndicators();
    if (PropertyChanged != null)
    {
    PropertyChanged(this, new PropertyChangedEventArgs("BidPrc"));
    }
    }
    }


    protected double _AskPrc;

    [Format(typeof(CustomFormat))]
    public double AskPrc
    {
    get { return _AskPrc; }
    set
    {
    if (_AskPrc == value)
    return;

    _AskPrc = value;
    // this.SetSellIndicators();
    //OnPropertyChanged(MarketDisplayModelProperty.AskPrc);
    this.FireEvent("AskPrc");
    }
    }

    }


    //Class that implements the ITask interface , where the Execute updates the values of the data bound object
    public class SynchronizationTask : ITask
    {
    private Trader.Models.MarketDisplayModel model;
    private halo.BookMsg bookMsg;

    public SynchronizationTask(Trader.Models.MarketDisplayModel pModel, halo.BookMsg bm)
    {
    this.model = pModel;
    this.bookMsg = bm;
    }

    public void Execute()
    {
    this.model.BidPrc = this.bookMsg.bPrice;
    this.model.AskPrc = this.bookMsg.aPrice;
    }
    }


    //Update Code -- pseudo code
    public class Update
    {
    private BulkDispatcher dispatcher = new BulkDispatcher();
    public Update()
    {

    Thread t = new Thread(() = > this.Run));
    t.Start();
    }

    public void Run()
    {
    while(true)
    {
    //Dequeue object and set it.
    BookMsg bm = queue.dequeue();
    MarketDisplayModel model = this.cache["IBM"];
    this.dispatcher.Dispatch(new SynchronizationTask(model,bm);
    }
    }


    }

    0
  • Michael Sage

    I see that with the above example I still do call the property changed multiple times. I should have probably been clearer with my earlier question. I do raise an event every time a value is changed, Is it possible to reduce the number of such event calls. So basically try and bundle up all these event calls into one call for the grid to update. Something similar to grid.BeginUpdate , grid.EndUpdate, but I'm not sure if that is the right example. Please let me know if any such feature exists with the dapfor grid. Thanks.

    0
  • Dapfor Team

    Dear Deepak,

    You are not right! Your objects stay unchanged and will implement only INotifyPropertyChanged interface. Upon notification, the grid uses IDispatcher to synchronize threads. So, all updates coming  from non-UI threads will be synchronized via the dispatcher. ITask objects are created internally to switch thread contexts.

    Look at the stack of calls:

    Calling thread: YourObject.SomeProperty->FireNotification->IDataAccessor->Grid->IDispatcher->new ITask->local queue of IDispatcher

    GUI thread: local queue of IDispatcher->ITask.Execute() ->Grid->Cell->Control.Invalidate(Rectangle)

    GUI thread (some time later): WM_PAINT->Grid drawing routine-> OnPaintRow() ->OnPaintCell() callback

    The most expensive in the process is the thread synchronization. So you can group updates in the local queue of IDispatcher and then call ITask.Execute() which will indirectly call the Control.Invalidate() method. Note, no drawing happens at this stage even if you call Coltrol.Invalidate() multiple times.

    Turn on the Grid.Threadsafety.AsynchronousUpdates property. In this case all updates from INotifyProeprtyChanged interface will be handled via IDispatcher.Dispatch() method. Otherwise - via IDispatcher.SyncDispatch() one. The difference between them is that in the second case the calling thread is blocked while the ITask.Execute() call. I.e while the Control.Invalidate() call.

    Hope this will help you.

    Dapfor

     

     

     

     

     

    0

Please sign in to leave a comment.

Powered by Zendesk