Groups | Blog | Home
all groups > dotnet performance > november 2003 >

dotnet performance : Optimization of loops and tests



David Notario [MSFT]
11/2/2003 9:49:56 PM
Unfortunately, our loop expression hoister is not the best of our
optimizations, I woudn't expect it to be removed in currently shipped CLR
versions. The best thing you can do is just like you would do in VC, look at
the disassembly, best way to do that currently, is using cordbg and
disassembling, don't forget to enable optimizations (m JitOptimizations=1,
IIRC)

And of course, before you make your code less readable due to performance
optimizations, make sure it is a bottleneck (for Example, DrawLine is likely
to be much more expensive than the test you want to hoist out of the loop)
--
David Notario
Software Design Engineer, CLR JIT Compiler
http://devdiary.xplsv.com



[quoted text, click to view]

news.microsoft.com
11/3/2003 12:14:31 AM
How will the compiler / JIToptimise the following?

for (int i = 0 ; i <= (stop - start)/step ; i++)
{
if (a = Axis.X)
{
g.DrawLine(blah
}
else
{
}
}


Would it make the test outside the for and save checking for each iteration?
like below...

if (a ==Axis.X)
{
for (int i ... )
{
g.DrawLine(...)
}
}
else
{
for (int i ... )
{
g.DrawLine(...)
}
}

Thus saving a test for each iteration on a == Axis.X


Would it do this or should I do this myself?

I would presume and hope the (stop - start)/step is optimised as a constant
value in the loop so I dont have to put this before...

Jon Skeet [C# MVP]
11/3/2003 8:02:07 AM
[quoted text, click to view]

I disagree strongly. I believe we should always write the most
*readable* code we can. If, after careful benchmarking, we find one
area of code which is causing a bottleneck, then it's worth considering
making the code less readable for the sake of performance - making
absolutely sure that the optimisations have the effect we hope they
will through more careful benchmarking, of course.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 8:13:04 AM
Hi,

From the tests that I have performed, the jitter does not appear to perform
this particular optimization on the code presented. However even if it did,
I would recommend that you do not rely on the optimizations of the jitter to
remedy inefficient code, I believe that we should always write the most
optimal code we can and then let the compilers/jitters do what they can from
there. As with compilers not all jitters are created equal.

Hope this helps

Chris Taylor

[quoted text, click to view]

news.microsoft.com
11/3/2003 8:20:54 AM
Well, the reason im asking is because I read on MSDN that in some cases its
BAD to optimise the code for optimisations done in the JIT and it confuses
it.



[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 8:58:12 AM
[quoted text, click to view]

While it's almost always possible to write code which performs
"reasonably well" and is still optimally readable, there *will* be
times when optimisation and readability conflict. Furthermore,
optimisation isn't always obvious. For instance, take a very simple
loop:

string x = "hello";
foreach (char c in x)
{
// Do something with c
}

That would *perhaps* be more optimally written as:

string x = "hello";
for (int i=0; i < x.Length; i++)
{
char c = x[i];
// Do something with c
}

or we could hoist the length:

string x = "hello";
int length = x.Length;
for (int i=0; i < length; i++)
{
char c = x[i];
// Do something with c
}

or perhaps (if ordering isn't an issue)

string x = "hello";
for (int i=x.Length-1; i >=0; i--)
{
char c = x[i];
// Do something with c
}

Without testing these, it's hard to know whether *any* performance
increase would be achieved, and even after testing we'd only know it
for one particular architecture and framework version. (In 1.0, for
instance, the first would be significantly slower than the second. In
1.1 any distinction has been drastically reduced.)

Unless I had some firm test evidence to suggest that it was going to
cause a *real* performance change, I'd always go with the first
version, because I view it as the most readable. I certainly don't have
time to test all four versions for every bit of code I write. All of
the versions are reasonably readable in themselves, but I regard
optimal readability as the *first* thing that should be achieved
(correctness follows from readability, IMO) - I would only even
*consider* making the code less readable for the sake of performance
later on.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
news.microsoft.com
11/3/2003 9:19:03 AM
I always do the simplest solution first then add complexity as needed..


[quoted text, click to view]

news.microsoft.com
11/3/2003 9:26:48 AM
Ive seen products that when sent to perf teams, they get so morphed about
that they are unreadable - maybe this is due to the QUICK PERF FIXES or
patching done for performance gains rather than being "designed in", and the
same for the data stores, they become just unbelievablly different. Makes
you think about how they where designed in the first place is such a
transformation can take place.



[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 10:18:25 AM
[quoted text, click to view]

But why does it matter if that code isn't anywhere *near* to being a
bottleneck? Unless you actually measure how long things take, quite
often

Of course, it helps to know some *really* bad things in terms of
optimisation - I'm not suggesting that concatenating strings in loops
without using StringBuilder is a good idea, for instance :)

[quoted text, click to view]

Why though? Because it made sense in a different language? Do you have
any *evidence* that hoisting the length has any significant benefit?

(I agree about the length change business, btw - except in the
situation where you're going through a collection and removing some
elements, and there I would often iterate backwards so as to avoid
having to fiddle with the index variable directly - but I'd include a
brief comment to explain why I wasn't doing the for loop in the more
normal way :)

[quoted text, click to view]

I disagree - even though it's only a very small point, the intent of
the code is clearer to my mind without the extra variable hoisted out
of the loop - the intent is "go from 0 to just under the length of the
string" which is most clearly expressed as
for (int i=0; i < x.Length; i++)
- it says *exactly* what it means, and I trust the JITter to make the
code perform reasonably well. If it *doesn't* hoist it, I'll find that
out if and when that loop turns out to be a bottleneck. The chances of
that are very slim - and then I'll measure it.

The ++i vs i++ business is more personal taste - I very, very rarely
use either of them in a case where it matters which one is used. I
therefore prefer the more commonly used one - if everyone uses similar
conventions, it's easier to read other people's code. Not a big problem
in either case - but that's the difference between "readable" code and
"as readable as possible" code, IMO. (Take another example: suppose I'd
used the variable "a" instead of "i". Logically, it shouldn't make any
difference in readability at all, but the fact that "i" is so
conventionally used in this context makes it just that bit more
familiar.)

[quoted text, click to view]

Ah - I make a point of trying not to bring any idiomatic baggage from
one language to another. I very consciously decided to adopt the .NET
naming conventions rather than sticking with Java ones when I started
learning C#, for instance. As I've said quite often, some of the worst
Java code I've seen is from C++ programmers who are trying to bend Java
into a C++-like shape.

[quoted text, click to view]

No offence taken, I assure you, and none intended either :) Life would
be very boring if we all agreed on absolutely everything!

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 10:27:50 AM
Hi,

I agree entirely with your sentiments regarding readable code. However I
believe that it is quite possible to have well written optimized code that
is perfectly readable, particularly with a language like C#. I am not
talking about the kind of optimizations required to perform realtime
rendering at 60 frames per second, just good programming practice. Badly
written code can be just as unreadable.

If my comments seemed to imply drastic optimization techniques should be the
norm I appologize.

Regards

Chris Taylor

[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 11:26:25 AM
[quoted text, click to view]

It shouldn't need to - it should be able to inline the call to
String.Length anyway. The generated IL will make the property method
call, but that's not necessarily what the JIT will do. (When you looked
at the generated assembly code, was that running in release mode, or
under a debugger? The JIT compiler doesn't perform as many
optimisations under debug as release mode.)

I'd be very surprised if String.Length weren't inlined - although I've
been surprised before, of course :)

[quoted text, click to view]

It would have to know that String.Length was constant for any given
string, of course, which it may well not to - but the actual method
call should be converted into inline code, which may not be quite as
fast as a local variable lookup, but should be fast enough for almost
everything.

Again, I believe that the optimised pattern is less readable than the
slightly less efficient one, and I'd rather have more readable code for
the 99% of cases and only go to less readable code - even *slightly*
less readable code - for that 1% (or probably far fewer) cases where it
makes a truly *significant* difference.

[quoted text, click to view]

I suspect I wouldn't use the overloaded operator on a class which
provided one anyway (unless it naughtily didn't provide an equivalent
non-operator way of doing things) - I dislike overloaded operators in
general, apart from a very few cases (string concatenation and
(date/time)/timespan handling being the primary cases).

I'd far rather code the more conventional way for the 99.99% of the
time I'm not using the very, very occasional non-built-in type where
using the ++ operator makes sense.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 11:39:22 AM
Hi,

I agree with you on the point of readable code, my comment was targeted at
the "art" of writting code that just works and hoping the JITter/compiler
will make sure it works well. This seems to be an ever increasing trend and
programmers no longer consider good programming practice. Maybe I have just
had a bad experience?

I 100% agree, I would also use the foreach construct, however when
constructing a manual loop (if there is no enumerator) will always hoist the
length invocation unless of course I was expecting the length to change
(shiver at the thought, I would typically consider this very bad practice),
when used in the condition statement. I also always use ++i or --i rather
than the more traditional i++ or i--. I do not believe these optimization
make for less readable code. And the latter "optimization" stems from my 12
year love affair with C++.

I beg you not to take this personally, I think that in fact we would
actually be agreeing on most of what is said here was it not maybe for my
bad choice of wording or lack of elaboration in my initial post :).

Best Regards

Chris Taylor

[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 11:39:34 AM
[quoted text, click to view]

I presume that's when it's used in the middle of an expression, rather
than on its own? On its own I get the IL code of:

ldloc.0
ldc.i4.1
add
stloc.0

for both versions.

(As I said before, I very rarely use it at all in the middle of
something else - it's just not clear enough. When I do, the semantics I
want determine which version I use, rather than speed :)

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Jon Skeet [C# MVP]
11/3/2003 11:58:02 AM
[quoted text, click to view]

You can't append to the string itself (unless you're running in
mscorlib). The JIT should be able to note that the variable's value
(the string reference) doesn't change in the body of the loop. However,
this *does* require some reasonably in-depth knowledge about the string
type, so I'm not *hugely* surprised that it doesn't hoist the length
out. (Out of interest Chris: could you check whether or not it hoists
the length if you're iterating through an *array* rather than a string?
If you could post how you're doing the testing, that would be great too
- then I can do it myself in future :)

Inlining shouldn't matter even if the variable's value *does* change -
it would still get the right value.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Jon Skeet [C# MVP]
11/3/2003 12:39:30 PM
[quoted text, click to view]

The property may or may not be inlined, but if it's going to change
then the condition can't be hoisted from the loop. That's a separate
decision from inlining.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
news.microsoft.com
11/3/2003 12:55:11 PM
what if that string.Length changes during a loop :D say i append to the
string?


[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 1:04:08 PM
[quoted text, click to view]

Hmmm... I'm still not sure whether you'll really be seeing the fully-
JITted-with-all-optimisations code there. I don't know the best way of
doing it though - ngen would give some idea.

For ArrayList.Count not to be inlined is very odd - it's a prime
candidate for inlining. Can you see *any* calls actually being inlined?

Here's some code to demonstrate why I believe inlining is occurring
with the String.Length call:

(See http://www.pobox.com/~skeet/csharp/benchmark.html for the
framework in which to run this code.)

using System;
using System.Runtime.CompilerServices;

public class Test
{
static int iterations=100000;
static int counter;
static string testString = new string ('x', 100000);
static int testStringLength = testString.Length;

public static void Init (string[] args)
{
if (args.Length >= 1)
iterations = Int32.Parse(args[0]);
if (args.Length >= 2)
{
testString = new string ('x', Int32.Parse(args[1]));
testStringLength = testString.Length;
}
}

public static void Reset()
{
counter=0;
}

public static void Check()
{
if (counter != iterations*testString.Length)
throw new Exception ("Counter value incorrect.");
}

[Benchmark]
public static void TestHoisted()
{
int localIterations = iterations;
string localString = testString;
int localCounter = 0;

for (int i=0; i < localIterations; i++)
{
int length = localString.Length;
for (int j=0; j < length; j++)
{
localCounter++;
}
}

counter = localCounter;
}

[Benchmark]
public static void TestUnhoisted()
{
int localIterations = iterations;
string localString = testString;
int localCounter = 0;

for (int i=0; i < localIterations; i++)
{
for (int j=0; j < localString.Length; j++)
{
localCounter++;
}
}

counter = localCounter;
}

[Benchmark]
public static void TestMethodCallNoInlining()
{
int localIterations = iterations;
string localString = testString;
int localCounter = 0;

for (int i=0; i < localIterations; i++)
{
for (int j=0; j < GetStringLengthNoInlining(); j++)
{
localCounter++;
}
}

counter = localCounter;
}

[MethodImpl(MethodImplOptions.NoInlining)]
static int GetStringLengthNoInlining ()
{
return testStringLength;
}

[Benchmark]
public static void TestInlinedMethodCall()
{
int localIterations = iterations;
string localString = testString;
int localCounter = 0;

for (int i=0; i < localIterations; i++)
{
for (int j=0; j < GetStringLengthInlined(); j++)
{
localCounter++;
}
}

counter = localCounter;
}

static int GetStringLengthInlined ()
{
return testStringLength;
}
}


The results are:
Benchmarking type Test
Run #1
TestHoisted 00:00:06.9375000
TestUnhoisted 00:00:10.3437500
TestMethodCallNoInlining 00:00:40.2812500
TestInlinedMethodCall 00:00:06.7968750
Run #2
TestHoisted 00:00:06.6875000
TestUnhoisted 00:00:10.3281250
TestMethodCallNoInlining 00:00:40.1250000
TestInlinedMethodCall 00:00:06.9218750

Interestingly, it looks like the JIT *is* able to spot that the inlined
method value can be hoisted, but not the String.Length property.
However, as soon as we make the method call not inlined, it's *much*
slower than using the unhoisted String.Length - and the only reason I
can think of for that is that String.Length is inlined itself.

(You might want to run that test using your check for inlining or not,
as a way of testing its validity.)

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
news.microsoft.com
11/3/2003 1:04:46 PM
Stringbuilder then.

or anything .Length.
or something..


[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 1:05:09 PM
[quoted text, click to view]

<snip>

[quoted text, click to view]

See my other post for evidence that your check for the JIT'd code might
not be valid - it looks to me like you're not seeing inlining when I
think it *must* be happening.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 1:22:02 PM
[quoted text, click to view]

Well, this is actually one I always confirm. And both the generated IL as
well as the i386 assembler performs a function call every iteration.
Granted! this might not have any significant impact in 99% of the cases, but
I (and I agree I might be wrong here) always try to promote patterns like
these so that we start to think about the code we write, after all these
years I have not come accross a compiler/JITter that optimizes this.

[quoted text, click to view]

My reasoning here, I am well aware that in the case of primitive types where
the return value is ignored there is actually no difference in the generated
code, however when ever these operators are overloaded the post-increment is
typically implemented using a temporary variable which the compiler/JITter
does not optimize away. As I always try to treat datatypes as having certain
traits, allowing one datatype to be interchanged for a datatype with similar
traits, this helps to ensure that even when a class with overloaded ++
operator I will have the benefit of the more optimal pre-increment operator.
Honestly I have not confirmed this in .NET as it handles the symantecs of
post/pre internally, that is why I made explicit reference to C++.

Regards

Chris Taylor

[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 1:23:07 PM
[quoted text, click to view]

Just to clarify what I mean here: it looks to me like you're not seeing
inlining when I think it would be happening with a full release-
optimising JIT.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 1:38:03 PM
Hi,

Quick update, just checked the ++i vs i++. The IL differs very slightly with
either a DUP before the increment or a DUP after, my first thought here was
that there would be no difference, but once I looked at the JIT'd code the
post increment requires an extra instruction and slower addressing rather
than faster register access on two counts. However, I am not trying to say
this is significant! Just sharing my findings since I had not previously
investigated this issue in .NET.

Regards

Chris Taylor

[quoted text, click to view]

Jon Skeet [C# MVP]
11/3/2003 1:41:31 PM
[quoted text, click to view]

I wish :)

[quoted text, click to view]

I suspect that's because there's no code to be called "outline" as it
were - it's not like there's a method call going on in the IL, it's
just a ldlen instruction.

(I also suspect that in a "real" situation that ldlen is hoisted - I'll
have another go with my benchmarking framework later to see if the
evidence supports that.)

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Chris Taylor
11/3/2003 2:03:18 PM
Just to confirm, all the test were done using a release build. .NET Framwork
1.1.

[quoted text, click to view]

Chris Taylor
11/3/2003 2:13:34 PM
Yes, that was used in a WriteLine statement. And I appreciate your feelings
on this point with regards to readability,
I am guilty of this :( those old C/C++ habbits die hard.

Thank you for an enlightening discussion.

Chris Taylor

[quoted text, click to view]

Chris Taylor
11/3/2003 2:44:35 PM
Hi,

First how I test this.
1) I write the code in a function
2) I Call the function so that it get JIT'd
3) I then call System.Diagnostics.Debugger.Break(); This breaks in a release
build
4) I Call the function again, this way I am sure the JIT did not do a debug
JIT since this is the cached from the previous JIT -> Might be over cautious
here
5) Display the dissassembler to view the i386 code

For arrays eg. byte[] the length is checked directly against the internal
member -> Very Good

For ArrayList the Count is not inlined or hoisted it is invoked on each
iteration -> Not So Good (Well in my books :) )

Regards

Chris Taylor

[quoted text, click to view]

Chris Taylor
11/3/2003 3:05:31 PM
Hi,

Whenever the object being tested changes, since System.String is immutable
appending to it actually creates a new string object with the new contents
and the Length function is invoked on the new string object. In the case of
StringBuilder the call to Length is also invoked each iteration, regardless
of whether the length is changing or not.

Hope this helps

Chris Taylor

[quoted text, click to view]

Chris Taylor
11/3/2003 3:10:31 PM
Hi,

for ( int i = 0; i < sb.Length; ++i )
{
// Code Not affecting the sb in any way
}

or

for ( ; sb.Length < 10; )
{
sb.Append( "a" );
}

In a release build, both cases the Length property is invoked with a 'CALL'
in the JIT'd code for each iteration.

Regards

Chris Taylor

[quoted text, click to view]

Chris Taylor
11/3/2003 3:45:30 PM
It might well be, I have to think how I might view a ngen'd version, maybe I
will try with cordbg. If you have any ideas let me know. The arrays were
inlined.

Cheers

Chris Taylor
[quoted text, click to view]

Stu Smith
11/5/2003 5:13:39 PM
AFAIK the JITter not only hoists the length call but also elimintaes the
bounds checks.

So

for(int i = 0; i < array.Length; ++i)
{
}

is actually quicker than

int length = array.Length

for(int i = 0; i < length; ++i)
{
}

[quoted text, click to view]

Jerome Bonnet
11/6/2003 11:26:15 AM

[quoted text, click to view]

Well done, Chris! I am myself a "prefixally incrementer". I have never
understood why K&R intially promoted the more semantically complex "i++"
over "++i" in their examples for the "C" language.

news.microsoft.com
11/6/2003 11:36:01 AM
i++ is easier to read than ++i, maybe thats because its so well used more
than ++i

When we get to runtime environemnts with optimisation, readability is more
of a focus.

[quoted text, click to view]

Stu Smith
11/6/2003 12:12:32 PM
Sorry, the method body in both cases should have been something like:

Foo(array[i]);

Apologies.

[quoted text, click to view]

Jon Skeet [C# MVP]
11/7/2003 2:01:58 PM
[quoted text, click to view]

It's not completely subjective, but it *is* very difficult to what
would be more readable just on instinct. You'd need a decent study with
people who had no bias beforehand.

Personally I *think* I prefer i++ because it tells me what I'm
operating on before the operation itself, but I'm happy to admit that
may well just be bias due to using it more often.

One thing *is* important though: due to K&R (or whatever) many (most, I
suspect) *existing* devlopers find i++ more readable than ++i, and as
it makes no difference in managed code when it's a statement on its own
(assuming the compiler is being reasonable), I think it's a better idea
to use the more familiar form.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Jerome Bonnet
11/7/2003 2:48:42 PM
Come on. That is completely subjective. If Kernighan and Ritchie had written
their examples with "++i" instead of "i++" you would write just the
opposite.

[quoted text, click to view]

Jon Skeet [C# MVP]
11/7/2003 3:15:13 PM
[quoted text, click to view]

Um, yes. And the compiler *does* optimise i++ and ++i to the same IL -
at least, the C# compiler does. So the real question is which is the
most readable, which is what my post was about.

--
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
Jon Skeet [C# MVP]
11/7/2003 3:17:40 PM
[quoted text, click to view]

No - the only difference is in terms of the value of the expression.=20
One means "the value is that of i; increment i". The other is=20
"increment i; the value is that of i". I don't see that either is more=20
complicated than the other.

--=20
Jon Skeet - <skeet@pobox.com>
http://www.pobox.com/~skeet
discussion NO[at]SPAM discussion.microsoft.com
11/7/2003 3:47:56 PM
The compiler and runtime should work FOR ME not me work FOR IT.




[quoted text, click to view]

Jerome Bonnet
11/7/2003 4:02:54 PM
This is irrelevant (and it's also a cliché).
I don't prefer "++i" because it helps the compiler, I prefer "++i" because
it is the most accurate description of what I want to achieve: simply
increment variable i. On the other hand, "i++" means "take the value for i,
then increment i", which has the same effect when the value of the
expression is not used, but is sementically more compex than needed for what
I want to achieve.
For me writing "i++" instead of "++i" is not wrong, it is just unecessarily
complex, just like writing "i = i + 1" or "i = i++" or "i = i + 1 | 0x00 *
1" or whatever.

[quoted text, click to view]

discussion NO[at]SPAM discussion.microsoft.com
11/7/2003 4:15:16 PM
Whatever gets you wet basically.

I prefer to use butter but hey thats my preference.



[quoted text, click to view]

Jerome Bonnet
11/7/2003 6:35:26 PM
I see your point. I suppose I must be C++-biased then, just like Chris
Taylor.

[quoted text, click to view]


n!
12/17/2003 11:13:23 AM
FWIW, the optimization guidelines suggest you use this method for iterating
through an array:

[quoted text, click to view]

Esp. If you are indexing the array with your loop parameter.

Why? Because the compiler detects your loop validates the array index and
removes the index validation code (because it knows you've already validated
it), which could mean a large performance gain. AFAIK hoisting the sb.Length
out of the loop is not recommended because it goes against this
optimization.

n!

Tony Nassar
1/16/2004 11:37:49 AM
I've read, in this group and elsewhere, that loop invariants will be hoisted
outside of the loop as part of the optimization. All the profiling data I've
collected (by means of DevPartner's profiler and others) conflicts with this
assertion. Let's take the following:

public SomeClass
{
public static int DoSomethingWithAString(string text)
{
for (int I = 0; I < text.Length; ++i)
{
// blah blah blah
}
}
}

Since I'm working on pattern-matching algorithms, I'm very concerned about
this problem. String.get_Length() should be called only as many times as the
function itself. This does *not* seem to happen, i.e., get_Length() is in
fact called on every iteration. You can imagine how badly this affects
pattern-matching. Yes, I've compiled it to release mode with optimization
turned on. It may be that profiling affects the optimization; if so, please
tell me. A C++ compiler would have no trouble hoisting this expression
outside of the loop if text were a "reference to const," but of course
there's no such thing in C#. I would normally consider it bad style to
assign text.Length to a local variable, i.e., manually optimize the code,
but I can't afford the performance penalty here.

Tony

[quoted text, click to view]

Justin Rogers
1/16/2004 1:06:12 PM
Or try using a hoistable item like string.Chars.Length


--
Justin Rogers
DigiTec Web Consultants, LLC.

[quoted text, click to view]