I need to add 950 strings that are 2500 characters in length to a listbox. The method I am using below takes 2.5 seconds and ideally it needs to happen in less then 500ms.
Stopwatch sw = Stopwatch.StartNew();
listBox1.BeginUpdate();
listBox1.Items.AddRange(items.ToArray());
listBox1.EndUpdate();
sw.Stop();
What would be the best way to optimize the insertion time?
Thanks.
One thing you could try is changing this line:
listBox1.Items.AddRange(items.ToArray());
to something like this:
foreach (var item in items)
{
listBox1.Items.Add(item);
}
That way, you do not have the overhead of creating a whole new array (the ToArray() call) before putting the items into your ListBox.
Listbox is dealing with 2500 characters. That is what is slow. All that data, including converting to/from arrays, is peanuts in memory. Hence skipping the ToArray step not making a difference. If your users have to scroll horizontally to see this info, chances are, you're stuck with 'slow.'
If not, consider refactoring a tiny bit. Strategy: only put as many characters - about 100 - as are viewable in a regular width listbox. Full strings are retained behind the scenes.
Make a helper class like so (you can convert the public string to a property if you're that anal;-):
Class TruncatedListItem
Public Content as string
Overrides sub ToString() as string ' Pardon me if this is wrong I always use intellisense
return Mid(Content,1,100)
end sub
end class
Add those items to the listbox. When an item in a listbox isn't of type string, it calls the item's ToString method (which hey, we just tailored to give the listbox a break) and adds that as a string, then the items collection appears as the items you've added. (keep the begin/end update too)
For each each itm as string in Items
dim tli as new TruncatedListItem
tli.Content = itm
listbox.add(tli)
next
When you want to see what the user picked, instead of getting the string like this:
MyString = Ctype(Listbox.SelectedItem,string)
do this
MyString = Ctype(ListBox.SelectedItem,TruncatedListItem).Content
Now I am assumming the user, at some point, still needs to see all 2500 chars before selecting. Unless they are a serious stick in the mud, they should settle for this alternative (In fact there are advantages to scrolling).
When they double click an item, in the handler for double click, show them the full text in a messagebox. You could tell them to do that in a tooltip. For example, in the double-click handler: msgbox Ctype(ListBox.SelectedItem,TruncatedListItem).Content,,"Full Item Text"
Good luck!
Related
I have a program which uses a RichTextBox (which is part of Tab Control) and in the TextChanged event I these variable declarations:
RichTextBox programTextBox = (RichTextBox)tabControl.TabPages[tabControl.SelectedIndex].Controls[0];
int selectStart = programTextBox.SelectionStart;
int programCurrentLine = programTextBox.GetLineFromCharIndex(programTextBox.SelectionStart);
int programCurrentLineIndex = programTextBox.GetFirstCharIndexFromLine(programCurrentLine);
int programCurrentLineLength = programTextBox.Lines[programCurrentLine].Length;
string programCurrentLineText = programTextBox.Lines[programCurrentLine].ToString();
All of them are important in that particular event and I use them multiple times for multiple purposes. However, recalculating every single time slows down my program.
For example, I've noticed that if I open a somewhat large file (with my RichTextBox) and then start pressing e.g. the 'a' button, there is some noticable lag. Deleting every single piece of code except the declaration of these variables does not help at all with the lag, but also deleting the code above, completely solves the problem.
I have two questions: 1) Why do these declarations slow down that much the TextChanged event and 2) What can I do? (Is there a faster way to calculate these variables?)
It's not the variable declarations - it's the code you're using to initialize them. I wouldn't be at all surprised to find that GetLineFromCharIndex and GetFirstCharIndexFromLine were expensive - and currently you're calling the Lines property twice.
You could probably improve matters at least slightly just by removing one of those Lines calls, simply by fetching the line first and then looking at its length:
// No need to call ToString() - Lines is a string[]
string programCurrentLineText = programTextBox.Lines[programCurrentLine];
int programCurrentLineLength = programCurrentLineText.Length;
It's a shame there isn't some way of saying "Get all the information about the position of the given index: its line, first char index from line, and the line itself" in one call :(
If it is not important to recalculate on every key press you might be better off implementing a boolean variable which is set on keypress then has a timer to reset it. For example adding a 1.2 second delay on the event. I am not sure the needs of your app so that time might be a bit much.
I'm writing an app where by i would like to implement an object array which will periodically have a new item added.
i would always like to know which the last object added was and was considering placing that object at position 0. (thus pushing each item down one index to a maximum of 130 items)
this is fairly easy to implement using a List using
items.Insert(0,new item());
items.RemoveAt(130);
which will then push each item down automatically and remove the item at 130 yet this is not so simple with an array
my initial thoughts where somewhere along the line of
for(int i = 129; i>0;i--)
{
items[i] = items[i-1];
}
items[0] = new item();
this then allows me simple access to the latest item (via index[0]), and each preceding item in order of creation (1 -> 129);
now in itself this is fairly simple i was wondering however if there were any other ways of performing this.
EDIT: thanks for the quick replies,
i have performed some testing on this (using 1 million iterations)
it would seem that the queue method is the quickest here but only marginally
then the list
then the array which took some 50% longer to process 1 million items
i think i'll explore the queue Stack option
thanks again;
i would always like to know which the last object added was and was
considering placing that object at position 0. (thus pushing each item
down one index to a maximum of 130 items)
...
now in itself this is fairly simple i was wondering however if there
were any other ways of performing this.
Yes, there is a simpler way of performing this by using an already builded structure which is the Queue (First Input First Output). You mainly need three methods which are:
Enqueue() adds an object to the end of the queue.
Dequeue() returns the object at the beginning of the queue removing it.
Peek() returns the object at the beginning of the queue without removing it.
Your loop is unnecessary, Array.Insert will "shift down" the elements for you.
But if you really want to always add new elements at the beginning, a LinkedList will be better for performance reasons because it doesn't need to shift elements regardless of where you insert an element.
I think that would be about as simple as you can make it. Personally I would make it a generic extension method.
public static void Insert<T>(this T[] array, int position, T item)
{
for ( int i = array.Length-1; i > position; i-- )
array[i] = array[i-1];
array[position] = item;
}
So you can use it like
string[] lolCats = { "ceiling cat", "invisible bike cat", "lime cat" };
lolCats.Insert(0, "monorailcat");
I am currently working on syntax highlighting and code completion project, and making user control based on RichTextBox. I've had some problems adapting to the way RTB works and everything, but I have managed to make simple syntax highlighting.
Simple means that I highlight entire text every time user types a character. It's not supposed to be fast or anything, but it is too slow. Performance issues become visible when I have about 500 chars worth of text, and I do only one pass through the text for each typed character('colorInterval' function gets called about 100 times in one pass).
Performance analysis says the problem is TextRange constructor that takes about 80%+ of the time, and I use it every time I need to color an interval of text:
private void colorInterval(TextPointer start, TextPointer end)
{
TextRange range = new TextRange(start, end);
if(isFunction(range.Text)) colorAsFunction(range);
if(isInQuotes(range.Text)) colorAsQuoted(range);
...
}
So here goes my question:
Am I doing something wrong doing everything this way, or is there a way to boost performance of TextRange, recycle the 'range' object or something like that? What other solutions are there.
The simplest avenue is to (as you suggest) reuse the TextRange object, if it really is the constructor that is taking up most of your time. The TextRange properties Start and End are read only, but there is a public method Select which will update both, taking two TextPointer objects just like the constructor you have been using.
protected TextRange range;
private void colorInterval(TextPointer start, TextPointer end)
{
if (range == null)
range = new TextRange(start, end);
else
range.Select(start, end);
...
}
(N.B. checking for a null reference before deciding whether to initialise the variable isn't as neat as just instantiating a TextRange in the declaration. Unfortunately, TextRange has no public empty constructor and TextPointer no public constructors at all. You could create it with some dummy values in your class constructor to avoid this check.)
Above, I said 'if it really is the constructor'. Obviously, the profiling you've rightfully done has highlighted the constructor, but it could just as easily be a routine common to the constructor and the Select method.
Assuming you don't call colorInterval from more than one thread, I would say this is a better approach than you have currently whatever the time saving, because (I would guess that) colorInterval is being called frequently and constant creation and garbage collection of the subsequent TextRange objects it leaves behind is certainly an inefficiency.
Having made this suggestion, I strongly suggest you move away from the model where you scan the entire document every time you want to react to (for example) a single character change. Assuming you are targetting >= .net 3.5, the RichTextBox provides a TextChanged event that reports a list of TextChange objects from which you can work out the location of (and characters added or removed by) changes.
Naturally, there will be some work here because any change is unlikely to completely encapsulate a highlighted range. The TextRange class has a method for finding the paragraphs in which the start and end of a range can be found, in case that helps. There's probably a case for storing details of each highlighted range so you can quickly check for intersection.
I have a Dictionary the first string, the key's, must never change.. it cant be deleted or anything.. but the value, i keep adding lines, and lines, and lines to the values.. i just create new lines with \r\n or \r .. and im just wondering what would be the easiest way to retain just the last 50 lines. and delete anything over the 50 lines.. im doing this because when i return it i have to put the values through a char array, and go through each letter, and this can be slow if there is too much data. any suggestions?
Guffa's general idea is right - your data structure should reflect what you actually want, which is a list of strings rather than a single string. The concept of "the last 50 lines" is pretty obviously to do with a collection rather than a single string, even if you've originally read it that way.
However, I'd suggest using a LinkedList<T> rather than a List<T>: every time you remove the first element of a List<T>, everything else has to shuffle up. List<T> is great for giving random access and not too bad at adding to the end, but sucks for removing from the start. LinkedList<T> is great at giving you iterator access, adding to / removing from the start, and adding to / removing from the end. It's a better fit. (If you really wanted to go to town you could even write your own fixed-size circular buffer type which encapsulated the logic for you; this would give the best of both worlds, in the situation where you don't want to be able to expand beyond a certain size.)
Regarding your comments to Guffa's answer: it's pretty common to convert input into a form which is more appropriate for processing, then convert it back to the original format for output. The reason why you do it is precisely the "more appropriate" bit. You don't want to have to parse the string for line breaks as part of the "updating the dictionary" action, IMO. In particular, it sounds like you're currently introducing the idea of "lines" where the original text is just being read in as strings. You're effectively creating your own "collection" class backed by a string, by delimiting strings with line breaks. That's inefficient, error-prone, and much harder to manage than using the built-in collections. It's easy to perform the conversion to a line-break-delimited string at the end if you want it, but it sounds like you're doing it way too early.
Instead of concatenating the lines, use a Dictionary<string, List<string>>. When you are about to add a string to the list you can check the count and remove the first string if the list already has 50 strings:
List<string> list;
if (!theDictionary.TryGetValue(key, out list)) {
theDictionary.Add(list = new List<string>());
}
if (list.Count == 50) {
list.RemoveAt(0);
}
list.Add(line);
I have a C# application in which a LOT of information is being added to a Textbox for display to the user. Upon processing of the data, almost immediately, the application becomes very slow and unresponsive. This is how I am currently attempting to handle this:
var saLines = textBox1.Lines;
var saNewLines = saLines.Skip(50);
textBox1.Lines = saNewLines.ToArray();
This code is run from a timer every 100mS. Is there a better way to handle this? I am using Microsoft Visual C# 2008 Express Edition. Thanks.
The simple answer is TextBox.AppendText().
You get much better performance initially.
I tested writing a 500 char message every 20 ms for 2 mins (with BackgroundWorker) and the UI remained responsive and CPU minimal. At some point, of course, it will become unresponsive but it was good enough for my needs.
Try by having in memory a list with the content, and removing the first 50 elements by RemoveRange and then going with ToArray();
Like this :
lst.RemoveRange(0,50);
textBox1.Lines = lst.ToArray();
It should be a lot faster.
I'd say your main problem here is that you are using the TextBox as your primary storage for your text. Everytime you call TextBox.Lines, the string is split on Environment.NewLine.
Try turning it around:
Store the text in a new List<String>(maxLines)
In your AddLine method, check the length of your text buffer and use RemoveRange(0, excessCount)
Update your display TextBox by calling String.Join(Environment.NewLine, textBuffer.ToArray())
That last call is a bit expensive, but it should stop your slowdowns. To get it any faster you'd need to use a statically sized string array and move the references around yourself.
The most efficient way to trim an array is to create a new array of the desired size, then use Array.Copy to copy the desired portion of the old array.
I would recommend that you maintain a List<string> containing all of your lines.
You should use a StringBuilder to build a string containing the lines you're looking for, and set the textbox's Text proeprty to the StringBuilder's string. For added performance, set the StringBuilder's capacity to a reasonable guess of the final sie of the string. (Or to list.Skip(...).Take(...).Sum(s => s.Length))
If you're concerned about memory, you can trim the List<string> by calling RemoveRange.
As long as you don't put too much in the textbox at once, doing it this way should be extremely fast. All of the manipulation of the List<string> and the StringBuilder can be done in a background thread, and you can pass the completed string to the UI thread.
The TextBox.Lines property simply concatenates the array you give it using a StringBuilder, so there's no point in using it (and making a needless array).
Instead of splitting the text and then re-joining it, just get the sub-string from the 51st line:
int i = textBox1.GetFirstCharIndexFromLine(50);
if (i > 0) textBox1.Text = textBox1.Text.Substring(i);