4

I want to use a pool of non-thread-safe encoder instances across multiple concurrent ASP.NET requests, without reinitializing them. If I didn't care about initialization costs, I'd do this:

public class ResultController {
    public JsonResult GetResults() {
        List<MyViewModel> items = new List<MyViewModel>();
        // It obviously does more than this in real life
        for(id = 0; id < 1000; id++) {
            items.Add(new MyViewModel(id));
        }
        return Json(items);
    }
}

public class MyViewModel() {
    public string CodedId { get; set; }
    public MyViewModel(int id) {
        // This "new" is the concern
        CodedId = new ExpensiveToInitializeCodec().InexpensiveEncode(id);
    }
}

Works fine. All locals, no worries about threading, and no-one outside my model needs to understand that the value is presented encoded. However, a quick performance test shows that each initialization takes about .129ms, while an encode itself takes less than .006ms. (FYI, the codec is TripleDESCryptoServiceProvider).

I want to limit that initialization cost without passing pre-initialized objects around (such as into constructors to improve performance, but breaking separation of concerns). Here's what I currently do, and it obviously gets messy outside this simple example:

public class ResultController {
    public JsonResult GetResults() {
        List<MyViewModel> items = new List<MyViewModel>();
        ExpensiveToInitializeCodec codec = new ExpensiveToInitializeCodec();
        for(id = 0; id < 1000; id++) {
            items.Add(new MyViewModel(id, codec));
        }
        return Json(items);
    }
}

public class MyViewModel() {
    public string CodedId { get; set; }
    public MyViewModel(int id, ExpensiveToInitializeCodec codec) {
        CodedId = codec.InexpensiveEncode(id);
    }
}

I could leverage the well-known ASP.NET per-request cache pattern:

public class MyViewModel() {
    public string CodedId { get; set; }
    public MyViewModel(int id) {
        CodedId = ExpensiveToInitializeCodec.Get().InexpensiveEncode(id);
    }
}

public class ExpensiveToInitializeCodec {
    public static ExpensiveToInitializeCodec Get() {
        ExpensiveToInitializeCodec codec = HttpContext.Current.Items["codec"];
        if (codec == null) {
            codec = new ExpensiveToInitializeCodec();
            HttpContext.Current.Items["codec"] = codec;
        }
        return codec;
    }
}

But that's still wasteful to run in a tight loop: How much computation is behind a HttpContext.Current call?

It also seemed like a per-thread solution may be more precise than a per-request solution. Any suggestions that are compatible with an ASP.NET request?

Outside of ASP but still in the .NET space, one person's answer was ThreadStatic: Using ThreadStatic to replace expensive locals -- good idea? . However, http://blog.idm.fr/2010/03/aspnet-thread-agility-or-why-threadstatic-should-not-be-used.html apparently precludes that solution in ASP.NET. A similar question to mine Is there any way to imitate ThreadStatic for use with HttpContext.Current.Items? went unanswered.

EDIT: It looks like I may be able to use ThreadStatic if I make sure my codec usage isn't interleaved with I/O operations, but I'm not sure how safe that is.

I'm reaching now, but some other approaches I've thought of include 1) providing a custom serialization/deserialization for items with an EncodedAttribute, 2) implementing my own static TripleDES single-block encryptor with no instance initialization overhead, 3) against my preferences, passing an externally-initialized encryptor into the constructor for each item, 4) implementing an IEncryptable interface and re-enumerating the items after the results are populated, 5) perform all encryption external to the viewmodel, implemented everywhere the viewmodel is used.

Community
  • 1
  • 1
shannon
  • 8,664
  • 5
  • 44
  • 74
  • What do you mean by: "without passing pre-initialized objects around (and breaking separation)." Does that mean you don't want a 'resource pool' of these objects? If not, I don't understand the objection. – ZeroBugBounce Aug 08 '12 at 20:21
  • @ZeroBugBounce : No, a resource pool of these objects would work great. What I don't want, is to be creating a shared utility object in a parent class, just to pass it in for the initialization process of all the children that will be attached to that parent, sacrificing separation of concerns for performance. That's what I'm currently doing. I have both multiple child classes and multiple parent classes that require this feature, and it's a tangle. I added a code example. – shannon Aug 08 '12 at 21:13
  • Are you saying the actual instances between parent and child need to be coordinated? If not, could the parent and child simply check out their own instances from a shared pool and return them when done? Or if so, do you have some kind of IoC container that could do the work of coordinating the checkout on a per request basis? – ZeroBugBounce Aug 08 '12 at 21:19
  • @ZBB: No need to be coordinated. A worker object pool may be exactly the right solution. I haven't ever built one. Synchronization doesn't scare me, but it seems to me that accidentally saturating it would be the big risk and take some care and code to prevent. http://stackoverflow.com/questions/2510975/c-sharp-object-pooling-pattern-implementation . Thanks for the idea, I'm reading up on it. – shannon Aug 09 '12 at 00:58

1 Answers1

1

Is the performance cost of accessing HttpContext.Current.Items really an issue or you are just speculating? According to the numbers you gave and the numbers in the question you gave and then numbers in the linked question it should not be: - You have 129 ms for single initialization + 6ms for 1000 iterations - HttpContext takes like 0.1ms over 10K iterations (i.e. 0.01 in your case)

Thread static is a bad idea. While it may work you will not be able to use async controllers and similar features in the future.

Stilgar
  • 22,354
  • 14
  • 64
  • 101
  • Thank you. Yes, I was speculating just based on a couple articles, although I have a test in the works on that. I'll update. – shannon Aug 09 '12 at 01:01
  • You are correct. 10000 iterations of the codec from a saved instance ran 60ms, and adding the cache lookup added 1ms. Inconsequential relative to the 1.5 seconds initialization would take. Thanks for the reply. – shannon Aug 13 '12 at 19:44
  • 1
    Implemented the current context cache without any apparent load test changes. Not sure how I misread that article about it I linked. I thought it implied there was a heavier cost. – shannon Aug 13 '12 at 20:13