.NET Daily Quiz #031
Note: this question and the next one generated a lot of intense discussion in my office, which was awesome. See if they do in yours, too!
(note: let’s pretend my algorithms are fine. We’re looking for bottlenecks in terms of the way my application is interpreted and compiled)
I’m not going to go into as much detail as Daniel Clifford did in his talk, so I highly recommend watching it.
The most significant and obvious bottleneck in this example occurs inside the first loop. We are conditionally appending a z field to Vector instances. This is a no-no. The first time the Vector constructor is called, the interpreter will generate a hidden class (at run time, not compile time) that represents the data inside a Vector. This class will be used when working with Vectors going forward. Now if we modify any instance of Vector a new hidden class must be generated.
This is a costly operation and it also results in us losing any optimizations that have been optimistically applied to our Vectors. V8 will apply optimizations under the assumption that things aren’t going to change from under it. If things do change (such as a hidden class change) these optimizations must be thrown out.
Another hidden class change is applied further down when we apply the label field to some vectors.
A couple more little issues that may or may not be lurking in this code:
Are x and y doubles or integers? Integers in V8 are represented as “Small Integers”, or SMIs. A SMI is a 31-bit integer and they are very fast to work with. When a supplied numeric value cannot be represented as a SMI (it’s not an integer or it overflows) it must be represented as a “boxed double”. In this case the memory previously used for the SMI is now a 31-bit object pointer to a boxed double-precision float.
How is the array being prepared?
If an array is initialized to be very big but sparse, for example by using "new Array(128);" then the compiler will use a less efficient data structure because it expects a sparse array (because that’s exactly what you’ve told it to expect). Similarly you should always fill an array in order instead of leaving lots of holes. Contiguous arrays are much faster than sparse arrays when you’re expecting contiguous data at the end.
.NET Daily Quiz #032
Back to C# (for now).
Is this a valid overload?
How about this?
Why/why not? Do you know the specific rules that determine a method signature?
The only overload that will fail is
The reason this overload fails is because of a special rule in the C# spec (section 3.6, Signatures and overloading). The definition of a signature is (basically) composed of the method name, type (generic) parameters and the ordered list of parameters (taking into account type and kind, ie value, reference and output). The method return type is NOT part of the method signature. Neither is a params modifier (the params parameter is part of the signature, the modifier is not), nor are type parameter constraints.
A correctly defined method overload must have a unique method signature. Although ref and out modifiers are considered part of the method signature and are distinct, this is an additional rule on overloads — method overload signatures must not differ only in ref and out modifiers. More specifically, a compile-time error occurs if all ref modifier are changed to out modifiers and there is a method signature collision.
Interestingly, the reason for this rule is so that C# programs can be easily translated to run on the CLI. The CLI doesn’t support the same ref and out semantics as C#.
For more detail check out the C# spec section 3.6.
.NET Daily Quiz #033
How many boxing/unboxing operations occur in the following program?
The quick answer here is 3 boxings and 2 unboxings.
Int32 implements its own version of ToString() so no boxed copy is required (more on this in tomorrow’s(?) quiz).
Casting i to IConvertible causes a boxed copy to be created (ToInt32() is implemented explicitly on all numeric types in .NET, so the cast is necessary).
Inserting a retrieving items from List
The same argument for the prior applies to ArrayList, which is backed by object.
What about DoThings
Two things to consider about this relatively simple example:
- All .NET numeric types implement IConvertible explicitly. What are the performance implications of this? Some people believe this was a mistake in the design of the framework — do you agree?
- You could very easily have answered this quiz by opening the compiled application in ildasm.exe and doing a search for “box” and “unbox”. Is it always this easy to find instances of boxing? (hint: no — wait for tomorrow’s(?) quiz to see why).
.NET Daily Quiz #034
Following on from yesterday’s quiz, is there a boxing operation in the following code? If yes, where and why?
The answer is yes, there is indeed a boxing operation. If you compile this code and open it in ildasm you’ll find the following IL in the main method:
So if there is no box opcode, where is the boxed copy? The clue is in the virtual function call to ToString(). Just before the ToString() call you can see the constrained opcode. According to the documentation for constrained:
If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.
So all of this is just a fancy way of saying that a boxed copy of a value type is always created when you call the virtual members on Object, if and only if your value type does not implement these virtual members. The relevant Object members are GetHashCode(), Equals() and ToString(). Implement these methods to avoid unwanted boxed copies!
.NET Daily Quiz #035
Note: Unless otherwise specified all SQL questions will apply to T-SQL on the latest version of MS SQL Server.
I’m trying to retrieve all log entries with a non-null severity level that is not level 1 or 2. Why isn’t my query returning anything?
SQL uses three-valued logic for logical predicates like this one. This means that in addition to the usual TRUE and FALSE logical values we have a NULL value. NULL is not the same as FALSE, but it does evaluate to NOT TRUE. So NULL = FALSE is false, as is NULL = TRUE, but NULL != TRUE is true. This causes the NOT IN predicate to fail on all NULL values (and every comparison involves a null comparison AND’ed together with comparison to 1 and 2 in our predicate).
A colleague pointed out to me that you can “resolve” this issue in T-SQL by using SET ANSI_NULLS OFF. He also correctly pointed at that you should never do this. According to the documentation:
In a future version of SQL Server, ANSI_NULLS will always be ON and any applications that explicitly set the option to OFF will generate an error. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.
There were a couple of alternative queries posted. One colleague offered this
And another gave me:
I’m not enough of a SQL expert to know which is the better solution, but they both seem to work correctly.