2

I need to execute strategy.AllTablesUpdated(); for 50 strategies in 2 ms (and I need to repeat that ~500 times per second). Using code below I discovered that just Monitor.TryEnter call spents up to 1 ms (!!!) and I do that 50 times!

    // must be called ~500 times per second
    public void FinishUpdatingTables()
    {
        foreach (Strategy strategy in strategies)   // about ~50, should be executed in 2 ms
        {
            // this slow and can be paralleled
            strategy.AllTablesUpdated();
        }
    }

...................

    public override bool AllTablesUpdated(Stopwatch sw)
    {
        this.sw = sw;
        Checkpoint(this + " TryEnter attempt ");
        if (Monitor.TryEnter(desiredOrdersBuy))
        {
            Checkpoint(this + " TryEnter success ");
            try
            {
                OnAllTablesUpdated();
            } finally
            {
                Monitor.Exit(desiredOrdersBuy);
            }
            return true;
        } else
        {
            Checkpoint(this + " TryEnter failed ");
        }
        return false;
    }

    public void Checkpoint(string message)
    {
        if (sw == null)
        {
            return;
        }
        long time = sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
        Log.Push(LogItemType.Debug, message + time);
    }

From logs (in µs), failed attempt spents ~ 1ms:

12:55:43:778 Debug: TryEnter attempt 1264 12:55:43:779 Debug: TryEnter failed 2123

From logs (in µs), succeed attempt spents ~ 0.01ms:

12:55:49:701 Debug: TryEnter attempt 889 12:55:49:701 Debug: TryEnter success 900

So now I think that Monitor.TryEnter is too expensive for me to be executed one by one for 50 strategies. So I want to parallel this work using Task like that:

    // must be called ~500 times per second
    public void FinishUpdatingTables()
    {
        foreach (Strategy strategy in strategies)  // about ~50, should be executed in 2 ms
        {
            // this slow and can be paralleled
            Task.Factory.StartNew(() => {
                strategy.AllTablesUpdated();
            });
        }
    }

I will also probably replace Monitor.TryEnter to just lock as with such approach everything will be asynchronous.

My questions:

  • Why Monitor.TryEnter is so slow ? (1 ms if lock is not obtained)
  • How good would be to start 50 Task each 2 ms = 25 000 of Tasks each second? Can .NET manage this effectively? I can also use producer-consumer pattern with BlockingCollection and start 50 "workers" only ONCE and then submit new pack of 50 items each 2 ms to BlockingCollection? Would that be better?
  • How would you execute 50 methods that can be paralleled each 2 ms (500 times per second), totally 25 000 times per second?
Brad Werth
  • 17,411
  • 10
  • 63
  • 88
Oleg Vazhnev
  • 23,239
  • 54
  • 171
  • 305

1 Answers1

4
  1. Monitor.TryEnter(object) is just Monitor.TryEnter(object, 0, ref false) (0 millisecond timeout). That 1 ms if the lock is not obtained is just overhead of trying to acquire a lock.
  2. You can start as many tasks as you like, they all use the ThreadPool though which will be limited to a maximum number of threads. The maximum is dependent on your system, number of cores, memory etc... It will not be 25,000 threads that's for sure though. However, if you start meddeling with the TPL scheduler you'll get into trouble. I'd just use Parallel.Foreach and see how far it gets me.
  3. Parallel.ForEach. I'd also ensure that strategies is of type IList so as many items are fired off as possible without waiting on an iterator.

You haven't pasted the code to OnAllTablesUpdated(), you keep the lock for the duration of that procedure. That's going to be your bottleneck in all liklihood.

Some questions, why are you using a lock for when the table is ready to be processed?

  1. Are delegates not possible?
  2. Why lock it when you're running the strategy? Are you modifying that table inside each strategy? Can you not take a copy of it if that is the case?
M Afifi
  • 4,645
  • 2
  • 28
  • 48
  • 1. Sorry I don't understand question. 2. `OnAllTablesUpdated` recalculates `desiredOrdersBuy`. Also I have `OrdersExecutor` which uses `desiredOrderBuy` to put orders. So there are two incompatible tasks - recalculate orders and put orders. While orders are recalculating executor shouldn't try to put orders that's why each strategy locks it desiredOrders while recalculating and each executor locks the same orders while puting orders – Oleg Vazhnev Apr 18 '12 at 10:45
  • @javapowered ignore question 1, given your answer to 2. So it sounds like you have an OrderExecutor that will execute the orders regardless of whether the strategy is updated? I.e. it has locked desiredOrders, executed it, which is why for some of your cases Strategy.AllTablesUpdated fails to acquire the lock? Are the orders so time sensitive that you prefer it to execute an old strategy? Or can you not notify your OrderExecutor the strategy has been updated, please execute the orders on completion (and lose the locking) – M Afifi Apr 18 '12 at 11:08
  • @M Afifi thank you very much for trying to deeper understand logic of my program :) If `OrdersExecutor` locked `desiredOrdersBuy` then this means that orders and in the proccess of `execution` (pushing, placing) right now! So yes, sometimes (not too ofthen though) Strategy.AllTablesUpdated fails to acquire the lock, then it will do it on next iteration. If orders already "executing" I can not cancel that. I can only wait until this is finished, update desiredOrdersBuy, and then `OrdersExecutor` will change orders as requested. What problem do you see and what are you tring to achieve? – Oleg Vazhnev Apr 18 '12 at 11:17
  • I can not just notify OrderExecutor to execute orders. Because OrderExecutor also `query` to execute orders because it needs free connections for that and they not always available. While `OrdersExecutor` is waiting for free connection sometimes I "recalculate" orders many times, so when `OrderExecutor` is ready to place orders it always has "up to date" `desiredOrdersBuy". I have separate connections for receiving market data and executing and I have to synchronize them.... – Oleg Vazhnev Apr 18 '12 at 11:22
  • However probably `OrdersExecutor` can just copy `desiredOrdersBuy` and so the time it locks it will be significantly reduced..... Or probably I can just use some kind of "synchronized" collection. I have to think about that... – Oleg Vazhnev Apr 18 '12 at 11:23
  • @javapowered Neither suggestion would work. Remember that you are not waiting on acquiring the lock (timeout of 0). I can't look into the CLR internals, but I suspect that truly means it is not waiting and the 1 msec is overhead. Strategy.OnAllTableUpdated() still modifies the table, and if you don't use locks you have no guarantee that the object you've copied has not been modified half way through. Similarly a synchronized collection still has the same issue, it uses locks. – M Afifi Apr 18 '12 at 11:39
  • @javapowered PS ref your earlier question, we're assuming (not proven) that the main bottleneck is the attempt (and failure) to acquire a lock on that object. My approach is to see if there is any method of avoiding using locks, but it doesn't sound like there is. Final suggestion from me, try SpinLocks, it may be more suitable for your use case, http://msdn.microsoft.com/en-us/library/system.threading.spinlock.aspx Make sure you not the remarks. – M Afifi Apr 18 '12 at 11:44