Draft Perf Article

Topics: General
Developer
May 8, 2014 at 9:49 PM
I'm posting a draft of an article I'll soon publish on MSDN, but it likely won't go out for another month. It is based on a year of work on tuning the Roslyn compilers against key user experience scenarios (typing, diagnostics, quick fixes, completion lists, etc.). Here's the PDF of the perf article, but I can make the Word doc available if someone wants it. At the end is a link to a talk version of the article some of you may have seen already.

If you have comments, feel free to send me mail.

Cheers,
Bill
May 9, 2014 at 5:34 PM
Edited May 9, 2014 at 5:34 PM
Beginning of page 4. You're saying that var s = id.ToString() + ':' + size.ToString(); introduces boxing because of String.Concat call. Well, you're calling ToString on int values, so there will be no boxing - code snippet should not use ToString() here.
May 9, 2014 at 6:17 PM
marcinjuraszek wrote:
so there will be no boxing
I believe the point was that the ':' gets boxed:
The .NET Framework must box the character literal to invoke Concat.
May 9, 2014 at 6:37 PM
This was interesting, thanks. I'm surprised that single StringBuilder allocations were a big enough problem that caching and reusing the same StringBuilder made a performance impact.
May 9, 2014 at 6:55 PM
How does the cost of acquiring the builder compare with the cost of two thread-static writes and a thread-static read, and how are such costs affected by the usage patterns for memory allocation and other thread-static variables? Conceptually, it seems like a good pattern, but allocation is often cheap, and threadstatic variables aren't exactly free.
May 9, 2014 at 7:02 PM
One other thing I've wondered about the Roslyn implementation- did you ever consider using unsafe code at any point for performance gains?
May 9, 2014 at 7:26 PM
JanKucera wrote:
I believe the point was that the ':' gets boxed:
oh, you're probably right. I missed that ...
Developer
May 9, 2014 at 8:11 PM

Yes, thank you.

Bill

Developer
May 9, 2014 at 8:19 PM
I'm going to draw Paul Harrington's attention to this thread for a couple of the deeper code questions, such as thread-statics and unsafe. Paul came up with all the examples, and while I owned Roslyn perf as a PM, Paul is the chief perf tech lead.

On the StringBuilder comment, remember 1) we're talking at scale parsing and reparsing and generating msgs while working in or compiling, say, all of Roslyn or VS; and 2) if every dev whips up a StringBuilder here and there to squish some things together into a string, at scale, it adds up :-).

Thanks,
Bill
Developer
May 9, 2014 at 9:20 PM
Briefly:
@supercat: The thread-static access costs pale into insignificance compared to a GC. It's all about avoiding an allocation that might trigger a GC. You can see that the .Net Framework also chose this approach: http://bit.ly/1jqewvK
You could do it without the thread-static, but using Interlocked operations instead. You can see that in action elsewhere in Roslyn. In particular take a look at the ObjectPool<T> implementation: http://bit.ly/RxqpoI

@MgSam: We use 'unsafe' where necessary, particularly when emitting binaries and PDBs. You could argue this is for performance. It avoids constructing the output in a giant managed byte array and then serializing that to disk. I've occasionally seen 'stackalloc' (which requires 'unsafe') used to avoid allocations, but it's rare and, at first glance, I can't find any uses within the Roslyn codebase.
  • Paul
May 9, 2014 at 9:58 PM
pharring wrote:
@supercat: The thread-static access costs pale into insignificance compared to a GC. It's all about avoiding an allocation that might trigger a GC. You can see that the .Net Framework also chose this approach: http://bit.ly/1jqewvK
Interesting. I like the way that code is written, since it will work correctly even code needs multiple StringBuilder objects with non-overlapping lifetimes or if instances get abandoned.

Out of curiosity, do you know have any idea to what extent the authors of various Framework classes sought to optimize for "create and abandon" versus "cache and recycle" usage patterns? Given that the most common usage pattern for StringBuilder is to append a few strings, call ToString, and then not do anything else, an implementation with four backing fields for the first four strings and a String[] field for use in case there are more than four would require only a fraction of the allocations of an implementation using a Char[] or a "rope" as its backing store. If such a type were used and recycled, though, having special-case code for the first four strings would become counterproductive once a String[] backing store was allocated.
May 19, 2014 at 7:38 AM
Nice article and thanks for sharing your experience! In our products we are constantly tracking and optimizing excessive allocations and it adds a lot to application responsiveness and overall performance. Boxing, closures, delegates, paramarrays, linq, async, iterators - all these constructs add to language expressiveness on the one hand and add memory pressure on the other hand. In real world applications (not a compiler) you can't prohibit such 'inefficient' code. Which means you end up in using profiling tools to optimize hotspots. I would add references to third-party tools like memProfiler (which is combines power and flexibility in tracking allocations), dotMemory (yes, this is our product and it is quite fast and can show everything you need to optimize hotspots) and may be a tool for complete geeks like this one http://resharper-plugins.jetbrains.com/packages/ReSharper.HeapView/0.9.1 which shows all possible sources of allocations just in VisualStudio editor.
Developer
May 19, 2014 at 3:51 PM

Thanks for the pointers. I'll look into adding more refs to third-party tools, especially since one of the points is there's nothing like good tools :-).

Bill

May 19, 2014 at 4:18 PM
serjic_shkredov wrote:
In real world applications (not a compiler) you can't prohibit such 'inefficient' code. Which means you end up in using profiling tools to optimize hotspots.
I've sometimes wished for various compiler options to, within marked contexts, forbid various constructs which result in "hidden" allocations; if a piece of code was marked as "performance critical", then implicit boxing or closure generation would generate compiler errors (lambdas which can be resolved into static methods would remain legal, and would explicit casts from value types to reference types).

On a related note, I wish there were a way to mark certain methods overloads as expressly forbidding boxing or other representation-changing conversions. While it would have been better to have done so from the start, adding such tags even to some pre-existing methods shouldn't pose any real compatibility problem if there were a compiler option to disable their effect, especially if the system could make certain exceptions. For example, code which passes a value type to a primitive's Equals(Object) overload is almost always wrong, and there are only two cases where the behavior might be as intended.
  1. When the purpose of the code is to demonstrate e.g. that (1.0f).Equals(1.0) is false, even though (1.0).Equals(1.0f) is true (a warning would not prevent such demonstration).
  2. When the the primitive is being compared to a generic value type (e.g. someFloat.Equals(someGeneric)) such code might actually behave as intended, but might perhaps be more clearly written as someFloat.Equals((Object)someGeneric)), (typeof(T) == typeof(float) && someFloat.Equals((Object)someGeneric))), or more efficiently written as StrictComparer<float,T>.Equals(someFloat, someGeneric) [StrictComparer<T,U> would be a general-purpose helper class with a static property Equals of type Func<T,U,bool>; the first call for a given set of types would require Reflection, but subsequent calls would use a cached delegate. Performance testing reveals this approach to be much faster than boxing,.] I would consider a warning for someFloat.Equals(someGeneric)) reasonable, since it would be entirely possible the programmer wasn't intending boxing, or was intending that the method should yield a true comparison in case e.g. someGeneric was an integer equal to 1. This might a breaking change one some project compiled with "stop on warnings", but fixing the warning while preserving the same semantics would be easy (add an explicit Object cast), and from what I understand it is considered reasonable to impose such a burden on those who use "stop on warnings".
May 19, 2014 at 4:29 PM
supercat wrote:
serjic_shkredov wrote:
In real world applications (not a compiler) you can't prohibit such 'inefficient' code. Which means you end up in using profiling tools to optimize hotspots.
I've sometimes wished for various compiler options to, within marked contexts, forbid various constructs which result in "hidden" allocations; if a piece of code was marked as "performance critical", then implicit boxing or closure generation would generate compiler errors (lambdas which can be resolved into static methods would remain legal, and would explicit casts from value types to reference types).

On a related note, I wish there were a way to mark certain methods overloads as expressly forbidding boxing or other representation-changing conversions. While it would have been better to have done so from the start, adding such tags even to some pre-existing methods shouldn't pose any real compatibility problem if there were a compiler option to disable their effect, especially if the system could make certain exceptions. For example, code which passes a value type to a primitive's Equals(Object) overload is almost always wrong, and there are only two cases where the behavior might be as intended.
  1. When the purpose of the code is to demonstrate e.g. that (1.0f).Equals(1.0) is false, even though (1.0).Equals(1.0f) is true (a warning would not prevent such demonstration).
  2. When the the primitive is being compared to a generic value type (e.g. someFloat.Equals(someGeneric)) such code might actually behave as intended, but might perhaps be more clearly written as someFloat.Equals((Object)someGeneric)), (typeof(T) == typeof(float) && someFloat.Equals((Object)someGeneric))), or more efficiently written as StrictComparer<float,T>.Equals(someFloat, someGeneric) [StrictComparer<T,U> would be a general-purpose helper class with a static property Equals of type Func<T,U,bool>; the first call for a given set of types would require Reflection, but subsequent calls would use a cached delegate. Performance testing reveals this approach to be much faster than boxing,.] I would consider a warning for someFloat.Equals(someGeneric)) reasonable, since it would be entirely possible the programmer wasn't intending boxing, or was intending that the method should yield a true comparison in case e.g. someGeneric was an integer equal to 1. This might a breaking change one some project compiled with "stop on warnings", but fixing the warning while preserving the same semantics would be easy (add an explicit Object cast), and from what I understand it is considered reasonable to impose such a burden on those who use "stop on warnings".
Wouldn't such analysis belong to tools like FxCop instead?

I don't think the compiler should be involved in such analysis.
Developer
May 19, 2014 at 4:35 PM

I added Alex who is driving the Roslyn diagnostics work (he might chime in), and I believe warnings such as proposed can or would be handled one day along the lines of FxCop like checks. I'd also point out, if I’m understanding, that baking such warnings into the compiler and having flags that say I'd like this piece of code to be efficient smacks a bit of premature optimization. Maybe the marked code has nothing to do with any large scale hotspot, but the compiler warnings will have caused the developer to write potentially more complicated code to maintain.

Bill

May 19, 2014 at 6:00 PM
Edited May 19, 2014 at 6:01 PM
PauloMorgado wrote:
Wouldn't such analysis belong to tools like FxCop instead?
The compiler knows when it is generating boxing instructions or performing type conversions. As such, all that would be necessary for the compiler to squawk when such things are being used in a fashion contrary to the programmer's intention would be for it to parse directives that would indicate such intention.
I don't think the compiler should be involved in such analysis.
I would posit that, as a matter of general philosophy, in cases where an implicit code transform would sometimes yield intended behavior and sometimes not, a language should provide a means by which the programmer can specify whether it would be intended or not. For almost every kind of implicit transform one can imagine, there are some cases where it would match programmer's intention and others where it would not. Right now C# is riddled with places where language writers thought the probability of an implicit transform having unintended behavior was sufficiently high as to justify forbidding its use everywhere, even though in some contexts it would be useful if allowed. It's also riddled with places where implicit transforms can turn code which wouldn't compile without the transforms into compilable buggy code. I would posit that a simple solution to both these problems would be to provide means of specifying when certain transforms should or should not be applicable.

I tools like FxCop as being a proper way to examine things which would require analysis beyond what a compiler would be expected to perform. On the other hand, since the compiler already has to determine if a method argument requires boxing or other implicit conversion, and already has to examine method arguments for various other attributes, checking for e.g. an AllowConversions(ArgumentConversionFlags) attribute before generating boxing code would hardly seem like the sort of extensive analysis which should require a tool like FxCop. Further, having the compiler handle such things itself would be helpful in cases like:
string formatDecimal([AllowConversions(ArgumentConversionFlags.None)] float val) {...}  // Show nearest decimal representation for `float`
string formatDecimal(double val) {...}; // Show up to 15 significant figures
If code attempts to perform formatDecimal(87654321), the double overload would yield precise and correct results, but in the absence of any flags to indicate otherwise the compiler would instead use the vastly inferior float overload. While it would be possible to explicitly specify a long overload to prevent implicit conversions from integer types to float, that would only work as long as no longer integer type was available. If C# were to add a 128-bit veryLongInt, whose implicit conversion rank was between long and float, values of that type would--absent the aforementioned attribute--get converted to float rather than double. I don't see tools like FxCop as being helpful in dealing with situations like the above; the proper remedy would instead be to provide means by which programmers can specify what kinds of implicit transforms they want the compiler to accept or reject in various situations.
May 19, 2014 at 6:14 PM
billchi wrote:
Maybe the marked code has nothing to do with any large scale hotspot, but the compiler warnings will have caused the developer to write potentially more complicated code to maintain. Bill
The purpose of the markings would be to ensure that code does not accidentally get changed in a fashion which would vastly affect its performance characteristics. If a change to the code causes the compiler to squawk, the maintainer should be able to determine whether:
  1. The change wasn't actually doing what was intended, in which case it should obviously be corrected.
  2. The change could easily be made in an alternate fashion which was essentially as clean, but would avoid an unnecessary allocation.
  3. It would be awkward to make the change in such fashion as to avoid the allocation, in which case the programmer could edit the tag to allow the allocation but would know that the code needed to be re-profiled. The results of such profiling would then indicate whether the change should remain, or be redone in a possibly-more-awkward fashion to avoid the extra allocation.
I would not expect the tags to be limited to identified performance hotspots, but rather employed in things which had the potential to become performance hotspots. I would not expect programmers to run a full performance suite on their code every time they make a change, and finding out after making a bunch of changes that some aspect of the program has become slower (especially if it's a slowdown which might not be noticed immediately), but having no clear indication as to which change had that effect, isn't much fun. Getting a "heads-up" that "This code has been changed in such a fashion that it's likely to run slower than it did before, and should be checked to ensure that it hasn't become a performance hotspot" could do a lot to avoid such situations.
May 22, 2014 at 10:44 AM
Edited May 22, 2014 at 7:49 PM
Thanks for posting this, it's a really good and useful read.

It's nice to see an example of a large code-base (from Microsoft) that shows how to deal with these type of things, especially GC pressure. I've worked on projects that have a "no allocations in the hot-path" rule, but it was hard to enforce. Making sure "using System.Linq" isn't present is a good start though!!

BTW the link at the bottom titled "A Few C# and VB Performance tips" seems to be broken, should it point to http://msdn.microsoft.com/en-us/library/ms973839.aspx instead?
May 22, 2014 at 11:10 AM
In the section "Classes vs. Structs" you mention that Structs can save memory overhead compared to Classes, but they have an trade-off cost because they need to be copied across when used a function arguments.

It might be worth noting that you can get the best of both worlds by passing them "by ref", so you don't have the overhead of a copy. Although with the usual warning/caveat that "mutable structs are evil"!!
May 23, 2014 at 2:10 AM
MattWarren wrote:
It might be worth noting that you can get the best of both worlds by passing them "by ref", so you don't have the overhead of a copy. Although with the usual warning/caveat that "mutable structs are evil"!!
Mutable structs do not behave like objects--they behave like aggregates (bunches of variables stuck together with duct tape). Some people think that everything should behave like objects, and thus regard as evil anything which does not. My own belief is that efforts to pretend that "everything is an object", when such a belief is not consistent with the way the CLR actually works, create needless complication. Aggregates which behave as simple aggregates are easier to understand than aggregates which try to behave like objects [among other things, all simple aggregates work the same way].

Code which needs immutable value objects should follow the MS struct-versus-class guidelines, but the guidelines are totally inapplicable to aggregates. If something should behave as an aggregate with twelve float values that need to be independently manipulable in various combinations, it should be an exposed-field structure. Using a mutable class where an aggregate is desired is a recipe for disaster. An immutable class can be used to poorly emulate an aggregate, but the bigger the aggregate, the worse the emulation. An immutable structure can be used to emulate an immutable class which is doing a poor emulation of an aggregate, but unless code may have to be refactored to do something an exposed-field struct can't do, and starting out settling for a poor emulation of an aggregate may be better than having to refactor code later to use one, it's often for code which needs a simple aggregate type to simply use one, rather than trying to use a structure which enumates a class object which is doing a poor job of emulating the behavior of a structure type.
May 23, 2014 at 11:18 AM
serjic_shkredov wrote:
... and may be a tool for complete geeks like this one http://resharper-plugins.jetbrains.com/packages/ReSharper.HeapView/0.9.1 which shows all possible sources of allocations just in VisualStudio editor.
Thanks for sharing that, I've just been playing around with it. It's a really nice tool, it gives some really useful info.
May 28, 2014 at 8:38 AM
supercat wrote:
Mutable structs do not behave like objects--they behave like aggregates (bunches of variables stuck together with duct tape). Some people think that everything should behave like objects, and thus regard as evil anything which does not. My own belief is that efforts to pretend that "everything is an object", when such a belief is not consistent with the way the CLR actually works, create needless complication. Aggregates which behave as simple aggregates are easier to understand than aggregates which try to behave like objects [among other things, all simple aggregates work the same way].
I don't disagree, but I think that the "mutable structs are evil" guideline is to protect people from shooting themselves in the foot, rather than anything else. It's more like, "using structs can be useful and more efficient, but they don't always work the way you expect. So make them immutable, unless you really know what you are doing!".

I would say that the majority of programmers don't understand the CLR to the level you do, so they could create a lot of subtle bugs if they use structs incorrectly.
May 29, 2014 at 5:16 PM
MattWarren wrote:
I would say that the majority of programmers don't understand the CLR to the level you do, so they could create a lot of subtle bugs if they use structs incorrectly.
It's too bad C# used the same syntax for struct field access and class field access, since the use of different syntax would help affirm programmer intentions.

Otherwise, I would posit that the possibilities for bugs with value types are less severe than with mutable class types. If Point3d is an exposed-field structure, and MyList is a List<Point2d>, how would one increase by 1.0 the X value encapsulated by MyList[0], and make MyList[1] encapsulate the same position? I would suggest:
var temp = MyList[0];
temp.X += 1.0;
MyList[0] = temp;
MyList[1] = temp;
If it's a class type, then perhaps the right approach is:
var temp = MyList[0];
temp.X += 1.0;
MyList[1].X = temp.X;
MyList[1].Y = temp.Y;
MyList[1].Z = temp.Z;
or perhaps
var temp = MyList[0];
temp = new Point2d(temp.X+1.0, temp.Y, temp.Z);
MyList[0] = temp;
MyList[1] = temp;
or maybe one should be semi-paranoid and do
var temp = MyList[0];
temp = new Point2d(temp.X+1.0, temp.Y, temp.Z);
MyList[0] = temp;
temp = new Point2d(temp.X, temp.Y, temp.Z);
MyList[1] = temp;
I'd say the possibilities for getting things slightly wrong when using mutable classes are much worse than when using structures, which are more likely to either work or not.
Coordinator
May 31, 2014 at 12:09 AM
With Roslyn-based Diagnostic analyzers, it would be interesting to explore some sort of [HotPath] attribute that you could apply to methods or types that you wish to hold to the higher standard that supercat and Matt Warren are describing. Anywhere you fall off the wagon and have hidden allocations in such contexts would then be called out with a warning. You could then safely use LINQ in such projects, confident that any perf gotchas won't bite you in your perf-sensitive codepaths.

I could see a compiler warning about this type of thing if what you're doing involves primitive types and is clearly wrong from a perf perspective. Once you get into either specific contexts (with attributes) or false positives that you opt-in to so you can work through each possible issue, the rules make more sense to me as opt-in Diagnostic analyzers that you add to your project.
May 31, 2014 at 8:00 PM
AlexTurnMSFT wrote:
With Roslyn-based Diagnostic analyzers, it would be interesting to explore some sort of [HotPath] attribute that you could apply to methods or types that you wish to hold to the higher standard that supercat and Matt Warren are describing. Anywhere you fall off the wagon and have hidden allocations in such contexts would then be called out with a warning. You could then safely use LINQ in such projects, confident that any perf gotchas won't bite you in your perf-sensitive codepaths.

I could see a compiler warning about this type of thing if what you're doing involves primitive types and is clearly wrong from a perf perspective. Once you get into either specific contexts (with attributes) or false positives that you opt-in to so you can work through each possible issue, the rules make more sense to me as opt-in Diagnostic analyzers that you add to your project.
Hey, great minds think alike, I've been working on exactly this since the idea got posted higher up this thread, you can see the (work in progress) code here. At the moment it just works standalone, but next on my list is to use the Roslyn VS extension points and implement a Diagnostic Analyser. I hadn't thought about allowing [HotPath] attributes, that's a good idea.

I must also give credit to the Resharper HeapView plugin (source code) that I'm verifying my code against, it does a very good job of identifying heap allocations.

At the moment the code uses a mixture of Roslyn and Cecil, it does the following:
  1. Parses the code using Roslyn and CSharpSyntaxTree.ParseText(..)
  2. Emit the IL and PDB (in-memory) using CSharpCompilation.Emit
  3. Processes the IL using Cecil (couldn't see a way to do this using Roslyn)
  4. Scans the IL, looking for the following:
    1. Box and Newarr IL instructions (easy)
    2. Newobj instruction (but only for reference types)
    3. Callvirt instruction
      1. To identify when GetEnumerator is called and it returns a Value type (that will then be boxed)
      2. To see when GetHashCode() is called on a Value type that doesn't override it (have to also find the constrained IL instruction)
Basically I'm trying to identify all the issues outlined in this talk (slides). You can see the corresponding unit tests and they all currently pass
Jun 11, 2014 at 8:32 AM
I have read the article and also watched this Dustin Campbell's talk. And I wonder - can Roslyn mitigate some of the boxing pain points on behalf of user? Surely c# compiler did such thing before - e.g. foreach is implemented in a way which keeps boxing out. There are some obvious, simple cases where compiler also have full necessary knowledge and can help with user code. These are 3 examples taken from above talk:
string foo, bar;
var s = foo + ':' + bar;
int a, b;
var s = string.Format("{0}:{1}", a, b);
MyEnum e;
int hash = e.GetHashCode();
In the first case, the fix is so simply, and it can never hit you back:
var s = foo + ":" + bar;
Eventually, it can be addressed with proper diagnostics + code fix.

But in the cases 2 and 3, you really don't want to write code like below, as it hurts readability a bit:
var s = string.Format("{0}:{1}", a.ToString(), b.ToString());
and
int hash = ((int)e).GetHashCode();
You can say - measure and don't write "correct" code if the simple version does not create perf issues for you. True. But why not to address it at compiler level? If compiler could insert missing .ToString() calls and casting, we could have both - more readable code and no perf issues related to boxing in such cases. Also, these examples are taken from Roslyn itself, so Roslyn codebase could also have some gains here.
Why Roslyn team decided to address them at code level, and not incorporate into compiler itself? It is all about putting dev into a pit of success after all.
Jun 11, 2014 at 5:50 PM
Fixing the ToString example would require that the compiler know how String.ToString is going to parse its first argument; consider, for example, that if the format were {0} ({0:X8}}, it would be necessary to either box the integer, or call ToString on it twice). As for calling GetHashCode() on an enum, I would think that should be handled by having the type loader auto-generate certain methods for types which derive from System.Enum and do not define such methods themselves. Otherwise, in the event that in the future types derived from enum are ever allowed to define their own hashing methods, the requested compile-time substitution would break. BTW, I think the type loader should do something similar with ValueType as well; if every value type which didn't define an override for Equals was given a default public override equals(Object other) { return ValueTypeEqualsHelper<typeof(this)>.ValueTypeEquals(ref this, other);} it would be possible to implement a static generic ValueTypeEqualsHelper class which would, upon construction, construct a comparer object which would efficiently perform the necessary comparisons about as fast as a custom-written method (if the static class construction could occur before the class loader JITted the Equals method, it might be able to inline the call to the helper method, and achieve speed as good as or--in cases where a bitwise comparison would suffice, better than--custom-written methods that simply compare fields).
Jun 12, 2014 at 9:58 PM
Briefly:
@supercat: The thread-static access costs pale into insignificance compared to a GC. It's all about avoiding an allocation that might trigger a GC. You can see that the .Net Framework also chose this approach: http://bit.ly/1jqewvK
You could do it without the thread-static, but using Interlocked operations instead. You can see that in action elsewhere in Roslyn. In particular take a look at the ObjectPool<T> implementation: http://bit.ly/RxqpoI
I wrote a blog post based on this comment and some digging around the Roslyn source code, it's available at the link below if anyone is interested.
Roslyn code base performance lessons
Jun 13, 2014 at 6:29 PM
MattWarren wrote:
I wrote a blog post based on this comment and some digging around the Roslyn source code, it's available at the link below if anyone is interested.
Roslyn code base performance lessons
One of my thoughts when I read that was that it's too bad there's no way in .NET to declaratively ensure that when an object is returned to a pool no other reference will exist. Then I was thinking about what would be required to offer such a guarantee, and realized it might not be all that hard for things only used within an assembly; if the framework could allow a type to be coded in such a way as to only be usable by languages which "understand" it, and language groups could agree upon how that should be done, the types could be usable between assemblies as well.

Basically, what I'd like would be means by which a structure type could be marked as a "lunk", in such a way as to forbid code not contained within the structure itself from doing anything with it except creating a default-initialized local variable of the type, invoking its members, assigning it to a new expression (which really asses it by ref to the constructor) or passing it around as a ref parameter, If PoolableStringBuilder were a lunk, then the type could ensure that it would be impossible for a StringBuilder from the pool to get shared between threads, or for a thread to return a StringBuilder to the pool while other references could exist. For code to use a PoolableStringBuilder, any client code which would expect a StringBuilder would have have an overload that could accept a ref PoolableStringBuilder instead, but "lunk" types would facilitate "escape analysis" [if the code for the lunk itself doesn't let a reference escape, it won't escape]. Lunk types could in some cases offer implicit or explicit conversions to related non-lunk types [e.g. a PoolableStringBuilder might include an explicit conversion to StringBuilder which would return the encapsulated StringBuilder after setting a flag to indicate that the Dispose method should abandon the StringBuilder without adding it back to the pool.

Beyond the restrictions on assignment or other such uses, the biggest semantic change necessary for C# would be to change the behavior of using so that when invoked on a lunk, its Dispose method would operate on the controlled variable directly rather than a copy of it. Since lunk types couldn't be passed as unconstrained generics, that shouldn't be difficult.

What would you think of lunk types to help facilitate pooling?
Developer
Jun 16, 2014 at 7:40 PM
@Przemyslaw
Yes Roslyn can definitely help here. It would be easy to write Roslyn a diagnostics analyzer to find possibly unintended allocations and make fixes - much like the HeapView Resharper plugin that has been mentioned.

@supercat
The "Enum.GetHashCode" boxing has been fixed in .Net 4.5.2 and I've been working closely with the .Net Framework BCL team to get more of these kinds of fixes into future versions of the libraries.
The "lunk" idea is interesting. It's a special case of a more general feature that restricts access or mutation via escape analysis. I think there's a lot of merit in that feature and, indeed, it's something that one of our internal teams has been exploring.
Jun 16, 2014 at 8:52 PM
pharring wrote:
The "lunk" idea is interesting. It's a special case of a more general feature that restricts access or mutation via escape analysis. I think there's a lot of merit in that feature and, indeed, it's something that one of our internal teams has been exploring.
Has anything been written up regarding such exploration? One of the biggest semantic weakness in .NET and Java is the lack of any means of distinguishing different kinds of sharable and unsharable references to objects. If a framework were designed from the bottom up with such distinctions in mind, a runtime could auto-generate reasonably-efficient and semantically-appropriate comparison, hashing, and cloning methods for 99% of the types used to encapsulate values. Since .NET obviously wasn't designed that way, facilities for auto-generating such methods will be limited, but the more effectively such distinctions can be expressed, the more effectively tools will be able to catch changes to code that might inadvertently alter the semantics.
Oct 13, 2014 at 6:49 PM
Reddit brought me here (http://www.reddit.com/r/programming/comments/2j2rh7/roslyn_c_based_heap_object_allocation_analyzer)

Why don't we move these discussions and code to GitHub it's very inconvenient to participate here completely one-off.

I'll ask my question here again: Isn't this stuff the compiler should do for you? And/or why not?