Zone Locks on Massive NPC Death

For any problems with Dawn of Light website or game server, please direct questions and problems here.

Moderator: Support Team

Re: Zone Locks on Massive NPC Death

Postby Tolakram » Thu Oct 02, 2014 5:21 pm

I can agree that locking should be evaluated for collection checks. Do we really lock region members to check for attackers? IMO unless something will be corrupted there should be no locking. I'm looking for locks that are not needed, because if we have them they should be removed. This is what I've found

GameNPC:

lock (m_xpGainers.SyncRoot)

In all cases this should be locked, a copy made, and then unlocked.


GameLiving:

In TakeDamage:
Code: Select all
if (!IsAlive) { if (wasAlive) Die(source); lock (m_xpGainers.SyncRoot) m_xpGainers.Clear(); } else { if (IsLowHealth) Notify(GameLivingEvent.LowHealth, this, null); }
I'm concerned this clears on death and not on respawn. Die is doing some locking with m_xpGainers and it's at least feasible that some damage could happen before the die code is executed, so this might end up attempting multiple locks. I think that's why the IsAlive nonsense was added, but I'm not positive it's working as intended.

AbstractServerRules:

lock (killed____.XPGainers.SyncRoot)

In all cases this should be a lock, copy list, unlock. I don't see any reason the list should remain locked during processing of a copied list.

There is also some group code that could be copied and released, but the impact of this looks very low.
- Mark
User avatar
Tolakram
Storm / Storm-D2 Admin
 
Posts: 9189
Joined: Tue Jun 13, 2006 1:49 am
Location: Kentucky, USA

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Thu Oct 02, 2014 5:50 pm

Yes this part of code are what I called "Heavy Locks" or "Ugly Locks" in my previous posts...

When the NPC Take Damage it's "ordered" by the Game timer call, it may be arbitrary, maybe it's a result of player latency, but any way only one and one player can make the last "HIT" to a NPC !

The HIT after this one will not trigger the IsAlive/WasAlive check. But it has all chance of trying to lock the xpgainer collections to try to clear it multiple times ! The "Clear" Call should be in the "WasAlive If" statement, a hit when the object is already in die sequence should only be some "collateral damage"

Copies are well enough in many places, managed code will make these copy use memory only short time, most of times collections are pretty small, and "Runtime" improvement can take care of optimizing "copy for thread safety", I'm sure some compiler can understand a "snapshot" logic around some code like any compiler will copy data for function "call" by stacking and unstacking it pretty efficiently.

And by the way I see absolutely no reason of changing the content of xpgainers after a NPC death (except clearing it maybe...) and there is no reason to handle any changes that could occur in there through death sequence, so yes most of time a "snapshot" is great, we want to handle some "rules" with a given T - time status we don't have to handle change made millisecond later, an other update thread will take care of sending changes to client with some latency...
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Crazys » Thu Oct 02, 2014 10:15 pm

I can agree that locking should be evaluated for collection checks. Do we really lock region members to check for attackers? IMO unless something will be corrupted there should be no locking. I'm looking for locks that are not needed, because if we have them they should be removed. This is what I've found

GameNPC:

lock (m_xpGainers.SyncRoot)

In all cases this should be locked, a copy made, and then unlocked.


GameLiving:

In TakeDamage:
Code: Select all
if (!IsAlive) { if (wasAlive) Die(source); lock (m_xpGainers.SyncRoot) m_xpGainers.Clear(); } else { if (IsLowHealth) Notify(GameLivingEvent.LowHealth, this, null); }
I'm concerned this clears on death and not on respawn. Die is doing some locking with m_xpGainers and it's at least feasible that some damage could happen before the die code is executed, so this might end up attempting multiple locks. I think that's why the IsAlive nonsense was added, but I'm not positive it's working as intended.

AbstractServerRules:

lock (killed____.XPGainers.SyncRoot)

In all cases this should be a lock, copy list, unlock. I don't see any reason the list should remain locked during processing of a copied list.

There is also some group code that could be copied and released, but the impact of this looks very low.

These are the locks I mainly removed
and did exactly what you mentioned. However I did the same thing in GameNPC also on Die().
I don't have time to go back and test just guild buffs atm though since alot of this helped overall and to much other stuff on my plate.
Crazys
Contributor
 
Posts: 346
Joined: Tue Nov 07, 2006 10:18 pm

Re: Zone Locks on Massive NPC Death

Postby Crazys » Fri Oct 03, 2014 3:59 am

Our first guild got high enough level and found out guild dues causing the SAME $#*# issues!!!!
Rewrote the section on guild dues from gold from npcs on my server. Tested working fine...
This whole Give to play remove from player give to guild thing seemed backwards..
Crazys
Contributor
 
Posts: 346
Joined: Tue Nov 07, 2006 10:18 pm

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Fri Oct 03, 2014 4:05 pm

I've read some article about threading, and a lot of comments says that "lock" is really an overestimated way of synchronizing threads...

http://stackoverflow.com/questions/1067 ... thout-lock
http://blogs.msdn.com/b/jaredpar/archiv ... -hard.aspx

There is a lot of ways to bypass total lock

Some collections can use a "versioning" with the help of a Semaphore, you increment a thread-safe counter to know if you're going to insert a copy of the "current" collection correctly updated, if the copy/modified version isn't the same as the current version then some thread changed the collection before this, you'll have to retry a copy/modify

These article are a lot more concerned by race conditions than by simply data integrity like we can be in DOL, but they talk about thread safety without Locks ! And even the basic MSDN article describing .NET 4.0 concurrent collections talks about the loss of performance around "lock" used for reading/writing, they describe their new collection as being able to use spinner instead of locks, because locks seems to performs "Wait" "Sleep" calls which are pretty heavy on kernel context change...

Also I'm repeating myself, but by my current readings it appear obvious that "swapping" and "copying" are the easiest way of accomplishing thread safety that DOESN'T need concurrent WRITE access !

Yes in fact most article explain how to handle high performance scenario where data is supposed to change a lot even between a "count" and a "get" which is not the kind of scenario that can happen in a plain lock method, and we don't have this need of concurrency for DOL collections, we just need to have some "immutable" collections once we know that the data in there is what we need to process some game rule !

That's also why I don't trust "ConcurrentCollection" which gives a false feeling of safety, these are "non-blocking" collection AT ALL, so if you use this in some critical part where you just want to keep some data integrity, you can run in easy race conditions, you're not going to lose performance on Concurrent Collection but the lack of locking will prevent your code from being sure it checked the "revision" he meant to use by just waiting for other thread to finish ! (locking can sometime have side effect of acting like a "Thread.Join()")

Well any way DOL is not subject to much race conditions through "Game Rules" due to the nature "single-threaded" of time code !

We really should need only some lightly implemented thread synchronization for DOL ! (Every Property used Cross Region/Timer, and Every Handler coming FROM network, that may include command handler for admins...)

PS : I still want to read some more article about ReaderWriterLock() and even newer ReaderWriterLockSlim() !!
PS2 : IF performance is a bottle neck all I said is mostly false, and we could use multi-threaded collection implementation that allows to use ALL cpu's for any part of the game rules collections handling !
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Fri Oct 03, 2014 4:12 pm

I must explain my "PS2"

For example you have a collection "XPGainer" you need to go through this collection and sum up the XP, with some simple queries in recent .NET you can "Multi-Thread" the sum ! use a "Volatile" int value, create a lambda method that you will give to a LINQ parallel "foreach" using a concurrent collection from the .NET framework, and there you can "vectorize" your sum on all available CPU !

I'm pretty sure this shouldn't been needed for our use case...

Update :
Here is some doc about ReaderWriterLock

http://www.albahari.com/threading/part4 ... iter_Locks

It looks like it can pretty much serve our needs :)

I still need some safety around the SkillBase object that can be updated by script or admin reload (to register spellLine and such...)
This looks great for a test, I was writing a defective read/write logic with locks actually :)
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Sat Oct 04, 2014 9:25 am

Here is some try out with ReaderWriterLockSlim

I wrote a IList<T> Implementation that gives a "copy" for enumerator, and locks everything read/write depending on other methods...

I was still thinking of "Race Condition" if we were to enumerate the copy while building a list of "update" (classic behavior in Generics Collections, as we CAN'T update a collection while enumerating it !) so I made a Method "FreezeWhile(Action)"

This should make the collection "Freezed" during a delegate execution, which should answer to the need of enumerating and updating at the same time (and as I didn't thought of a way of giving the real enumerator, you CAN enumerate copy and update original collection at the same time, but it can be tricky !)
Code: Select all
/* * DAWN OF LIGHT - The first free open source DAoC server emulator * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace DOL.GS { /// <summary> /// ReaderWriterList is a IList implementaiton with ReaderWriterLockSlim for concurrent acess. /// This Collection supports Multiple Reader but only one writer. Enumerator is a Snapshot of Collection. /// You can use TryXXX Method to prevent race conditions between read checks and write lock acquirement. /// </summary> public class ReaderWriterList<T> : IList<T> { private List<T> m_list; private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim(); public ReaderWriterList() { m_list = new List<T>(); } public ReaderWriterList(int capacity) { m_list = new List<T>(capacity); } public ReaderWriterList(ICollection<T> collection) { m_list = new List<T>(collection); } #region Implementation of IEnumerable /// <summary> /// Returns an Enumerator on a Snapshot Collection /// </summary> /// <returns></returns> public IEnumerator<T> GetEnumerator() { m_rwLock.EnterReadLock(); IList<T> newList = null; try { newList = new List<T>(m_list); } finally { m_rwLock.ExitReadLock(); } if (newList != null) return newList.GetEnumerator(); return default(IEnumerator<T>); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region Implementation of ICollection<T> public void Add(T item) { m_rwLock.EnterWriteLock(); try { m_list.Add(item); } finally { m_rwLock.ExitWriteLock(); } } public void Clear() { m_rwLock.EnterWriteLock(); try { m_list.Clear(); } finally { m_rwLock.ExitWriteLock(); } } public bool Contains(T item) { bool contains = false; m_rwLock.EnterReadLock(); try { contains = m_list.Contains(item); } finally { m_rwLock.ExitReadLock(); } return contains; } public void CopyTo(T[] array, int arrayIndex) { m_rwLock.EnterReadLock(); try { m_list.CopyTo(array, arrayIndex); } finally { m_rwLock.ExitReadLock(); } } public bool Remove(T item) { bool removed = false; m_rwLock.EnterWriteLock(); try { removed = m_list.Remove(item); } finally { m_rwLock.ExitWriteLock(); } return removed; } public int Count { get { int count = 0; m_rwLock.EnterReadLock(); try { count = m_list.Count; } finally { m_rwLock.ExitReadLock(); } return count; } } public bool IsReadOnly { get { bool readOnly = false; m_rwLock.EnterReadLock(); try { readOnly = ((ICollection<T>)m_list).IsReadOnly; } finally { m_rwLock.ExitReadLock(); } return readOnly; } } #endregion #region Implementation of IList<T> public int IndexOf(T item) { int index = -1; m_rwLock.EnterReadLock(); try { index = m_list.IndexOf(item); } finally { m_rwLock.ExitReadLock(); } return index; } public void Insert(int index, T item) { m_rwLock.EnterWriteLock(); try { m_list.Insert(index, item); } finally { m_rwLock.ExitWriteLock(); } } public void RemoveAt(int index) { m_rwLock.EnterWriteLock(); try { m_list.RemoveAt(index); } finally { m_rwLock.ExitWriteLock(); } } public T this[int index] { get { T ret = default(T); m_rwLock.EnterReadLock(); try { ret = m_list[index]; } finally { m_rwLock.ExitReadLock(); } return ret; } set { m_rwLock.EnterWriteLock(); try { m_list[index] = value; } finally { m_rwLock.ExitWriteLock(); } } } #endregion public bool AddOrReplace(T item) { bool replaced = false; m_rwLock.EnterWriteLock(); try { int index = m_list.IndexOf(item); if (index < 0) { // add m_list.Add(item); } else { // replace m_list[index] = item; replaced = true; } } finally { m_rwLock.ExitWriteLock(); } return replaced; } public bool AddIfNotExists(T item) { bool added = false; m_rwLock.EnterUpgradeableReadLock(); try { if (!m_list.Contains(item)) { m_rwLock.EnterWriteLock(); try { m_list.Add(item); added = true; } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return added; } public bool TryRemove(T item) { bool removed = false; m_rwLock.EnterUpgradeableReadLock(); try { if (m_list.Contains(item)) { m_rwLock.EnterWriteLock(); try { m_list.Remove(item); removed = true; } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return removed; } public bool TryRemoveAt(int index) { bool removed = false; m_rwLock.EnterUpgradeableReadLock(); try { if (m_list.Count > index) { m_rwLock.EnterWriteLock(); try { m_list.RemoveAt(index); removed = true; } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return removed; } public bool TryInsert(int index, T item) { bool inserted = false; m_rwLock.EnterUpgradeableReadLock(); try { if (m_list.Count > index) { m_rwLock.EnterWriteLock(); try { m_list.Insert(index, item); inserted = true; } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return inserted; } public void FreezeWhile(Action<List<T>> method) { m_rwLock.EnterWriteLock(); try { method(m_list); } finally { m_rwLock.ExitWriteLock(); } } public int FindIndex(Predicate<T> match) { m_rwLock.EnterReadLock(); int index = -1; try { index = m_list.FindIndex(match); } finally { m_rwLock.ExitReadLock(); } return index; } } }
I'm thinking about implementing IDictionary<T Key, TValue> this should cover about every use case of DOL...

edit Here is dictionary :
Code: Select all
/* * DAWN OF LIGHT - The first free open source DAoC server emulator * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace DOL.GS { /// <summary> /// ReaderWriterDictionary is a IDictionary implementaiton with ReaderWriterLockSlim for concurrent acess. /// This Collection supports Multiple Reader but only one writer. Enumerator is a Snapshot of Collection. /// You can use TryXXX Method to prevent race conditions between read checks and write lock acquirement. /// </summary> public class ReaderWriterDictionary<TKey, TValue> : IDictionary<TKey, TValue> { private IDictionary<TKey, TValue> m_dictionary; private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim(); public ReaderWriterDictionary() { m_dictionary = new Dictionary<TKey, TValue>(); } public ReaderWriterDictionary(int capacity) { m_dictionary = new Dictionary<TKey, TValue>(capacity); } public ReaderWriterDictionary(IDictionary<TKey, TValue> collection) { m_dictionary = new Dictionary<TKey, TValue>(collection); } #region implementation of IEnumerator /// <summary> /// Get an enumerator over collection Snapshot. /// </summary> /// <returns></returns> public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { m_rwLock.EnterReadLock(); IDictionary<TKey, TValue> copy = null; try { copy = new Dictionary<TKey, TValue>(m_dictionary); } finally { m_rwLock.ExitReadLock(); } if (copy != null) return copy.GetEnumerator(); return default(IEnumerator<KeyValuePair<TKey, TValue>>); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion #region implementation of ICollection<KeyValuePair<TKey, TValue>> public int Count { get { m_rwLock.EnterReadLock(); int count = 0; try { count = m_dictionary.Count; } finally { m_rwLock.ExitReadLock(); } return count; } } public bool IsReadOnly { get { m_rwLock.EnterReadLock(); bool readOnly = false; try { readOnly = m_dictionary.IsReadOnly; } finally { m_rwLock.ExitReadLock(); } return readOnly; } } public void CopyTo(KeyValuePair<TKey, TValue>[] array, int index) { m_rwLock.EnterReadLock(); try { m_dictionary.CopyTo(array, index); } finally { m_rwLock.ExitReadLock(); } } public void Add(KeyValuePair<TKey, TValue> item) { m_rwLock.EnterWriteLock(); try { m_dictionary.Add(item); } finally { m_rwLock.ExitWriteLock(); } } public void Clear() { m_rwLock.EnterWriteLock(); try { m_dictionary.Clear(); } finally { m_rwLock.ExitWriteLock(); } } public bool Contains(KeyValuePair<TKey, TValue> item) { bool contains = false; m_rwLock.EnterReadLock(); try { contains = m_dictionary.Contains(item); } finally { m_rwLock.ExitReadLock(); } return contains; } public bool Remove(KeyValuePair<TKey, TValue> item) { bool removed; m_rwLock.EnterWriteLock(); try { removed = m_dictionary.Remove(item); } finally { m_rwLock.ExitWriteLock(); } return removed; } #endregion #region implementation of IDictionary<TKey, TValue> public TValue this[TKey key] { get { m_rwLock.EnterReadLock(); TValue el = default(TValue); try { el = m_dictionary[key]; } finally { m_rwLock.ExitReadLock(); } return el; } set { m_rwLock.EnterWriteLock(); try { m_dictionary[key] = value; } finally { m_rwLock.ExitWriteLock(); } } } public ICollection<TKey> Keys { get { m_rwLock.EnterReadLock(); ICollection<TKey> result = default(ICollection<TKey>); try { result = new List<TKey>(m_dictionary.Keys); } finally { m_rwLock.ExitReadLock(); } return result; } } public ICollection<TValue> Values { get { m_rwLock.EnterReadLock(); ICollection<TValue> result = default(ICollection<TValue>); try { result = new List<TValue>(m_dictionary.Values); } finally { m_rwLock.ExitReadLock(); } return result; } } public bool ContainsKey(TKey key) { m_rwLock.EnterReadLock(); bool contains = false; try { contains = m_dictionary.ContainsKey(key); } finally { m_rwLock.ExitReadLock(); } return contains; } public void Add(TKey key, TValue value) { m_rwLock.EnterWriteLock(); try { m_dictionary.Add(key, value); } finally { m_rwLock.ExitWriteLock(); } } public bool Remove(TKey key) { m_rwLock.EnterWriteLock(); bool removed = false; try { removed = m_dictionary.Remove(key); } finally { m_rwLock.ExitWriteLock(); } return removed; } public bool TryGetValue(TKey key, out TValue value) { m_rwLock.EnterReadLock(); bool found = false; try { found = m_dictionary.TryGetValue(key, out value); } finally { m_rwLock.ExitReadLock(); } return found; } #endregion public bool AddOrReplace(TKey key, TValue val) { m_rwLock.EnterWriteLock(); bool replaced = false; try { if (!m_dictionary.ContainsKey(key)) { m_dictionary.Add(key, val); } else { m_dictionary[key] = val; replaced = true; } } finally { m_rwLock.ExitWriteLock(); } return replaced; } public bool AddIfNotExists(TKey key, TValue val) { m_rwLock.EnterUpgradeableReadLock(); bool added = false; try { if (!m_dictionary.ContainsKey(key)) { m_rwLock.EnterWriteLock(); try { m_dictionary.Add(key, val); added = true; } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return added; } public bool TryRemove(TKey key, out TValue val) { m_rwLock.EnterUpgradeableReadLock(); bool removed = false; val = default(TValue); try { if (m_dictionary.ContainsKey(key)) { val = m_dictionary[key]; m_rwLock.EnterWriteLock(); try { removed = m_dictionary.Remove(key); } finally { m_rwLock.ExitWriteLock(); } } } finally { m_rwLock.ExitUpgradeableReadLock(); } return removed; } public void FreezeWhile(Action<IDictionary<TKey, TValue>> method) { m_rwLock.EnterWriteLock(); try { method(m_dictionary); } finally { m_rwLock.ExitWriteLock(); } } } }
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Sun Oct 05, 2014 7:22 am

I tried to use the ReaderWriterCollection in some update I have currently.

The Thread Safe object can't do all the job for the coder, there is still easy errors that can happens especially when we try to update a collection concurrently with an other thread... (if we keep the locks atomic when updating we can run into race condition, if we locks it "widely" we get back to previous lock() behavior...)

But I think it should prevent any collection from being corrupted while reading it, and atomic update should take advantage of this new locks without getting in race conditions using some AddorUpdate() or AddIfNotExists() method which are safe for race conditions ! (these methods are here to make the CHECK before an UPDATE in the same lock, this can't be obtain using "Getters")


---

On an other Area I started to check is How to use "Parallel LINQ" this is an extension to LINQ assembly which enable multi-threading on Collections manipulations...

The use-cases seems pretty specific, either a big collection that we can handle fast, or a small collection that is iterated over a heavy method (and obviously a huge collections with heavy work...)

Parallel LINQ requires a lot of thread safety, which mean it should iterate over a Safe-Thread Read collection, and append its results in a Safe Concurrent Write Collection ! That's where .NET Concurrent Colleciton are absolutely necessary, they are made for Concurrent write !

Parallel queries can be ordered or unordered, but ordered behavior prevent a lot of performance gain through parallelism, for now the best use case I found for this is some Skillbase method that needs to instanciate a Game Skill Index from a Database result

you can call some New() Method in a heavy threaded environment this is always thread safe as you'll work on an other object instance, thus creating objects from a read only data result in multi-thread is pretty easy !

Here is my "GetSpellList" Method, Spell list can be the example of "lot of small object to handle" even if it's more a "Proof of Concept"
I make a copy of the spell list I need to work on, then prepare a concurrent bag to get result,
The AsParallel().ForAll() does all the job, it's just like a foreach but in multi-thread, in there I just make a spell Clone then store it in the safe ConcurrentBag...
Once the Paralell query is finished I use the concurrent bag to retrieve a "List" of results for the function...
Code: Select all
public static List<Spell> GetSpellList(string spellLineID) { m_syncLockUpdates.EnterReadLock(); List<Spell> spellList = new List<Spell>(); try { if (m_lineSpells.ContainsKey(spellLineID)) spellList = new List<Spell>(m_lineSpells[spellLineID]); } finally { m_syncLockUpdates.ExitReadLock(); } ConcurrentBag<Spell> results = new ConcurrentBag<Spell>(); spellList.AsParallel().ForAll(item => { try { results.Add((Spell)item.Clone()); } catch { } }); return results.OrderBy(el => el.Level).ThenBy(el => el.ID).ToList(); }
In Class RealmAbilities it's the scenario "low-count / heavy work" for each ability we need to provide for a class I'm going to call the Assembly Activator to get custom object instances, I assume this is somewhat slower than a copy/increment or other basic update...

I Use a concurrent bag to get results the same as above, here in the parallel query I provide DB object I retrieved in a copy before, and give them to Ability GetInstance(), to build the new ability instance, this is safe because every thread will work on a different object, and then add it to the concurrent bag for results... (Casted to list with LINQ... pretty easy !)
Code: Select all
public static List<RealmAbility> GetClassRealmAbilities(int classID) { List<DBAbility> ras = new List<DBAbility>(); m_syncLockUpdates.EnterReadLock(); try { if (m_classRealmAbilities.ContainsKey(classID)) { foreach (string str in m_classRealmAbilities[classID]) { try { ras.Add(m_abilityIndex[str]); } catch { } } } } finally { m_syncLockUpdates.ExitReadLock(); } ConcurrentBag<RealmAbility> returns = new ConcurrentBag<RealmAbility>(); ras.AsParallel().ForAll(element => { try { RealmAbility rab = (RealmAbility)GetNewAbilityInstance(element); rab.Level = 1; returns.Add(rab); } catch { } }); return returns.OrderBy(el => el.ID).ToList(); }
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Wed Oct 08, 2014 5:33 am

Speaking around performance I'm getting interested by Region / Zone / Subzone system...

It actually use some kind of deterministic partition algorithm to reduce area where we need to find the nearest neighbors with a given range, it's able to refresh partition as it iterate it so it's totally not parallel thread safe !!

I read some article around space partitioning and algorithm for nearest neighbors or ray casting, and I could try some implementation of a KD-Tree (special case of Binary Space Partitioning Tree) that only use a small thousand of lines (compared to Region/Zone 3000 lines each...)

This can enable O(log² n + m) nearest neighbor search with the ability to stop after getting a defined number, O( log n) insert/remove, O(n) iterating, and it's safe for parallel query giving the ability to distribute a "GetObjectInRange" query over multiple processors...

Has anyone else researched this area ?
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon

Re: Zone Locks on Massive NPC Death

Postby Leodagan » Wed Oct 08, 2014 10:48 am

If anyone is interested there is a research team (French/Brazilian Team) that need a student to work on Parallel KD-Tree Acceleration Structure ;)

http://moais.imag.fr/membres/bruno.raff ... kdtree.pdf

I mailed one of the coordinators to know if they had any results but it seems stalled...
User avatar
Leodagan
Developer
 
Posts: 1350
Joined: Tue May 01, 2012 9:30 am
Website: https://daoc.freyad.net
Location: Lyon


Return to “%s” Support

Who is online

Users browsing this forum: No registered users and 1 guest