1

My financical software processes constantly almost the same objects. For example I have such data online:

HP 100 1
HP 100 2
HP 100.1 1
etc.

I've about 1000 updates every second.

Each update is stored in object - but i do not want to allocate these objects on the fly to improve latency. I use objects only in short period of time - i recive them, apply and free. Once object is free it actually can be reused for another pack of data.

So I need some storage (likely ring-buffer) that allocates required number of objects once and them allow to "obtain" and "free" them. What is the best way to do that in c#?

Each object has id and i assign id's sequentially and free them sequentially too. For example i receive id's 1 2 and 3, then I free 1, 2, 3. So any FIFO collection would work, but i'm looking for some library class that cover's required functionality.

I.e. I need FIFO collection that do not allocates objects, but reuse them and allows to reconfigure them.

upd

I've added my implementation of what I want. This is not tested code and probably has bugs. Idea is simple. Writer should call Obtain Commit methods. Reader should call TryGet method. Reader and writer can access this structure from different threads:

public sealed class ArrayPool<T> where T : class
{
    readonly T[] array;
    private readonly uint MASK;

    private volatile uint curWriteNum;
    private volatile uint curReadNum;

    public ArrayPool(uint length = 1024) // length must be power of 2
    {
        if (length <= 0) throw new ArgumentOutOfRangeException("length");
        array = new T[length];
        MASK = length - 1;
    }

    /// <summary>
    /// TryGet() itself is not thread safe and should be called from one thread.
    /// However TryGet() and Obtain/Commit can be called from different threads
    /// </summary>
    /// <returns></returns>
    public T TryGet()
    {
        if (curReadNum == curWriteNum)
        {
            return null;
        }
        T result = array[curReadNum & MASK];
        curReadNum++;
        return result;
    }

    public T Obtain()
    {
        return array[curWriteNum & MASK];
    }

    public void Commit()
    {
        curWriteNum++;
    }

}

Comments about my implementation are welcome and probably some library method can replace this simple class?

Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305
  • 16
    What you *need* is to ensure this is actually a bottleneck, ie. allocation and garbage collection. I can almost guarantee you that it isn't. Object allocation in .NET is very fast, and I doubt you can outsmart the GC guys in tracking objects which are no longer in use. Your first order of business: *performance measurement*. – Lasse V. Karlsen May 17 '13 at 12:27
  • 2
    Have you actually determined that GC is hurting your latency? And have you tried tweaking it? (There are various options available.) Have you considered using value types instead? – Jon Skeet May 17 '13 at 12:28
  • Relevant: [When to address managed heap fragmentation](http://stackoverflow.com/q/2600871) – H H May 17 '13 at 12:32
  • @LasseV.Karlsen when latency is a requirement, we should never ever allocate anything on the fly, it's by definition much more expensive than reusing object. – Oleg Vazhnev May 17 '13 at 12:34
  • Value types example: `Queue`, where `T` is not a class but `struct`. However beware of the strings - they are 'allocated' automatically. – Artemix May 17 '13 at 12:36
  • 2
    @javapowered not always; sometimes it is, sometimes it isn't. The generational collection model in .NET's GC means that allocs and collects for short-lived objects are both very very cheap. Probably cheaper than your custom code to recycle – Marc Gravell May 17 '13 at 12:36
  • 1
    @LasseV.Karlsen I will add a caveat there: for objects *known to have overhead*, pooling is desirable - for example, code that works with IO might want to have a recyling pool of `byte[]` buffers. For POCOs etc - as long as they are short-lived (i.e. gen-0), then: meh - allocate away! – Marc Gravell May 17 '13 at 12:37
  • @MarcGravell my custom code to recycle should be very simple. it's just ring-buffer around pre-allocated array, and that's it. i just looking for some implementation of this concept. – Oleg Vazhnev May 17 '13 at 12:37
  • 1
    @javapowered in particular keep in mind that alloc code almost certainly needs to be thread-safe; an `Interlocked.CompareExchange` loop over a short array should probably do the job, though - but again: whether this is a net gain *depends on the scenario* – Marc Gravell May 17 '13 at 12:39
  • Most of the implementation can be done simply by using a pre-allocated array and `Interlocked.Increment` to update the current index. The tricky part is finding an efficient way to reset the index when it gets larger than the collection's size. – Kevin Gosse May 17 '13 at 12:42
  • @KooKiz no, that's trivial; you use `uint` and modulo (`%`); you would, however, need to do removals via `Interlocked` too, to avoid handing the same object to multiple concurrent callers – Marc Gravell May 17 '13 at 12:43
  • @MarcGravell I was making the assumption that the list was big enough to avoid having the same objects used by two threads simultaneously. I don't believe it's a ridiculous requirement for a low-latency app, but that's indeed something to keep in mind. – Kevin Gosse May 17 '13 at 12:45
  • Your implementation is not thread-safe - the counter increments are unreliable, and you could give the same copy to multiple callers – Marc Gravell May 17 '13 at 15:07
  • @MarcGravell i have one reader and one writer, so everything should works fine. – Oleg Vazhnev May 17 '13 at 16:46

4 Answers4

5

I don't think you should leap at this, as per my comments on the question - however, a simple approach would be something like:

public sealed class MicroPool<T> where T : class
{
    readonly T[] array;
    public MicroPool(int length = 10)
    {
        if (length <= 0) throw new ArgumentOutOfRangeException("length");
        array = new T[length];
    }
    public T TryGet()
    {
        T item;
        for (int i = 0; i < array.Length; i++)
        {
            if ((item = Interlocked.Exchange(ref array[i], null)) != null)
                return item;
        }
        return null;
    }
    public void Recycle(T item)
    {
        if(item == null) return;
        for (int i = 0; i < array.Length; i++)
        {
            if (Interlocked.CompareExchange(ref array[i], item, null) == null)
                return;
        }
        using (item as IDisposable) { } // cleaup if needed
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • This is nicer than `ConcurrentBag`, especially seeing as this is lock-free and `ConcurrentBag` isn't. – Matthew Watson May 17 '13 at 13:10
  • it's good for big objects that expensive to allocate and when we have only several of them. but it is not good to store several thousands small of objects because of `for` loop inside. Well actually i can implement what i need myself using regular array, i just was thinking that there should be some library class. For example c++ boost has `ring-buffer` class that probably can do what i need.. – Oleg Vazhnev May 17 '13 at 13:21
  • @javapowered which is why you don't usually store thousands of small objects. What would be the point? A small buffer like this will typically remove *most* (not all) allocations of such objects, but: short lived objects are cheap to allocate **and** to collect. Frankly, by holding on to them you make them more expensive (by pushing them into later collection generations) – Marc Gravell May 17 '13 at 13:23
  • @MarcGravell i think array access by index is VERY fast and processor cache-friendly. so yes I can significantly speed-up application reusing objects from array instead of allocating them every time. – Oleg Vazhnev May 17 '13 at 13:24
  • @javapowered .Net *does* have something - ConcurrentBag. But it also has a good GC which renders all this other stuff less necessary. – Matthew Watson May 17 '13 at 13:36
  • thanks, i've modified your example for my needs, added it to question description. – Oleg Vazhnev May 17 '13 at 14:35
  • @javapowered note that your edited version is not thread-safe in any way : if this is used from multiple threads your version is very risky – Marc Gravell May 17 '13 at 15:07
  • @MarcGravell I have one writer and one reader (from different threads). also I guess my example can be modified a little bit to be completely thread-safe. – Oleg Vazhnev May 17 '13 at 15:10
3

If the loads come in burst, you may be able to use the GC's latency modes to offset the overhead by delaying collects. This is not a silver bullet, but in some cases it can be very helpful.

Brian Rasmussen
  • 114,645
  • 34
  • 221
  • 317
  • @javapowered Try it and measure. It doesn't prevent GCs from happening, but if you have bursts of usage followed by some quiet time, you may be able to delay cleanup. – Brian Rasmussen May 17 '13 at 17:08
  • 1
    i've found an example how to do that, adding it for reference http://stackoverflow.com/a/6005949/93647 However i'm not sure if I should use this in my application, i reuse almost all objects, trying to avoid `new` at runtime. Local variables are likely destructed with almost no GC-overhead. – Oleg Vazhnev May 19 '13 at 07:13
  • If you can avoid allocations, you're obviously in a better situation (assuming the cost of recycling can be kept at a minimum). – Brian Rasmussen May 20 '13 at 00:59
0

I am not sure, if this is what you need, but you could always make a pool of objects that are going to be used. Initialize a List of the object type. Then when you need to use an object remove it from the list and add it back when you are done with it.

http://www.codeproject.com/Articles/20848/C-Object-Pooling Is a good start.

Hope I've helped even if a little :)

  • 1
    eugh; using a list for that is *terrible* - that is **really, really** inefficient. That implementation is not "a good start" - it is a problem waiting to happen. Hint: what happens (performance-wise) when you remove something from the start of a list? – Marc Gravell May 17 '13 at 12:45
  • You really should read all what other people type before commenting. First I didn't necessarily mean list as List more as container, also this method might not be good performance-wise when implemented for small objects, yet it is way more efficient when it comes to large data objects - like financial documents .... – Georgi Gamzakov May 17 '13 at 12:58
  • The size of the "object" is irrelevant; a `List` (for object-type T) stores *references*, for which the size is known very precisely: 4 or 8 bytes (x86 or x64). That has *nothing to do with* what I said, however - the issue is that the list must *move all the later items* - it will do a mem-copy every time you remove the item at the start. In what is basically an allocator: that is very bad. Re "I didn't necessarily mean `List`" - you said "List"; the article linked uses `List`. So... what *did* you mean? – Marc Gravell May 17 '13 at 13:20
  • @MarcGravell Well, the code in the article looks a bit weird to me (why using weak references? It defeats the whole purpose of having an allocation pool), but at least it uses `LinkedList`, not `List`. – Kevin Gosse May 17 '13 at 16:26
-2

If you are just worried about the time taken for the GC to run, then don't be - it can't be beaten by anything you can do yourself.

However, if your objects' constructors do some work it might be quicker to cache them.

A fairly straightforward way to do this is to use a ConcurrentBag

Essentially what you do is to pre-populate it with a set of objects using ConcurrentBag.Add() (that is if you want - or you can start with it empty and let it grow).

Then when you need a new object you use ConcurrentBag.TryTake() to grab an object.

If TryTake() fails then you just create a new object and use that instead.

Regardless of whether you grabbed an object from the bag or created a new one, once you're done with it you just put that object back into the bag using ConcurrentBag.Add()

Generally your bag will get to a certain size but no larger (but you might want to instrument things just to check it).

In any case, I would always do some timings to see if changes like this actually make any difference. Unless the object constructors are doing a fair bit of work, chances are it won't make much difference.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • No. This will always be slower than the GC. – H H May 17 '13 at 12:33
  • Yes, GC is faster way than creating other objects to make separate threads for taking memory... – A.T. May 17 '13 at 12:35
  • 1
    @HenkHolterman I was assuming that there was some overhead associated with the object constructors. If the constructors are relatively slow this would help. If it's just memory allocation then I agree - the GC can't be beaten. I'll update my answer with this point. – Matthew Watson May 17 '13 at 12:37
  • Although requests to improve performance in a specific area may be based on invalid assumptions, it is equally invalid to assume all programmers work on the same type of code, and have the same performance problems, therefore all performance questions are invalid. It is not difficult to demonstrate that certain data structures are much faster when they avoid allocating objects on the .net heap. Dictionary, List, etc all use pools for low level internal data structures: these are arrays of structures, not objects, but GC overhead is the motivation. – Frank Hileman Jan 29 '14 at 18:27
  • @FrankHileman I'm afraid I really don't understand your point at all... I must also point out that the only real difference between my answer and the ACCEPTED answer is that this one is using ConcurrentBag to avoid allocations whereas the accepted answer is using a custom solution - but they are both doing the same thing: Caching objects to avoid allocations. – Matthew Watson Jan 30 '14 at 08:39
  • @MatthewWatson I should have made it clear I was referring to the statement at the top, "If you are just worried about the time taken for the GC to run, then don't be..." Developers working on low level data structures typically have to worry about the GC all the time. – Frank Hileman Jan 31 '14 at 17:50