Up to Main Index Up to Journal for November, 2019 JOURNAL FOR THURSDAY 21ST NOVEMBER, 2019 ______________________________________________________________________________ SUBJECT: In search of a better attribute finder DATE: Thu 21 Nov 23:52:14 GMT 2019 WolfMUD v0.0.14 was released, and due to a stupid bug, v0.0.15 swiftly followed. By now the screaming has died down and users are once again happy. Since then I’ve been casting my eye over the code and decided to address a few things that have been niggling me for a while. One of those niggles has been the number of allocations made when the server is just ticking over and handling mobiles and other events. With the tweaks made so far allocations are reduced by about 50%. Part of this has been possible by addressing another major niggle. That being the attribute finder functions, for example the attr.FindName function. I’ve had many private discussions with people about the finders. Everybody agrees the current finders work, but the code is butt-ugly and inefficient. However nobody could come up with a better way of implementing them :( I had to find another solution. I tried using reflection and ended up with an even worse mess. So that idea was thrown out the window. After a few iterations I ended up adding two new methods to Thing: FindAttr and FindAttrs. I also added one new method to the has.Attribute interface: Is. Taking attr.FindName as an example, which finds the Name attribute for a Thing, the current implementation is: func FindName(t has.Thing) has.Name { for _, a := range t.Attrs() { if a, ok := a.(has.Name); ok { return a } } return (*Name)(nil) } func (t *Thing) Attrs() []has.Attribute { t.rwmutex.RLock() a := make([]has.Attribute, len(t.attrs)) for i := range t.attrs { a[i] = t.attrs[i] } t.rwmutex.RUnlock() return a } With the new implementation this becomes: func FindName(t has.Thing) has.Name { return t.FindAttr((*Name)(nil)).(has.Name) } func (*Name) Is(a has.Attribute) bool { _, ok := a.(has.Name) return ok } func (t *Thing) FindAttr(cmp has.Attribute) has.Attribute { t.rwmutex.RLock() for _, a := range t.attrs { if cmp.Is(a) { t.rwmutex.RUnlock() return a } } t.rwmutex.RUnlock() return cmp } The new implementation does not make a copy of the t.attr slice. It also puts all of the inner workings into Thing. This makes writing a finder function and Is method simpler when implementing new attributes. But… performance is a tad slower, about 5% on average :( I ran the quiet zone for an hour logging the times to process a command with the new and old methods. All the times where then averaged for each command: Command Old Method New Method %Change --------- ---------- ---------- -------- GET 125.197µs 126.262µs +0.85% DROP 98.240µs 101.313µs +3.13% PUT 126.550µs 129.379µs +2.24% TAKE 125.602µs 126.872µs +1.01% EXAMINE 116.760µs 125.031µs +7.08% INV 80.097µs 90.335µs +12.78% JUNK 125.240µs 126.209µs +0.77% LOOK 105.105µs 112.236µs +6.78% SAY 92.1888µs 102.568µs +11.26% SNEEZE 90.4013µs 96.355µs +6.59% $CLEANUP 145.407µs 145.440µs +0.02% $RESET 107.439µs 117.315µs +9.19% Average %Change: +5.14% Not very scientific at all, lots of averages of averages, but it gives me some idea of the performance change. Which is not very much. I’m guessing that the slight performance increase maybe because FindAttr cannot inline the Is call? I’ve been performing a lot of testing and benchmarks on these changes — which explains me going quiet again. I think all the kinks have been dealt with… As a bonus, the inefficient Thing.Attrs method has been removed :) WolfMUD has a very simple locking scheme when it comes to concurrency. Over time things have become a little muddied with specific locks being added for certain situations. The attr.Locate attribute springs to mind. Moving logic into attr.Thing it may now be possible to just lock a Thing and not individual attributes which would simplify the code a lot. Something I’ll be looking into next. These changes and a few other minor ones are now on the public dev branch. -- Diddymus Up to Main Index Up to Journal for November, 2019