How about this syntactic candy?

  • Thread starter Thread starter Hilton
  • Start date Start date
Steve Thackery said:
Although Hilton's results disprove my arguments ("instincts" would be a
better term), I want to congratulate him on actually doing some
measurements!

There's a tongue-in-cheek expression - "Don't let the facts ruin a perfectly
good theory" - which we are probably all guilty of at some time or other.
Lots of our debates in this forum are based on opinions rather than facts,
so it's a pleasant contrast to see some real evidence in support of
someone's position!

True - although it's worth saying that the tests need to be performed
on real-world code in order to prove whether or not they're
significant. Most loops actually do real work, and that's where the
time goes. I would be surprised to see a change like Hilton's making a
significant difference to much genuine code. (It's not impossible, but
I believe it's rare.)
 
Hilton said:
In the post to which you're repying, I wrote: "I suggested: "for (int i = 0;
i < const list.Count; i++)""

As many have pointed out, the problem is that list.Count is not constant. It
can change between iterations. Adding "const" to it is therefore a lie, and
breaks the code.
 
Hilton said:
Did you use an array or a list? Anyway, here are the results on my machine
and the results are significant performance-wise:

foreach takes 3.7 seconds
list.Count takes 1.6 seconds
const list.Count take 0.88 seconds

So, even if "const" never gets adopted, using a temporary variable
(especially for simple loops) seems to achieve 2x results. The temporary
variable is the same as my proposed const and it is exactly what the
compiler would do anyway. I bet that most for loops loop to a constant and
that we're all losing a lot of effiency by using "i < list.Count" in the for
condition.

1)

for (int loop = 0; loop < LOOP_COUNT; loop++)
{
for (int i = 0; i < 1024; i++)
{
if (list == null) // if (IsNull (i, list))
{
x++;
}
}
}

is not what you suggested. You suggested:

for (int loop = 0; loop < LOOP_COUNT; loop++)
{
int n = list.Count;
for (int i = 0; i < n; i++)
{
if (list == null) // if (IsNull (i, list))
{
x++;
}
}
}

2)

Somehow I think you used .NET 1.1 and not 2.0:

C:\>csc /o+ z.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.


C:\>z
2,781
0,656
1,234
0

C:\>csc /o+ z.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.1433
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.


C:\>z
2,344
0,719
1,172
0

the relative gap narrows a bit with a recent CLR.

3)

The relative gap is rather uninteresting.

It is still only 5 nanoseconds per iteration.

How many apps would that have an impact on total performance ??

4)

Changing:

IList list = new ArrayList ();

to:

List<string> list = new List<string> ();

gives:

1,000
0,219
0,297
0

which is still a relative 35% difference, but the absolute gap
has been reduced to 1 nanosecond per iteration.

Using a newer CLR and a newer library is much more important
than teaking the code manually.

Arne
 
Hi,

for (int i = 0; i < list.Count; i++)

has a hidden performance hit; i.e. list.Count gets evaluated each time, so
we write something like:

int listCount = list.Count;
for (int i = 0; i < listCount; i++)
You might be able to help the compiler more by adding a const to the
decalration:

const int listCount = list.Count;
for (int i = 0; i < listCount; i++)

rossum
 
rossum said:
You might be able to help the compiler more by adding a const to the
decalration:

const int listCount = list.Count;
for (int i = 0; i < listCount; i++)

Of course, if the list size changes during the loop this will cause trouble.
This is a fine idiom for when you know the size will not change.
 
What are you talking about? Lying? Breaking the code?

If the compiler does not enforce the "const"-ness of the expression,
then it's possible for the programmer to write "const" even when it's
not. Instant bug, due to an invalid optimization.

I'm not aware of any other use of the keyword "const" where that's
possible, at least not without some explicit "unsafe" code on the part
of the programmer (like casting away the "const" attribute).
:) Anyway, your example would not compile - you changed my suggestion.
I
suggested: "for (int i = 0; i < const list.Count; i++)" and I bet the
compiler writers could add this very very simply.

What's the difference? Are you proposing that the only legal variation of
the syntax would be "[comparison operator] const"? If so, then I submit
that's an even more limited addition (and thus unwarranted) than I'd
originally thought you were talking about.

If not, then perhaps you could be more clear about what it is exactly
you're proposing. So far, you haven't been very specific.

In the post to which you're repying, I wrote: "I suggested: "for (int i = 0;
i < const list.Count; i++)""

Yes, but you're using a single example to try to explain some more
general rule. What is your more general rule? Both FTM and I have
proposed other examples of code that would conform to _a_ general rule
that includes your example, but you are unsatisfied with those examples.

So what _is_ your general rule that describes what a legal syntax would
be, if not one of the rules we've inferred so far?

Describe the grammar. Providing a single code example is insufficient,
because language specifications rely on grammar descriptions, not code
examples.
I never said there's never any use for it. But it should add real value,
rather than being some sort of gimmick.


Well, first of all, lots of languages do okay without that. But more
significantly, IMHO, the reason that syntax has survived so many
generations of this family of languages is that it's so commonly useful.
It _significantly_ abbreviates the code, but in a way that's still quite
readable. More important, it provides a syntax to do something that would
simply not be practical any other way.

What? Come on Peter...

x = A ? B : C can easily be written with an "if". If you look at the code,
that is exactly what compiler generally do anyway.
Wrong.

[I've ignored formatting here]
if (A) { x = B; } else {x = C; }

The expression is "A ? B : C". Your code is not the expression, it's a
different way of achieving the assignment to the variable "x".

It's true that there is an alternative way to represent "x = A ? B :
C", but a) that's not what was originally being discussed (you've added
the assignment), and b) the new representation isn't actually the
literal equivalent of the original. The original uses a conditional
expression in a single assignment to a variable "x", where as your
"alternative" uses a conditional expression to choose between two
different code paths assigning to the variable "x".

The difference is subtle and I don't blame you for missing it. But it
does exist, and it is important.

As Jon pointed out, that is just one example of how the expression
could be used. But an if() statement isn't an expression and can't be
used where an expression can, unlike the "?:" operation. While in the
above example, you're correct that you can essentially achieve the same
thing, there are lots of examples that cannot be successfully converted
like that, and even in this case the resulting code is not necessarily
identical.
[zap]
IMHO, your suggestion doesn't even come close to solving the kind of
issues that an expression like "A ? B : C" solves.

"A ? B : C" solves no 'issues' - it is pure syntactic candy.

Of course it solves issues. I haven't even said your suggestion
doesn't solve an issue. It just doesn't solve a very important issue,
where as the issue addressed by "A ? B : C" is quite common, and very
important in that there really is no readable, concise equivalent
without such a syntax.
Well since you don't develop for the Compact Framework, you clearly don't
need to optimize. (Yes, I really am just kidding here...)

Peter, C# has added so much new stuff in the latter versions that I
personally feel that C#'s readability has taken a huge nose-dive. As you
say, that is in the eye of the beer-holder, uhh, I mean beholder. I simply
suggested one little syntax change. It really isn't such a big deal.

What's not a big deal? IMHO, it'd be pretty dumb to add it to the
language and if it were added I'd say that'd be a pretty big deal.
Fortunately, I doubt those in charge of the language would ever do
something like that.

But is your suggestion itself a big deal? No...I agree, it's not.
Frankly, I have no idea why you've invested so much effort in defending
it.

It's pretty funny, actually. It seems like the people most likely to
refuse to accept any valid commentary regarding their suggestions are
the people who write things like "Thoughts?" as if they are actually
open to commentary. It appears to me that you aren't actually looking
for commentary; you're only interested in someone agreeing with you and
if they don't, well then you just don't have any use for their
contribution.

Well, good luck with that.

Pete
 
Jon said:
The changes in C# 3 actually *vastly* improve readability once you're
used to them. While the new elements such as lambda expressions are
"strangers" to you then yes, it'll be harder to read. The
expressiveness of LINQ etc is really great though, IMO. Just wait until
you've used it for a while. I get frustrated when I write C# 2 these
days...

That's what I feel like now when I write Java. Sounds corny but C# is a
very clean and neat language. For example "person.setAge
(person.getAge()+1)" is simply person.Age++, that's nice and very readable.
I really haven't got into C#2 or C#3 at all since I'm targetting CF1
devices, pity I like some (all?) of the new stuff, but some of it looks
C++ish which I found to be 'ugly'. Again, just my initial impressions of
generics etc. As you point out, after using it for a while, I'd probably
change my opinion on it.

Keep well,

Hilton
 
Arne,
1)

for (int loop = 0; loop < LOOP_COUNT; loop++)
{
for (int i = 0; i < 1024; i++)
{
if (list == null) // if (IsNull (i, list))
{
x++;
}
}
}

is not what you suggested. You suggested:

for (int loop = 0; loop < LOOP_COUNT; loop++)
{
int n = list.Count;
for (int i = 0; i < n; i++)
{
if (list == null) // if (IsNull (i, list))
{
x++;
}
}
}


Good point, that's for pointing that out and running the numbers.

2)

Somehow I think you used .NET 1.1 and not 2.0:

C:\>csc /o+ z.cs
Microsoft (R) Visual C# .NET Compiler version 7.10.6001.4
for Microsoft (R) .NET Framework version 1.1.4322
Copyright (C) Microsoft Corporation 2001-2002. All rights reserved.


C:\>z
2,781
0,656
1,234
0

C:\>csc /o+ z.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.1433
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.


C:\>z
2,344
0,719
1,172
0

Very nice that two of them got faster, but the 2nd got slower - interesting,
but of course, it could just accuracy of the timing.

the relative gap narrows a bit with a recent CLR.

That's good to see, I wonder why; i.e. did they speed up method calls or do
some unrolling. The unrolling would cause the code to get larger and might
narrow the gap too (as we see above)

3)

The relative gap is rather uninteresting.

It is still only 5 nanoseconds per iteration.

How many apps would that have an impact on total performance ??

4)

Changing:

IList list = new ArrayList ();

to:

List<string> list = new List<string> ();

gives:

1,000
0,219
0,297
0

This is interesting. I don't have the later Framekworks installed. If you
could list the IL on these that would be interesting.

Hilton
 
Peter said:
If the compiler does not enforce the "const"-ness of the expression, then
it's possible for the programmer to write "const" even when it's not.
Instant bug, due to an invalid optimization.

If the compiler doesn't enforce x++ by incrementing x, then it is my bug?

[zap]
x = A ? B : C can easily be written with an "if". If you look at the
code,
that is exactly what compiler generally do anyway.
Wrong.

[I've ignored formatting here]
if (A) { x = B; } else {x = C; }

The expression is "A ? B : C". Your code is not the expression, it's a
different way of achieving the assignment to the variable "x".

It's true that there is an alternative way to represent "x = A ? B : C",
but a) that's not what was originally being discussed (you've added the
assignment), and b) the new representation isn't actually the literal
equivalent of the original. The original uses a conditional expression in
a single assignment to a variable "x", where as your "alternative" uses a
conditional expression to choose between two different code paths
assigning to the variable "x".

The difference is subtle and I don't blame you for missing it. But it
does exist, and it is important.

Thanks for your concern, but it really is the same. Using Jon's example, I
expanded a bit to use his code, plus I added the same version using a basic
good ol' "if" statement. You'll be amazed. See how the "if" and the
"non-if" version are essentially the same? That's an "if" statement right
there, the results go to a 'real' variable, temporary variable, stack (if
any language), but it is still an "if" statement. Yet when I said: "If you
look at the code, that is exactly what compiler generally do anyway.", you
said "Wrong". If you're just trying to prove me wrong all the time, perhaps
you should spend a few minutes of research first. In another thread you
said that Dictionaries would just be a very little bit slower that arrays
and therefore my suggestion was not good. But, did you post any timing, any
code, anything? No. Nothing at all to back up your claims and your
'accusations' of my being wrong.

static void blob ()
{
int count = 2;

string plural;
if (count==1)
{
plural = "";
}
else
{
plural = "s";
}

Console.WriteLine ("You have {0} order{1} of 23 order{2}remaining.",
count, count==1 ? "" : "s", plural);
}



..method private hidebysig static void 'blob'() cil managed
{
// Code size 54 (0x36)
.maxstack 4
.locals init (int32 V_0,
string V_1)
IL_0000: ldc.i4.2
IL_0001: stloc.0
IL_0002: ldloc.0
IL_0003: ldc.i4.1
IL_0004: bne.un.s IL_000e
IL_0006: ldstr ""
IL_000b: stloc.1
IL_000c: br.s IL_0014
IL_000e: ldstr "s"
IL_0013: stloc.1
IL_0014: ldstr "You have {0} order{1} of 23 order{2}remaining."
IL_0019: ldloc.0
IL_001a: box [mscorlib]System.Int32
IL_001f: ldloc.0
IL_0020: ldc.i4.1
IL_0021: beq.s IL_002a
IL_0023: ldstr "s"
IL_0028: br.s IL_002f
IL_002a: ldstr ""
IL_002f: ldloc.1
IL_0030: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)


What's not a big deal? IMHO, it'd be pretty dumb to add it to the
language and if it were added I'd say that'd be a pretty big deal.
Fortunately, I doubt those in charge of the language would ever do
something like that.

But is your suggestion itself a big deal? No...I agree, it's not.
Frankly, I have no idea why you've invested so much effort in defending
it.

I'm not defending it, like I said it is not a big deal. But when you accuse
me of being wrong all the time and mud-sling, that's just not cool.

Hilton
 
If the compiler doesn't enforce x++ by incrementing x, then it is my bug?

Non sequitur.
[...]
The difference is subtle and I don't blame you for missing it. But it
does exist, and it is important.

Thanks for your concern, but it really is the same.

No, it's not.
Using Jon's example, I
expanded a bit to use his code, plus I added the same version using a basic
good ol' "if" statement.

Where? What are you talking about?
You'll be amazed. See how the "if" and the
"non-if" version are essentially the same?

No, I don't see any such thing. Did you intend to post something and forget?
That's an "if" statement right
there, the results go to a 'real' variable, temporary variable, stack (if
any language), but it is still an "if" statement. Yet when I said: "If you
look at the code, that is exactly what compiler generally do anyway.", you
said "Wrong".

Perhaps I could have put my statement of "Wrong" in a less ambiguous
place. I thought the rest of my post would have made clear where it is
you went wrong, but I can see from your statement about ("but it really
is the same") that you just don't get it.
If you're just trying to prove me wrong all the time, perhaps
you should spend a few minutes of research first. In another thread you
said that Dictionaries would just be a very little bit slower that arrays
and therefore my suggestion was not good. But, did you post any timing, any
code, anything? No.

Would you like me to? It would be trivial to test.

If I thought there was any chance at all that posting some data would
evoke a retraction from you, I would be more than happy to do your
testing for you. But you've never shown any sign of being able to
acknowledge when you've made a mistake. I'm not interested in wasting
my time on something that you're never going to acknowledge as helpful
anyway.
Nothing at all to back up your claims and your
'accusations' of my being wrong.

The same could be said of your own claims. Why is the onus on me to do
the work, when you are the one making claims that fly in the face of
the known facts. Algorithm order is a very good way of estimating
general performance characteristics, and there's already sufficient
documentation of that to prove your claims wrong without having to run
any actual tests.

All of that information was posted to the thread you're talking about,
and it's quite conclusive. If you want to reject the information, you
need to put some effort into it. I see no reason for me to bother
myself, especially since I have no reason to expect that having
additional data showing you to be wrong would change anything.
static void blob ()
{
int count = 2;

string plural;
if (count==1)
{
plural = "";
}
else
{
plural = "s";
}

Console.WriteLine ("You have {0} order{1} of 23 order{2}remaining.",
count, count==1 ? "" : "s", plural);
}

I'm not sure what the above code is supposed to prove. I've already
acknowledged in my previous post that in your degenerate case the use
of "?:" can be the same as using an if() statement. The point is that
it _is_ a degenerate case, and there are lots of examples of using that
syntax that _cannot_ be expressed in the same way just using an if()
statement.

[...]
I'm not defending it, like I said it is not a big deal.

You _are_ defending it. This entire thread is about you defending it.
But when you accuse
me of being wrong all the time and mud-sling, that's just not cool.

Why? I can't help it when you're wrong, and I certainly haven't
engaged in "mud-sling". Hint: just saying that you're wrong isn't
mud-slinging.

Pete
 
Hilton said:
Very nice that two of them got faster, but the 2nd got slower - interesting,
but of course, it could just accuracy of the timing.

I don't think it is accuracy. I tried with x10 more iterations. Same
picture.

But this type of benchmarks is measuring the speed of one or two simple
constructs. They are extremely sensitive to small changes to the
optimization. A real program consist of tens of thousands of simple
constructs. An enhanced optimizer may make practically all real programs
faster, but it will almost certainly make some constructs with some
data slower.

I don't think that it say much.
This is interesting. I don't have the later Framekworks installed.

You should - 1.1 is 4 years old.

Arne
 
Hilton said:
If you
could list the IL on these that would be interesting.

..method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() =
( 01 00 00 00 )
// Code size 459 (0x1cb)
.maxstack 2
.locals init (int32 V_0,
class [mscorlib]System.Collections.Generic.List`1<string> V_1,
int32 V_2,
int32 V_3,
valuetype [mscorlib]System.DateTime V_4,
int32 V_5,
object V_6,
valuetype [mscorlib]System.DateTime V_7,
valuetype [mscorlib]System.DateTime V_8,
valuetype [mscorlib]System.DateTime V_9,
valuetype [mscorlib]System.DateTime V_10,
valuetype [mscorlib]System.DateTime V_11,
bool V_12,
valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<string> V_13,
valuetype [mscorlib]System.TimeSpan V_14,
float64 V_15)
IL_0000: nop
IL_0001: ldc.i4 0x186a0
IL_0006: stloc.0
IL_0007: newobj instance void class
[mscorlib]System.Collections.Generic.List`1<string>::.ctor()
IL_000c: stloc.1
IL_000d: ldc.i4.0
IL_000e: stloc.2
IL_000f: br.s IL_0023
IL_0011: nop
IL_0012: ldloc.1
IL_0013: ldsfld string [mscorlib]System.String::Empty
IL_0018: callvirt instance void class
[mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
IL_001d: nop
IL_001e: nop
IL_001f: ldloc.2
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.2
IL_0023: ldloc.2
IL_0024: ldc.i4 0x400
IL_0029: clt
IL_002b: stloc.s V_12
IL_002d: ldloc.s V_12
IL_002f: brtrue.s IL_0011
IL_0031: ldc.i4.0
IL_0032: stloc.3
IL_0033: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_0038: stloc.s V_4
IL_003a: ldc.i4.0
IL_003b: stloc.s V_5
IL_003d: br.s IL_0090
IL_003f: nop
IL_0040: nop
IL_0041: ldloc.1
IL_0042: callvirt instance valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class
[mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
IL_0047: stloc.s V_13
.try
{
IL_0049: br.s IL_006a
IL_004b: ldloca.s V_13
IL_004d: call instance !0 valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<string>::get_Current()
IL_0052: stloc.s V_6
IL_0054: nop
IL_0055: ldloc.s V_6
IL_0057: ldnull
IL_0058: ceq
IL_005a: ldc.i4.0
IL_005b: ceq
IL_005d: stloc.s V_12
IL_005f: ldloc.s V_12
IL_0061: brtrue.s IL_0069
IL_0063: nop
IL_0064: ldloc.3
IL_0065: ldc.i4.1
IL_0066: add
IL_0067: stloc.3
IL_0068: nop
IL_0069: nop
IL_006a: ldloca.s V_13
IL_006c: call instance bool valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<string>::MoveNext()
IL_0071: stloc.s V_12
IL_0073: ldloc.s V_12
IL_0075: brtrue.s IL_004b
IL_0077: leave.s IL_0088
} // end .try
finally
{
IL_0079: ldloca.s V_13
IL_007b: constrained. valuetype
[mscorlib]System.Collections.Generic.List`1/Enumerator<string>
IL_0081: callvirt instance void
[mscorlib]System.IDisposable::Dispose()
IL_0086: nop
IL_0087: endfinally
} // end handler
IL_0088: nop
IL_0089: nop
IL_008a: ldloc.s V_5
IL_008c: ldc.i4.1
IL_008d: add
IL_008e: stloc.s V_5
IL_0090: ldloc.s V_5
IL_0092: ldloc.0
IL_0093: clt
IL_0095: stloc.s V_12
IL_0097: ldloc.s V_12
IL_0099: brtrue.s IL_003f
IL_009b: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_00a0: stloc.s V_7
IL_00a2: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_00a7: stloc.s V_8
IL_00a9: ldc.i4.0
IL_00aa: stloc.s V_5
IL_00ac: br.s IL_00e7
IL_00ae: nop
IL_00af: ldc.i4.0
IL_00b0: stloc.2
IL_00b1: br.s IL_00d2
IL_00b3: nop
IL_00b4: ldloc.1
IL_00b5: ldloc.2
IL_00b6: callvirt instance !0 class
[mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)
IL_00bb: ldnull
IL_00bc: ceq
IL_00be: ldc.i4.0
IL_00bf: ceq
IL_00c1: stloc.s V_12
IL_00c3: ldloc.s V_12
IL_00c5: brtrue.s IL_00cd
IL_00c7: nop
IL_00c8: ldloc.3
IL_00c9: ldc.i4.1
IL_00ca: add
IL_00cb: stloc.3
IL_00cc: nop
IL_00cd: nop
IL_00ce: ldloc.2
IL_00cf: ldc.i4.1
IL_00d0: add
IL_00d1: stloc.2
IL_00d2: ldloc.2
IL_00d3: ldc.i4 0x400
IL_00d8: clt
IL_00da: stloc.s V_12
IL_00dc: ldloc.s V_12
IL_00de: brtrue.s IL_00b3
IL_00e0: nop
IL_00e1: ldloc.s V_5
IL_00e3: ldc.i4.1
IL_00e4: add
IL_00e5: stloc.s V_5
IL_00e7: ldloc.s V_5
IL_00e9: ldloc.0
IL_00ea: clt
IL_00ec: stloc.s V_12
IL_00ee: ldloc.s V_12
IL_00f0: brtrue.s IL_00ae
IL_00f2: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_00f7: stloc.s V_9
IL_00f9: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_00fe: stloc.s V_10
IL_0100: ldc.i4.0
IL_0101: stloc.s V_5
IL_0103: br.s IL_013f
IL_0105: nop
IL_0106: ldc.i4.0
IL_0107: stloc.2
IL_0108: br.s IL_0129
IL_010a: nop
IL_010b: ldloc.1
IL_010c: ldloc.2
IL_010d: callvirt instance !0 class
[mscorlib]System.Collections.Generic.List`1<string>::get_Item(int32)
IL_0112: ldnull
IL_0113: ceq
IL_0115: ldc.i4.0
IL_0116: ceq
IL_0118: stloc.s V_12
IL_011a: ldloc.s V_12
IL_011c: brtrue.s IL_0124
IL_011e: nop
IL_011f: ldloc.3
IL_0120: ldc.i4.1
IL_0121: add
IL_0122: stloc.3
IL_0123: nop
IL_0124: nop
IL_0125: ldloc.2
IL_0126: ldc.i4.1
IL_0127: add
IL_0128: stloc.2
IL_0129: ldloc.2
IL_012a: ldloc.1
IL_012b: callvirt instance int32 class
[mscorlib]System.Collections.Generic.List`1<string>::get_Count()
IL_0130: clt
IL_0132: stloc.s V_12
IL_0134: ldloc.s V_12
IL_0136: brtrue.s IL_010a
IL_0138: nop
IL_0139: ldloc.s V_5
IL_013b: ldc.i4.1
IL_013c: add
IL_013d: stloc.s V_5
IL_013f: ldloc.s V_5
IL_0141: ldloc.0
IL_0142: clt
IL_0144: stloc.s V_12
IL_0146: ldloc.s V_12
IL_0148: brtrue.s IL_0105
IL_014a: call valuetype [mscorlib]System.DateTime
[mscorlib]System.DateTime::get_Now()
IL_014f: stloc.s V_11
IL_0151: ldloc.s V_7
IL_0153: ldloc.s V_4
IL_0155: call valuetype [mscorlib]System.TimeSpan
[mscorlib]System.DateTime::op_Subtraction(valuetype
[mscorlib]System.DateTime,

valuetype [mscorlib]System.DateTime)
IL_015a: stloc.s V_14
IL_015c: ldloca.s V_14
IL_015e: call instance float64
[mscorlib]System.TimeSpan::get_TotalSeconds()
IL_0163: stloc.s V_15
IL_0165: ldloca.s V_15
IL_0167: ldstr "F3"
IL_016c: call instance string
[mscorlib]System.Double::ToString(string)
IL_0171: call void [mscorlib]System.Console::WriteLine(string)
IL_0176: nop
IL_0177: ldloc.s V_9
IL_0179: ldloc.s V_8
IL_017b: call valuetype [mscorlib]System.TimeSpan
[mscorlib]System.DateTime::op_Subtraction(valuetype
[mscorlib]System.DateTime,

valuetype [mscorlib]System.DateTime)
IL_0180: stloc.s V_14
IL_0182: ldloca.s V_14
IL_0184: call instance float64
[mscorlib]System.TimeSpan::get_TotalSeconds()
IL_0189: stloc.s V_15
IL_018b: ldloca.s V_15
IL_018d: ldstr "F3"
IL_0192: call instance string
[mscorlib]System.Double::ToString(string)
IL_0197: call void [mscorlib]System.Console::WriteLine(string)
IL_019c: nop
IL_019d: ldloc.s V_11
IL_019f: ldloc.s V_10
IL_01a1: call valuetype [mscorlib]System.TimeSpan
[mscorlib]System.DateTime::op_Subtraction(valuetype
[mscorlib]System.DateTime,

valuetype [mscorlib]System.DateTime)
IL_01a6: stloc.s V_14
IL_01a8: ldloca.s V_14
IL_01aa: call instance float64
[mscorlib]System.TimeSpan::get_TotalSeconds()
IL_01af: stloc.s V_15
IL_01b1: ldloca.s V_15
IL_01b3: ldstr "F3"
IL_01b8: call instance string
[mscorlib]System.Double::ToString(string)
IL_01bd: call void [mscorlib]System.Console::WriteLine(string)
IL_01c2: nop
IL_01c3: ldloc.3
IL_01c4: call void [mscorlib]System.Console::WriteLine(int32)
IL_01c9: nop
IL_01ca: ret
} // end of method Class1::Main
 
Peter Duniho said:
Yes, but you're using a single example to try to explain some more
general rule. What is your more general rule? Both FTM and I have
proposed other examples of code that would conform to _a_ general rule
that includes your example, but you are unsatisfied with those examples.

So what _is_ your general rule that describes what a legal syntax would
be, if not one of the rules we've inferred so far?

Describe the grammar. Providing a single code example is insufficient,
because language specifications rely on grammar descriptions, not code
examples.

I stepped away from a little bit, but the discussion looks great. What
Peter wrote was what I was trying to get at describing. With one example,
the impact on the language can not be known. You have to have multiple
people interpret how they would use the change.
 
Peter said:
Would you like me to? It would be trivial to test.

Yes please. Putting aside our disagreements, I really would be interested
to see how much slower (if any) using a dictionary is than "array[x]++".

Thanks,

Hilton
 
Peter said:
Would you like me to? It would be trivial to test.

Yes please. Putting aside our disagreements, I really would be interested
to see how much slower (if any) using a dictionary is than "array[x]++".

I'm sorry. You appear to have missed the rest of my post. Granted,
you're a big fan of your "zap", trimming anything that you'd rather not
respond to. But I already explained my position. Frankly, nothing
you've ever written in this newsgroup gives me any confidence there
will be a point in putting together such a test.

Like I said, if I thought there was a good chance you'd admit your
original mistake when presented with the numbers, I'd be happy to
invest the time (and I have in fact done so in other discussions
regarding other topics). But there's already been plenty of other
kinds of evidence suggesting a mistake in your original claim that the
dictionary solution is "inefficient", with nary a peep from you
acknowledging it.

So, are you saying that you're prepared to admit you were wrong? If
so, why haven't you acknowledged the other information that's already
been provided? What motivation do I have for writing and running the
code, given the ample evidence that already exists in support of my
statements?

Pete
 
Peter said:
Peter said:
In another thread you
said that Dictionaries would just be a very little bit slower that
arrays
and therefore my suggestion was not good. But, did you post any
timing,
any code, anything? No.

Would you like me to? It would be trivial to test.

Yes please. Putting aside our disagreements, I really would be
interested
to see how much slower (if any) using a dictionary is than "array[x]++".

I'm sorry. You appear to have missed the rest of my post. Granted,
you're a big fan of your "zap", trimming anything that you'd rather not
respond to. But I already explained my position. Frankly, nothing you've
ever written in this newsgroup gives me any confidence there will be a
point in putting together such a test.

Frankly, I don't care what you think about me or what I write, I honestly
truely don't, I care about how much slower (if any) using a ditionary is
than "array[x]++". Because you have yet to produce any code or timings to
back up what you've claimed in this thread (I and others have), other people
would tell you to "put up or shut up", but I'd rather simply ignore your
ranting posts and poll the other readers here to see if anyone has a few
minutes to write a "dictionary versus array" time trial. [I would, but I
delibrately need to keep (only) earlier versions on the .NET Framework on my
work machine.]

Like I said, if I thought there was a good chance you'd admit your
original mistake when presented with the numbers, I'd be happy to invest
the time (and I have in fact done so in other discussions regarding other
topics). But there's already been plenty of other kinds of evidence
suggesting a mistake in your original claim that the dictionary solution
is "inefficient", with nary a peep from you acknowledging it.

So, are you saying that you're prepared to admit you were wrong?

Yes, I always am when given cold, hard facts. The nice thing about being
proven wrong is that you learn something.

Hilton
 
I'm sorry. You appear to have missed the rest of my post. Granted,
you're a big fan of your "zap", trimming anything that you'd rather not
respond to. But I already explained my position. Frankly, nothing you've
ever written in this newsgroup gives me any confidence there will be a
point in putting together such a test.

Frankly, I don't care what you think about me or what I write, I honestly
truely don't, I care about how much slower (if any) using a ditionary is
than "array[x]++".

For someone who cares, you sure are a lot more willing to tell people
they are wrong (i.e. those who proposed a dictionary solution are
suggesting an "inefficient" solution) than to actually provide any code
to back up your claim.

I find it ironic that you should take me to task for making what you
call unfounded claims of you being wrong without providing code to
prove it when you're the one who initially claimed the other replies
were wrong in the first place. Just as it's not clear what grammatical
rules you intend to apply to this "const" keyword use in a for()
statement, it's not really clear by what rules you're following in
which I am somehow obligated to provide your test code, while you are
not.

My only objection was your dismissal of the other suggestions as
"inefficient". The only thing I suggested you were wrong about was
that you were telling people they were wrong. And yet, somehow, the
obligation to "put up or shut up" has somehow fallen on me exclusively,
according to you.

As for whether you care what I think, you miss my point. It's not
important whether you care what I think about you. I'm simply
explaining that there's been nothing to suggest that any effort on my
part to provide more detailed information than what has already been
provided will change anything about your attitude.

Just as in this thread you've avoided providing any actual details
regarding the grammar rules for your new use of "const", in the other
thread you've carefully avoided even revisiting the question about
whether the dictionary solution is "inefficient". That makes me
suspect that you already realize the error, and that even if it were
shown that the dictionary solution can in fact be efficient, you would
either just ignore that or come up with some reason to dismiss the
demonstration as irrelevant.

Do I care whether this information matters to you? No. I am simply
offering it so that you understand better why I am not strongly
motivated to provide the test code that you yourself ought to be
providing anyway. If it's not useful information to you, that's fine.
Because you have yet to produce any code or timings to
back up what you've claimed in this thread (I and others have)

Um. You are confusing your threads. This thread is not the
array/dictionary thread. Why would I post code in this thread? I
haven't made any claims that require the posting of code to demonstrate.
, other people
would tell you to "put up or shut up", but I'd rather simply ignore your
ranting posts and poll the other readers here to see if anyone has a few
minutes to write a "dictionary versus array" time trial. [I would, but I
delibrately need to keep (only) earlier versions on the .NET Framework on my
work machine.]

If you have no suitable machine on which to test a version of .NET
Framework that supports the Dictionary<T> class, in what way are you
even qualified to make a statement regarding the efficiency of the
Dictionary said:
Yes, I always am when given cold, hard facts.

I see. Other than the "cold, hard facts" that have already been
provided, you mean. Is there something about things like...

* The algorithm order for Dictionary<T>, being nearly equivalent to
an array lookup, strongly suggests the performance of the two solutions
are basically also equivalent. Is it that you don't believe in order
analysis of algorithms? Against your religion? What?

* The "?:" operator (which frankly is a red herring here anyway,
but I don't mind humoring your assertion that it's relevant) provides a
syntax that is in many cases not possible to duplicate with some
reasonably convenient alternative.

....that you find to not be "cold, hard facts"?

And with respect to the second point, which I've limited only to
commenting about the "convenience" of an alternative: while I haven't
put much thought into it, I suspect that there are some scenarios that
simply can't produce the same compiled code. But regardless, for sure
there are scenarios that can't produce the same compiled code without a
much greater amount of extra code (as opposed to the trivial one extra
line of code required to deal with your "const" scenario).

Even a simple example like this one:

SomeMethod(fFlag ? 10 : 20);

would require at a minimum five new lines of code (nine, if you like
braces around all blocks as I do), and that assumes that the compiler
will be smart enough to optimize out the local variable required (it
_should_ be, but not all compilers are created equally). It's trivial
to write even more complex expressions that cause the needed
alternative code to balloon to enormous proportions. (And yes, I'm
insisting on no more than one statement per line of code...I like my
code readable, thankyouverymuch).

Give me some reason to believe that posting any actual timing
information for the array/dictionary thread will make a genuine
difference in your attitude, and I just might take some time to do it.
Otherwise, I just don't see the point.

Pete
 
Frankly, I don't care what you think about me or what I write, I honestly
truely don't, I care about how much slower (if any) using a ditionary is
than "array[x]++". Because you have yet to produce any code or timings to
back up what you've claimed in this thread (I and others have), other people
would tell you to "put up or shut up", but I'd rather simply ignore your
ranting posts and poll the other readers here to see if anyone has a few
minutes to write a "dictionary versus array" time trial. [I would, but I
delibrately need to keep (only) earlier versions on the .NET Framework on my
work machine.]

Okay, here's a fairly quick bit of code. I don't like it because it
uses mutable structs for your array-based version, but that's what you
were suggesting. I haven't optimised the dictionary code at all.

The program takes three parameters:
1) The number of iterations to use
2) The range of values (e.g. 100 means all values are in the range
0-99)
3) The number of samples

Code is at the bottom, here are the results:
test 100000 100 100 // Range=sample size
ArrayCountSorter: 4207
DictionaryCountSorter: 4020

test 100000 100 1000 // 10 times as many samples as range
ArrayCountSorter: 8625
DictionaryCountSorter: 15900

test 10000 100 10000 // 100 times as many samples as range
ArrayCountSorter: 5053
DictionaryCountSorter: 11486

test 10000 10 100000 // 10,000 times as many samples as range
ArrayCountSorter: 45711
DictionaryCountSorter: 109260

test 100000 1000 100 // 1/10 as many samples as range
ArrayCountSorter: 36110
DictionaryCountSorter: 4999

test 10000 10000 100 // 1/100 as many samples as range
ArrayCountSorter: 50147
DictionaryCountSorter: 521

I've conducted more tests, but here's the gist of the results:
1) When the range ~= sample size, the two algorithms are roughly
equal, with the dictionary approach just about winning
2) As the sample size increases without changing the range, the array
approach is better than the dictionary approach, but by *about* a
factor of 2 (increasing slightly, but not dramatically) as the
proportion changes
3) As the range size increases without changing the sample size, the
dictionary approach is *much, much* better.

Given a choice between "twice as fast in certain situations" and
"scales well and is never *very* slow" I know which I'd say is a
better *general* option (given no further information).

Oh, and the dictionary approach has the added benefit of being generic
across types, so you could use it for string-based samples etc with
*no* code changes :)


Here's the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

struct Pair
{
public int Key;
public int Value;

public Pair(int key)
{
Key = key;
Value = 0;
}
}

public class Test
{
const int Iterations = 100000;
const int MaxValue = 100;
const int DataSize = 100;

static void Main(string[] args)
{
int iterations = int.Parse(args[0]);
int range = int.Parse(args[1]);
int samples = int.Parse(args[2]);

Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
ArrayCountSorter sorter = new ArrayCountSorter(range);
sorter.CountAndSort(CreateData(range, samples));
}
sw.Stop();
Console.WriteLine ("ArrayCountSorter: {0}",
sw.ElapsedMilliseconds);

sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
DictionaryCountSorter<int> sorter = new
DictionaryCountSorter<int>();
sorter.CountAndSort(CreateData(range, samples));
}
Console.WriteLine ("DictionaryCountSorter: {0}",
sw.ElapsedMilliseconds);
sw.Stop();
}

static IEnumerable<int> CreateData(int range, int samples)
{
// Always use the same seed - always get the same data
Random rng = new Random(0);
for (int i=0; i < samples; i++)
{
yield return rng.Next(range);
}
}
}

class ArrayCountSorter
{
int range;

public ArrayCountSorter(int range)
{
this.range = range;
}

public IEnumerable<Pair> CountAndSort(IEnumerable<int> input)
{
Pair[] values = new Pair[range];
for (int i=0; i < range; i++)
{
values = new Pair(i);
}
foreach(int datum in input)
{
values[datum].Value++;
}
Array.Sort(values, delegate(Pair p1, Pair p2)
{ return p2.Value.CompareTo(p1.Value); }
);
return values;
}
}

class DictionaryCountSorter<T>
{
public IEnumerable<KeyValuePair<T,int>>
CountAndSort(IEnumerable<T> input)
{
Dictionary<T,int> histogram = new Dictionary<T,int>();
foreach (T datum in input)
{
int count;
histogram.TryGetValue(datum, out count);
histogram[datum] = count+1;
}
List<KeyValuePair<T,int>> list = new
List<KeyValuePair<T,int>>(histogram);
list.Sort(delegate(KeyValuePair<T,int> p1, KeyValuePair<T,int>
p2)
{ return p2.Value.CompareTo(p1.Value); }
);
return list;
}
}

Jon
 
Jon,

Fantastic, thanks, very interesting. When I have some time here I'll take a
closer look at the code, but as a first pass it looks like a tie for few
samples going to about 2x for a larger sample size. With a very few
samples, the sorting then takes the majority of the time and the Dictionary
wins.

Jon, assuming you were going to write it using an array, how would you store
the info given that you don't like the struct code below?

Thanks, and thanks again for the code, numbers, and time to put this
together.

Hilton


Jon Skeet said:
Frankly, I don't care what you think about me or what I write, I honestly
truely don't, I care about how much slower (if any) using a ditionary is
than "array[x]++". Because you have yet to produce any code or timings
to
back up what you've claimed in this thread (I and others have), other
people
would tell you to "put up or shut up", but I'd rather simply ignore your
ranting posts and poll the other readers here to see if anyone has a few
minutes to write a "dictionary versus array" time trial. [I would, but I
delibrately need to keep (only) earlier versions on the .NET Framework on
my
work machine.]

Okay, here's a fairly quick bit of code. I don't like it because it
uses mutable structs for your array-based version, but that's what you
were suggesting. I haven't optimised the dictionary code at all.

The program takes three parameters:
1) The number of iterations to use
2) The range of values (e.g. 100 means all values are in the range
0-99)
3) The number of samples

Code is at the bottom, here are the results:
test 100000 100 100 // Range=sample size
ArrayCountSorter: 4207
DictionaryCountSorter: 4020

test 100000 100 1000 // 10 times as many samples as range
ArrayCountSorter: 8625
DictionaryCountSorter: 15900

test 10000 100 10000 // 100 times as many samples as range
ArrayCountSorter: 5053
DictionaryCountSorter: 11486

test 10000 10 100000 // 10,000 times as many samples as range
ArrayCountSorter: 45711
DictionaryCountSorter: 109260

test 100000 1000 100 // 1/10 as many samples as range
ArrayCountSorter: 36110
DictionaryCountSorter: 4999

test 10000 10000 100 // 1/100 as many samples as range
ArrayCountSorter: 50147
DictionaryCountSorter: 521

I've conducted more tests, but here's the gist of the results:
1) When the range ~= sample size, the two algorithms are roughly
equal, with the dictionary approach just about winning
2) As the sample size increases without changing the range, the array
approach is better than the dictionary approach, but by *about* a
factor of 2 (increasing slightly, but not dramatically) as the
proportion changes
3) As the range size increases without changing the sample size, the
dictionary approach is *much, much* better.

Given a choice between "twice as fast in certain situations" and
"scales well and is never *very* slow" I know which I'd say is a
better *general* option (given no further information).

Oh, and the dictionary approach has the added benefit of being generic
across types, so you could use it for string-based samples etc with
*no* code changes :)


Here's the code:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

struct Pair
{
public int Key;
public int Value;

public Pair(int key)
{
Key = key;
Value = 0;
}
}

public class Test
{
const int Iterations = 100000;
const int MaxValue = 100;
const int DataSize = 100;

static void Main(string[] args)
{
int iterations = int.Parse(args[0]);
int range = int.Parse(args[1]);
int samples = int.Parse(args[2]);

Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
ArrayCountSorter sorter = new ArrayCountSorter(range);
sorter.CountAndSort(CreateData(range, samples));
}
sw.Stop();
Console.WriteLine ("ArrayCountSorter: {0}",
sw.ElapsedMilliseconds);

sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
DictionaryCountSorter<int> sorter = new
DictionaryCountSorter<int>();
sorter.CountAndSort(CreateData(range, samples));
}
Console.WriteLine ("DictionaryCountSorter: {0}",
sw.ElapsedMilliseconds);
sw.Stop();
}

static IEnumerable<int> CreateData(int range, int samples)
{
// Always use the same seed - always get the same data
Random rng = new Random(0);
for (int i=0; i < samples; i++)
{
yield return rng.Next(range);
}
}
}

class ArrayCountSorter
{
int range;

public ArrayCountSorter(int range)
{
this.range = range;
}

public IEnumerable<Pair> CountAndSort(IEnumerable<int> input)
{
Pair[] values = new Pair[range];
for (int i=0; i < range; i++)
{
values = new Pair(i);
}
foreach(int datum in input)
{
values[datum].Value++;
}
Array.Sort(values, delegate(Pair p1, Pair p2)
{ return p2.Value.CompareTo(p1.Value); }
);
return values;
}
}

class DictionaryCountSorter<T>
{
public IEnumerable<KeyValuePair<T,int>>
CountAndSort(IEnumerable<T> input)
{
Dictionary<T,int> histogram = new Dictionary<T,int>();
foreach (T datum in input)
{
int count;
histogram.TryGetValue(datum, out count);
histogram[datum] = count+1;
}
List<KeyValuePair<T,int>> list = new
List<KeyValuePair<T,int>>(histogram);
list.Sort(delegate(KeyValuePair<T,int> p1, KeyValuePair<T,int>
p2)
{ return p2.Value.CompareTo(p1.Value); }
);
return list;
}
}

Jon
 
Hilton said:
Fantastic, thanks, very interesting. When I have some time here I'll take a
closer look at the code, but as a first pass it looks like a tie for few
samples going to about 2x for a larger sample size. With a very few
samples, the sorting then takes the majority of the time and the Dictionary
wins.

Jon, assuming you were going to write it using an array, how would you store
the info given that you don't like the struct code below?

I suspect I'd use two arrays instead, one for the keys and one for the
values. Or I'd try a mutable reference type. Or use KeyValuePair and
create a new one each time, copying the key.

Mutable structs are bad news. They often behave in ways which are
logical but unexpected.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top