Struct access speed

107 views
Skip to first unread message

Jonathan Smith

unread,
Aug 17, 2016, 2:47:23 PM8/17/16
to Lucee
Hi,

I have an in-memory data accesor function, in a giant report, which gets called 20k times, and I'd like it to be faster. It is simple struct key access. I hadn't realised accessing in-memory data via a struct took this much time. Anyone have a suggestion how I could implement this code directly in Java to decrease the access time for this specific case? 

( And yes, I realise the glaring solution is to reduce the 20k calls, which were looking into.. but may not be possible. )
(As a caveat, given a low percentage of misses, a try/catch block is 2x faster than structKeyExists at 0.113ms per call, but around 0.300ms if each call is a catch.)

This method call takes an average of 0.208ms  ( x 20k = 4160ms ) running in Lucee 5.0.x
Where: "this.data" is a struct with 183 elements. each element has 5 keys, each containing an single integer

public any function getAnswerById(numeric id){
if (structKeyExists(this.data,id)){
return this.data[id].answer;
}
else {
return 0;
}
}



Gert Franz

unread,
Aug 17, 2016, 3:11:40 PM8/17/16
to lu...@googlegroups.com
Hi Jonathan,

If there aren't to many calls of the function getAnswerById() try replacing them with the following:

Say your call was:

Id = getAnswerById(yourId);

Replace it with:

Id = this.data[id] ?: 0;

In your case i presume the function calls are killing the performance.

Gert

Sent from somewhere on the road
--
Get 10% off of the regular price for this years CFCamp in Munich, Germany (Oct. 20th & 21st) with the Lucee discount code Lucee@cfcamp. 189€ instead of 210€. Visit https://ti.to/cfcamp/cfcamp-2016/discount/Lucee@cfcamp
---
You received this message because you are subscribed to the Google Groups "Lucee" group.
To unsubscribe from this group and stop receiving emails from it, send an email to lucee+un...@googlegroups.com.
To post to this group, send email to lu...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/lucee/b99e5590-63bf-41ef-986c-e86e74c93368%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Drew

unread,
Aug 17, 2016, 3:11:50 PM8/17/16
to lu...@googlegroups.com
Have you tried using a cacheget() instead of structs?

Mark Drew
- Sent by typing with my thumbs. 

Gert Franz

unread,
Aug 17, 2016, 3:13:14 PM8/17/16
to lu...@googlegroups.com
Actually there is a small typo...

I the line is slightly wrong. It should be:

Id = this.data[id].answer ?: 0;

Gert

Sent from somewhere on the road

Am 17.08.2016 um 20:47 schrieb Jonathan Smith <jonatha...@gmail.com>:

Jonathan Smith

unread,
Aug 17, 2016, 4:09:20 PM8/17/16
to Lucee
Hi Gert,

I'll try both options. For starters, I just changed the code to measure the function call overhead of 20k calls:

public any function getAnswerById(numeric id){
return 1;
}

Any my debug output shows 20ms for 20970 calls, or 0.00095 ms per call ), which leads me to believe the function call overhead is almost nothing compared to the structKeyExists and fetch statements?

Mark Drew

unread,
Aug 17, 2016, 4:16:14 PM8/17/16
to lu...@googlegroups.com
Because the return is the same so it's fairly cached I would expect. And it's static code. 


Mark Drew
- Sent by typing with my thumbs. 

Jonathan Smith

unread,
Aug 17, 2016, 4:27:42 PM8/17/16
to Lucee
Hi Mark,

Thanks for the reply! I tried cacheGet/cachePut using Lucee's ramCache and the function call dropped to 0.109 ms from 0.208ms, so 2x improvement... but still seems like a long time for an in-memory operation... ? ( I appreciate that the timing is subject to my workstation, but... I was hoping it would be negligible time to read a tiny data set from memory )

Mark Drew

unread,
Aug 17, 2016, 4:33:17 PM8/17/16
to lu...@googlegroups.com
Yeah. Sounds super slow 


Mark Drew
- Sent by typing with my thumbs. 

Jonathan Smith

unread,
Aug 17, 2016, 4:38:50 PM8/17/16
to Lucee
Hi Gert,

When I use the inline elvis operator ?:   I still get an error when I ask for a non-existent struct key, instead of a Zero.

ie. 

integer-key-that-does-not-exist = 999;
this.data[integer-key-that-does-not-exist].answer ?: 0;  // Throws: The key [999] does not exist , but there is a similar key with name [300] available.

On Wednesday, 17 August 2016 15:13:14 UTC-4, Gert Franz wrote:

Gert Franz

unread,
Aug 17, 2016, 5:18:25 PM8/17/16
to lu...@googlegroups.com
Ok, then try this:

Id = structKeyExists(this.data, id) ? this.data[id].answer : 0;

I'll check myself for a different solution...

Gert

Sent from somewhere on the road

Igal @ Lucee.org

unread,
Aug 18, 2016, 1:21:12 AM8/18/16
to lu...@googlegroups.com

My biggest issue with this construct:

    id = structKeyExists(this.data, key) ? this.data[key] : 0;

Is that if the key exists -- we do a hash lookup twice, which simply means that the operation, albeit fast, takes twice as long as it should.

I recommend trying this construct instead:

    id = structFind(this.data, key, 0);    // see http://docs.lucee.org/reference/functions/structfind.html

Which will return 0 if the key does not exist, but will return the key with a single lookup if it does exist.

As an aside, structFind() is a horrible name for that function.  It should have been structGet(), but some genius decided that structGet() should return something completely different.

Igal Sapir
Lucee Core Developer
Lucee.org

Andrew Penhorwood

unread,
Aug 18, 2016, 7:18:44 AM8/18/16
to Lucee
Can you provide more detail on how the data, you are searching, is structured?

It sounds like you are talking about nested structures.

For example:

data = {
      'node1':{ 'key1-1':100,'key1-2':200,'key1-3':300,'key1-4':400,'key1-5':500 }
    , 'node2':{ 'key2-1':100,'key2-2':200,'key2-3':300,'key2-4':400,'key2-5':500 }
}

I built a test case but I am not seeing anything like the numbers you are talking about.

Mark Drew

unread,
Aug 18, 2016, 7:19:55 AM8/18/16
to lucee
Doing a quick test it looks like the try/catch is the slowest:



On my machine I get:

population: 44ms
index: 121ms
getWithStructFind: 15ms
getWithElvis: 13ms
getWithStructKeyExists: 29ms
getWithTryCatch: 2502ms

population: creates a struct with 20000 elements
index: creates an array with 30000 items which are numbered 1-30000 randomly (so we get cache misses) 
And you can see I loop over the index array (to go get items from the struct) 

Regards

Mark Drew


Andrew Penhorwood

unread,
Aug 18, 2016, 8:53:04 AM8/18/16
to Lucee
In my test using trycf.com I found about the same as Mark.  Though ACF 2016 was generally faster than Lucee 5 on inline-ternary operations :-(

Lucee 4.5
inline-ternary 65 ms
structKeyExists 87 ms  ( function based )

Lucee 5
inline-ternary 96 ms
structKeyExists 130 ms  ( function based )

ACF 2016
inline-ternary 53 ms
structKeyExists 174 ms  ( function based )


Jonathan Smith

unread,
Aug 18, 2016, 10:02:08 AM8/18/16
to Lucee
Looks like I've figured out where the discrepancy is. I had Lucee's debugging template enabled. Turns out, by looking too closely at code performance, I inadvertently made it slow. So embarrassing, but it really points out how fast Lucee is in the end!

Without debug template:

population: 35ms
index: 26ms
getWithStructFind: 41ms 
getWithElvis: 27ms
getWithStructKeyExists: 56ms
getWithTryCatch: 8110ms

With Debug Template: ( Specifically, what caused the change was having "Implicit variable Access" enabled. Only having Timers enabled does not cause such a meaningful slow down, and it only applies to struct reads. )

population: 38ms
index: 27ms
getWithStructFind: 2980ms
getWithElvis: 2907ms
getWithStructKeyExists: 4855ms
getWithTryCatch: 11764ms

Reply all
Reply to author
Forward
0 new messages