Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

dcook

dcook dcook

Niner since 2007

Grew up in Idaho. Earned a living by driving tractors. Graduated from Skyline High School in 1995. Served a mission for the LDS church in Los Angeles, CA (Spanish speaking). Many random computer programming summer internships and on-campus jobs. Graduated in 2001 with a B.S. in Computer Science from BYU (magna cum laude and university honors, if you care about that sort of thing). Started working for the Windows CE Tools team I'm still there after 6 years.
  • Stephan T. Lavavej: Digging into C++ Technical Report 1 (TR1)

    C++0x shared_ptr Object Creation support (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm#creation ), voted into the Working Paper, will provide the efficiency advantages of intrusive reference counting without the usability penalties, by allocating a single chunk of memory for the object and its reference count.  Doing this properly requires variadic templates and rvalue references to solve the forwarding problem.

    Cool! Good to know.
    > It is often quite expensive to track items that
    > need to be deleted

    Remember: most objects in most C++ programs will NOT be immediately held by shared_ptr.  Automatics, members, and elements of containers incur no such expenses.  shared_ptr is the icing, not the cake.
    Ok, sure, most resources don't have to go into shared_ptr. But that wasn't what I was saying. The fact remains that tracking object lifetime is not free. shared_ptr makes it easier in some cases, and potentially even makes it more efficient, but with or without shared_ptr, tracking object lifetime is still a non-trivial cost, and manually tracking it (even with shared_ptr) is hard to get right. To repeat myself, several studies have found that 30-60% of a typical non-trivial program's CPU and memory usage is spent tracking and managing object lifetime (the kind of thing that goes away with GC), and that doesn't count the time spent in delete. You cited a particular paper as an argument that GC has lousy performance, but that paper ignored the costs of manual lifetime tracking (it assumed they were 0), so the paper's conclusions need to be adjusted appropriately before they are used to make any decisions. Even automatics, members, container cleanup and destructors have a cost, and the IF statements used to control flow and do cleanup at function exit also have a cost. This cost has to be factored into any comparison with GC's performance, and once that cost has been factored in, manual lifetime management and GC end up being fairly close in runtime cost.
    (I find most managed-talk incomprehensible, and I've found it hard to explain the native ways of doing things to managed programmers).
    I find it very frustrating when people who only have experience with one side of things criticize the other way without understanding it (and trying it for a while) first.
    It may seem easier to be able to ignore whether something is owning or not - but the price is being unable to handle non-memory resources properly.  That price is absolutely unacceptable to me.
    If that were actually the price, it would be unacceptable to me (and everybody else) too. Fortunately for managed runtimes, that isn't actually the case. There is a price, but it isn't nearly as dramatic as you make it out to be. With the current GC-based runtimes, critical resources need to be tracked separately from object lifetime. In C++, the release of critical resources is conflated with object destruction. That has some advantages, but it is not the only way to do it.

    The "managed way" is to let the runtime handle object lifetime and let the developer handle the release of critical resources, with finalization as a backstop in case the developer screws up. This assumes that there are a lot more objects than critical resources, and that seems to be true in my experience. There is certainly some room for improvement in the "managed way", but it certainly isn't an abomination or a catastrophe. If it were, nobody would be using it.
    My primary argument against GC is that it doesn't do anything for non-memory resources (finalizers are an abomination).
    Ah, now we're getting somewhere. You're right -- GC itself doesn't do much for non-memory resources (though it does do something -- it gives you a chance to do something at some point after the object becomes unreachable). GC solves an object lifetime issue and is mostly agnostic about critical resources. (Note that "non-memory" is not the best description here, since there are some memory-based resources that must be freed deterministically, and there are some non-memory resources that don't need deterministic release.)

    So basically, I think it boils down to this: with GC, you get automatic object lifetime management, but there is no support for deterministic critical resource management (finalizers are the backstop so that critical resources do eventually get freed, but they are tricky and should be used as little as possible). With C++, you have to manage object lifetime yourself, but you get a lot of help from the language and the runtime, and critical resource management is tied to the object lifetime. Neither system is perfect. Everybody will have a preference. But neither system is fatally flawed.

    Another thing I'd like to point out is that the real issue here is provable type safety, which actually can't be acheived in the general case with manual or RAII-based resource management. If there's any way for a program to access a type that has been released, type safety goes out the window. That's the real limitation here. If this were just an issue of garbage collection, it could be resolved by various clever schemes that allow both GC and non-GC resources to co-exist. However, since the actual goal is type safety (which is a very nice thing!), those clever schemes don't work very well.

    There is still some room for other clever schemes -- using, finally, and IDisposable are a start, but I would love to see more support from the language, runtime, and code analysis tools.

    Calling finalizers an abomination doesn't make it so. They are best avoided as much as possible, but they aren't inherently evil, and there are (rare) cases when letting the finalizer take care of things is really the best thing to do. If abused, they can certainly cause you no end of trouble. But the same goes for a lot of other things in software development. I'm not sure what makes them deserve the label "abomination". Is it the sharp edges? shared_ptr has a few sharp edges of its own -- accidentally turning a raw pointer into a shared_ptr twice leads to double-free, use of shared_ptr as a temporary leads to trouble, etc.
  • Stephan T. Lavavej: Digging into C++ Technical Report 1 (TR1)

    I have to apologize for turning this into a GC-vs-refcount thread. I am overjoyed that Visual C++ is getting support for this, because it has been sorely needed for a long time. (I still prefer to use intrusive reference counting where possible, but that's another issue.)

    Nevertheless, I always find myself compelled to stand up for GC when I see people dismissing it for invalid reasons. It makes me think of people who refuse to use Windows because "Linux is teh roxxors and M$ sux". Just as there are places where Windows is better than Linux (and vice-versa), there are places where GC is appropriate and useful, and there are places where it is not. It's just another tool to have on hand, and I'm always looking for new and useful tools to make me a more productive developer.

    @neerajsi: YAY! Somebody who is willing to actually discuss the issues and keep an open mind. Very rare. Good for you.

    Regarding the GC vs. Malloc paper that STL has referenced, I take exception with the methodology used -- there are a number of fundamental flaws in their analysis that stack their deck against GC. That's not to say that GC is necessarily always better, just that it isn't nearly as bad as indicated in their paper.

    In the referenced paper, they took some traces from some programs that used garbage collection, then simulated the performance of the programs with various memory management mechanisms. This kind of simulation has a lot of potential and is very interesting, but their application of it misses some key issues. For example, it assumes that it is trivial for a non-garbage-collected system to track live/free resources (they simply inserted "delete" calls into the trace at the point where each object became unreachable), and this is patently false. It is often quite expensive to track items that need to be deleted (interlocked reference count updates, allocation of reference counts on the heap, keeping lists of items to be deleted, ownership tracking, more complicated error handling, etc.). Many papers have researched this issue, and in some cases for complicated systems, 50% or more of the code and data is devoted to tracking object lifetimes. The paper STL referenced assumed that all of this came for free, so the results are not entirely valid.

    There are many real-world cases where GC has led to some significant performance wins. Some server applications I've investigated had serious memory issues that turned out to be due to heap fragmentation. Nasty hacks and tricks were done to reduce heap fragmentation, including things like having two heaps and switching back and forth between them (allowing one heap to empty, essentially defragmenting it). After moving to GC, these issues simply went away, performance became significantly better all around, and no nasty hacks were needed. Other cases involve argument passing and complicated ownership management protocols, often involving unneccessary copies of long strings -- these all go away when you can let the GC take care of it.

    As an aside, I've never had a performance or usability issue in my NT programs turn out to be caused by GC, so using performance as a primary argument against GC seems pretty worthless. In terms of developer productivity, I've found using GC to be a huge win -- there are certainly some places where it takes a step back (we need something better than IDisposable and using!), but it takes 10 or 20 steps forward for each step back.

    As I said before, there are ways to come up with examples where GC is a huge win, and there are ways to come up with examples where manual or reference-counted resource tracking wins. This applies to performance, reliability, scalability, ease of implementation, and last but definitely not least, ease of integration with your existing systems. You have to keep an open mind, understand the pros and cons of each, and be able to choose the right tool for the job without letting personal biases get into the decision. I switch back and forth between C# and C++ on a daily basis, even within the same project. I try hard to use the right tool for the job. Generally, the right tool is C# with GC, and I think my results have been good.

    In general, here are my rules of thumb:

    If I can assume that .NET is installed on the target computer already, I am working in user mode on NT, the program I'm writing won't need to load and unload repeatedly (process creation is about 4X slower if .NET is involved, and that can be a signficant factor for programs that are run 1000s of times per minute), and I don't have any other specific compelling reason such as interoperability, I use C#; otherwise, I usually use C++. I really like C# -- the standard library, the GC, and the language make me much more productive, and performance is not a problem. I miss some things like templates and deterministic destructors, but the benefit far outweighs the cost for most cases.

    shared_ptr is great because it makes me much more productive in C++ for the cases where C# isn't appropriate. So I'm first in line to be happy that this and other much-needed libraries are being added.

    Nearly every time I talk to somebody who thinks GC is a bad thing, they haven't used C#/.NET/etc. for anything significant. They look at it, find a few things they don't like about it, and use them as the reason for not using it. They've let a personal bias color their judgement. This is known as prejudice. There are a few people who have used C# for major programs and still don't like it; these guys have usually had to work with a poorly-designed program -- perhaps somebody assumed at first that GC makes all problems go away, painted themselves into a corner with a bad design, and blamed the GC when the real problem was a poor program design. Finally, there are people who used C# when it wasn't appropriate. Again, they blame C# instead of realizing that no single tool is best for all jobs. You can find a number of bad C++ apps too, but nobody blames C++ for that. A lot of people hate X because they've run into problems with it, where X can be GC, reference counting, or one of any number of other technologies. Don't blame the technology if you run into trouble when it is misused.

    Just use the right tool for the job. What else can I say?
  • Stephan T. Lavavej: Digging into C++ Technical Report 1 (TR1)

    garbage collection is useless for anything other than memory.

    No. Garbage collection is useless for anything that cannot be released lazily based on reachability. Sometimes, not even memory fits here, but often many things other than memory works just fine.
    GC is simply not a replacement for ref counting no matter how much Patrick Dussud congratulates himself.
    True, but that doesn't mean it isn't useful. Use the right tool for the job. There are cases where reference counting is better. But in my experience, if a garbage collector is available to you, you'd be an idiot not to take advantage of it, and the garbage collector is the right tool for the job quite often.
    Cycles can be worked around but total lack of support for managing non memory resources in .net, can't be, you are back in the C world of malloc (new), free (dispose) with absolutely no help from the compiler/runtime.
    Not true. Reference counting can be used if you an appropriate and well-adopted smart pointer library (if you think malloc/free is bad, you should try getting different reference counting libraries to work together correctly). Cycles can be worked around if you are exceedingly clever, carefully meticulous, and have a good weak reference library available (again, have fun with the integration issues). Not to say it is unusable, just pointing out that it isn't as easy as pie (and if you think it is, you haven't done anything sufficiently complex with reference counting). And to make all of this work, you have to think very carefully about how to handle your destructors, make a class for every allocated resource, etc.

    Garbage collection isn't without its problems either, but to say there isn't compiler or runtime support is not entirely accurate. The compiler supports it with the lock, using, and try/finally constructs. The runtime supports it with the IDisposable interface. There are still two primary weaknesses (lack of "Disposable" containers, and inability to mark a type as disposable after it has been released).

    Performance is a completely separate discussion, and for every example you give me where reference counting wins, I'll give you one where garbage collection wins. Usability, programmer efficiency, and type safety are also areas where there are tradeoffs. There's always a pro and a con.

    Just use the right tool for the job. For c++, the arrival of shared_ptr in VC 9.0 is very welcome because it is the right tool for a lot of jobs. But garbage collection is also the right tool for a lot of jobs.
     Don't dismiss it just because you don't understand it (and if you're dismissing it, you really don't understand it).
  • JAOO 2007: Bob Martin and Chad Fowler - Debating Static versus Dynamic Typing

    I'm pretty sure that the answer (as usual) is: use the right tool for the job. Sure, that's kind of avoiding the question, but I am pretty certain that there are places where assembly is better than Ruby. Rare, but they exist. And I'm pretty sure there are places where Ruby is better than C#. Rare (I'm a C# zealot!), but I think they might exist. Tongue Out

    I'm glad they argued both sides, though. There really are two sides to the issue, and understanding both points of view makes it possible to make an informed decision.

    Just to be clear that this isn't an argument between strong versus weak typing (weak typing is often defined as "an object doesn't have a strong notion of its own type" or "everything is implicitly castable to anything else"): I can hardly think of any times when I really want weak typing. Maybe when intoducing my mom to programming, but that's about it. Luckily for the scripting languages, most of them use strong typing, so this isn't really an argument. It's an argument about static (a variable has a notion of the type of object it holds) versus dynamic (a variable can hold an object of any type).

    The argument about achieving polymorphism via inheritance is an interesting one, though I think that is not strictly an issue of static versus dynamic typing - it's more of an argument between type identification (interface versus duck) and inheritance models. Not all dynamically typed languages support duck typing. I suppose it could be considtered as an optional layer of dynamic typing, though some static languages have some aspects of this as well, just a bit more explicit.

    The weaknesses of polymorphism via inheritance are somewhat well known, which is one of the reasons why Interfaces seem to be growing in popularity over base classes. Interfaces avoid some of the problems raised. Single-base-class, multiple-interface inheritance models (like the CLR's) reflect this.

    To some degree, the distinction between static and dynamic typing isn't entirely clear. For example, you can do dynamic typing in many "statically typed" languages. C# has an object type, and you can use the Invoke APIs to perform dynamic name-based dispatch. All that is missing is syntactic sugar to make the dispatch look pretty.

    In any case, I like the structure of static typing. I find it lets the compiler enforce the rules that I want to follow anyway. I don't want those rules enforced by discipline or unit tests when they can be enforced by the compiler.

    I do like C# 3.0's var keyword, because it lets the compiler do some of the work for me while keeping almost all of the rule enforcement.

    For large, complex programs where you want as much error checking done up front, I think static typing makes sense. For small, quick programs, dynamic typing might be better.

    But that's just me. For everybody else, I suggest doing what works best for your situation. Play around with both kinds of programs and see what works for you and your team.

  • Understanding handle leaks and how to use !htrace to find them

    For the record, the "internal" version is essentially used for communication between the people maintaining the CHM. It doesn't have any "secrets" in it -- just boring stuff like "this needs to be cleaned up" or "can you check with Bob to see if there is an easier way to do this". Tongue Out