CPU and memory usage increases when read about 10k lines from txt file c# WPF - TagMerge
4CPU and memory usage increases when read about 10k lines from txt file c# WPFCPU and memory usage increases when read about 10k lines from txt file c# WPF

CPU and memory usage increases when read about 10k lines from txt file c# WPF

Asked 5 months ago
1
4 answers

You could use a HashSet beside the collection that keeps unique elements. This will make the algorithm O(n) instead of O(n²).

https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.hashset-1?view=net-6.0

Another solution is store variable lineNumber, which points to the latest line observed in the log file, that is only if the log file is appended over.

Make sure the Probe object overrides the methods you use for comparison: ToString, GetHashCode, Equals.

Source: link

1

Since you didn't post the implementation of your file parser, we can't review it and help to improve it. Depending on the amount of data changes, maintaining a pointer to the last position in the stream could be sufficient.

In your current implementation the current bottleneck is clearly the UI.

Handling a big item count is not an issue - if you make use of the UI virtualization feature that controls like the ListView offer by default.
UI virtualization is a pattern where the items control (its Panel to be precise) controls the item container generation. The idea is to render only the visible items instead of all.
This means instad of your 10K item containers only e.g., 20 are rendered at the time.

Nonetheless, populating an ItemsControl with e.g., 10K items at once will impact the performance during the initialization of this control. Although UI virtualization is enabled, the control has to register the items and measure its layout, because for example the ScrollViewer needs to know its scroll range. After this initialization routine the UI virtualization will take effect.

When using an INotifyCollectionChanged implementation as data source, the refresh time is optimzed, as the CollectionChanged event usually reports only the new/changed elements - this is how it is designed to be.

Apparently, you have decided to create a custom ObservableCollection. That's fine. But you must raise the CollectionChanged event properly.
For some reason you have decided to report the NotifyCollectionChangedEventArgs.Action as NotifyCollectionChangedAction.Reset.
If you would have consulted the docs to find the proper NotifyCollectionChangedAction value, you would have learned that Reset is the most expensive action to choose:

"Reset: The contents of the collection changed dramatically"

This means, the changed items (position and count) couldn't be tracked. Therefore, the binding target e.g., the ListView will re-initialize itself by clearing its Items property and then read the complete ItemsSource property again - all 10+K items.

In your case this means, the ListView will read all 10+K items from scratch everytime a new range of lines arrives or a change is reported i.e. CollectionChanged is raised by your RangeEnabledObservableCollection.Update method - although you may have only added a single item...

I suggest the following improvements:

  • eliminate the sorting. I wonder what you sort when you read from a logfile. In case you read blocks in parallel, then sort the blocks and not the lines (this will reduce the sorted element count significantly).
  • configure the NotifyCollectionChangedEventArgs object properly. This will fix the current performance issue regarding the rendering.
  • use an internal HashSet to store the Probe items. This improves the lookup time significantly (for your performed uniqueness check before inserting new items).
  • to ensure data integrity, you must also override the ObservableCollection.InsertItem method and check for uniqueness there also.
  • to maintain the suggested lookup HashSet, you must also override the InsertItem, SetItem, RemoveItem and ClearItems methods of ObservableCollection.
  • use BindingOperations.EnableCollectionSynchronization instead of the Dispatcher. Aside from the obvious synchronization features, BindigOperations will also take care to not overload the UI with new changes by applying a throttling algorithm.
  • when using the Dispatcher, use the modern Dispatcher.InvokeAsync method instead of the outdated BeginInvoke (which is a remainder of the pre TPL era, .NET <4.0 - the member name's prefix "Begin..." indicates this).
  • the generic type parameter for the RangeEnabledObservableCollection is useless. It doesn't make the collection generic as the type is already hardcoded.
  • consider to remove the deferred change notification you have implemented. Currently, Update is directly called after InsertRange. If this is the only use case, then simplify your RangeEnabledObservableCollection implementation by calling ObservableCollection.ClearItems and ObservableCollection.InsertItem directly from within the InsertRange method. Those methods will also raise the CollectionChanged event properly for you.
  • ObservableCollection also implements INotifyPropertyChanged. So at least raise the PropertyChanged event for the Count and the indexer property.

RangeEnabledObservableCollection.cs

public class RangeEnabledObservableCollection : ObservableCollection<Probe>
{
  private List<object> NewItems { get; }
  private List<object> RemovedItems { get; }
  private HashSet<object> InternalItems { get; }
  private bool IsDirty { get; set; }

  public RangeEnabledObservableCollection()
  {
    this.RemovedItems = new List<object>();
    this.NewItems = new List<object>();

    // Optionally store strings if this is reasonable
    // (because you were originally calling Probe.ToString for comparison)
    this.InternalItems = new HashSet<object>();
  }

  public void InsertRangeDeferred(IEnumerable<Probe> items)
  {
    // Items.Count can never be < 0. The original condition is useless.
    if (!items.Any())
    {
      // If the intention is to clear the collection:
      this.RemovedItems.AddRange(this.Items.Cast<object>());

      // Is this really intended?
      this.Items.Clear();
      this.InternalItems.Clear();

      SetPendingState();
      return;
    }

    bool hasChanges = false;
    foreach (Probe item in items)
    {
      if (this.InternalItems.Contains(item))
      {
        continue;
      }

      this.Items.Add(item);
      this.InternalItems.Add(item);
      hasChanges = true;
    }

    if (hasChanges)
    {
      SetPendingState();
    }
  }

  // This method basically updates the layout
  // i.e. notifies the CollectionChanged listeners.
  // Items are already added to the collection,
  // the moment InsertRange is called. I assume this behavior is intentional.
  public void UpdateLayout()
  {
    CheckReentrancy();
    if (!this.IsDirty)
    {
      return;
    }

    var changeAction = this.NewItems.Any() 
      ? NotifyCollectionChangedAction.Add 
      : NotifyCollectionChangedAction.Remove;
    var changedItems = changeAction == NotifyCollectionChangedAction.Add 
      ? this.NewItems 
      : this.RemovedItems;

    OnPropertyChanged(nameof(this.Count));
    OnPropertyChanged("Item[]");
    OnCollectionChanged(changeAction, changedItems);

    ClearPendingState();
  }

  protected override void InsertItem(int index, Probe item)
  {
    CheckReentrancy();
    if (this.InternalItems.Contains(item))
    {
      return;
    }

    base.InsertItem(index, item);
    this.InternalItems.Add(item);

    OnPropertyChanged(nameof(this.Count));
    OnPropertyChanged("Item[]");
    OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
  }

  protected override void SetItem(int index, Probe item)
  {
    CheckReentrancy();
    if (this.InternalItems.Contains(item))
    {
      return;
    }

    Probe oldItem = this[index];
    base.SetItem(index, item);
    this.InternalItems.Remove(oldItem);
    this.InternalItems.Add(item);

    OnPropertyChanged("Item[]");
    OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldItem, item, index);
  }

  protected override void RemoveItem(int index)
  {
    CheckReentrancy();
    Probe removedItem = this[index];

    base.RemoveItem(index);
    this.InternalItems.Remove(removedItem);

    OnPropertyChanged(nameof(this.Count));
    OnPropertyChanged("Item[]");
    OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
  }

  protected override void ClearItems()
  {
    CheckReentrancy();
    base.ClearItems();
    this.InternalItems.Clear();

    OnPropertyChanged(nameof(this.Count));
    OnPropertyChanged("Item[]");
    OnCollectionReset();
  }

  private void SetPendingState()
  {
    this.IsDirty = true;
  }

  private void ClearPendingState()
  {
    this.IsDirty = false;
    this.NewItems.Clear();
    this.RemovedItems.Clear();
  }

  private void OnPropertyChanged(string propertyName)
  {
    OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
  }

  private void OnCollectionReset()
  {
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
  }

  private void OnCollectionChanged(NotifyCollectionChangedAction changeAction, IList changedItems)
  {

    OnCollectionChanged(new NotifyCollectionChangedEventArgs(changeAction, changedItems));
  }

  private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
  {
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
  }

  private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index)
  {
    OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
  }
}

Append new lines

private void ReloadProbeList()         
{
  probeCollection.InsertRangeDeferred(ProjectManager.Instance.CurrentProject.WPR.ProbeList);
  probeCollection.UpdateLayout();
}

Remarks

If the deferred notification is not required (splitting up inserting a range of Probe items and raising the event) then remove the UpdateLayout, SetPendingState and ClearPendingState methods from the example. In InsertRange simply don't reference the internal Items collection. Rather call ClearItems and InsertItem instead. Those protected members of ObservableCollection will automatically raise the proper CollectionChanged and PropertyChanged events for operation. Your posted code does not justify the added complexity by implementing such a deferring notification behavior. Instead it introduces a bug potential. This behavior is completely controlled by the client of the collection. If somebody forgets to call the Update method the observers will never get notified. At least consider to rename the API, for example BeginInsertRange and EndInsertRange.

Source: link

0

I ended up using telerik's RadGridView that has some of the GUI features I need and it comes with virtualization enabled.

Source: link

0

I ran resnet-18 soak test on v0.5.3. The attached picture shows memory usage is stable.
#!/usr/bin/env bash
while true; do
    ab -q -c 10 -n 1000000 -k -p examples/image_classifier/kitten.jpg -T application/jpg http://127.0.0.1:8080/predictions/resnet-18
done
Regarding frontend memory, Torchserve frontend is a Java based frontend. Usually Java application memory usage is wave style due to GC. You can set the following parameters in config.properties.
vmargs=-Xmx128m -XX:-UseLargePages -XX:+UseG1GC -XX:MaxMetaspaceSize=32M -XX:MaxDirectMemorySize=10m -XX:+ExitOnOutOfMemoryError
Prepare model files To shorten the path for easy use, I created the following folders in my workspace directory:
.
├── model-store
│   └── resnet-18.mar
├── request.sh
├── resnet
│   ├── config.properties
│   ├── index_to_name.json
│   ├── kitten.jpg
│   ├── model.py
│   └── resnet18-f37072fd.pth
Inside resnet folder, the model file and others are copied from serve/examples/image_classifier .
# in /my_workspace/, clone torchserve
git clone https://github.com/pytorch/serve.git
# copy examples
mkdir model_store
mkdir resnet
cp ./serve/examples/image_classifier/resnet_18/model.py resnet/model.py
cp ./serve/examples/image_classifier/index_to_name.json resnet/index_to_name.json
cp ./serve/examples/image_classifier/kitten.jpg resnet/kitten.jpg
wget https://download.pytorch.org/models/resnet18-f37072fd.pth -P ./resnet/resnet18-f37072fd.pth
Archive Mar file
torch-model-archiver --model-name resnet-18 \
--export-path ./model-store \
--version 1 \
--model-file resnet/model.py  \
--serialized-file resnet/resnet18-f37072fd.pth \
--handler image_classifier \
--extra-files resnet/index_to_name.json -f

Source: link

Recent Questions on c#

    Programming Languages