Showing posts with label $curocc. Show all posts
Showing posts with label $curocc. Show all posts

Sunday, 1 June 2014

Reducing your memory footprint

One common use for the while loop is to loop through a set of retrieved records.  This can also be done with a for loop, as I discussed in a previous post about types of for loops.  The example I gave was something like this...

  setocc "ent",1
  while $status > 0 )
    ;do something
    setocc "ent",$curocc(ent)+1
  endwhile 

This is nice and simple, and also pretty efficient.  There is one big downfall with it though, and that's memory.  In order to improve performance when you do a retrieve, Uniface works out what the "hitlist" is, but doesn't actually pull all of the retrieved records into memory.  As you loop through, more and more of the hitlist is completed, and the records are pulled into memory.  

To demonstrate this, I retrieved 600 records and then looped through them, using Process Manager to track the memory of the "uniface.exe" program...

  • Initial value: 14.56Mb
  • 600 records retrieved: 14.64Mb (+0.08Mb)
  • Looped to occurrence 100: 14.91Mb (+0.27Mb)
  • Looped to occurrence 200: 15.30Mb (+0.39Mb)
  • Looped to occurrence 300: 15.70Mb (+0.40Mb)
  • Looped to occurrence 400: 16.10Mb (+0.40Mb)
  • Looped to occurrence 500: 16.54Mb (+0.44Mb)
  • Looped to occurrence 600: 16.93Mb (+0.39Mb)

As you can see, retrieving the hitlist is just the first step, and a small one at that, the data is not read in until you loop through the occurrences.

If you had retrieved a rather large amount of data, looping through it like this would continue to hold all of that data in memory.  In the situation where you're processing records one by one, you often no longer need the record after it's been processed, so it's a good idea to discard the record, to free up the memory.  

Each time you discard a record, it removes it from the hitlist and then does an implicit setocc to the next record, which becomes the new current record.  Another thing that discard does is set $status to be the occurrence number, or 0 if there are no records left in the hitlist, just like setocc does.  This allows us to build a very similar loop to the one we had before...

  setocc "ent",1
  while $status > 0 )
    ;do something
    discard "ent",$curocc(ent)

  endwhile

An alternative that I often see is something more like this...

  setocc "ent",1
  while $hits("ent") > 0 )
    ;do something
    discard "ent",1

  endwhile

I'm not a fan of this one though, as $hits can sometimes have performance problems of it's own, caused by it's tendency to complete the hitlist in order to return the count.  

So, let's check how these different methods stack up against each other performance-wise, using the same 600 records as before...

  • Original loop: 6.37, 6.02, 5.90 (around 6 seconds)
  • Discard loop with $curocc: 5.95, 5.95, 5.86 (just under 6 seconds)
  • Discard loop with $hits: 5.95, 6.00, 5.89 (just under 6 seconds)

As you can see, there isn't that much difference in the approaches as far as performance goes.  In this case $hits hasn't caused a problem, but if I remember correctly, it's environment and/or database specific, so that's probably why.  


Summary: Using discard with a large record set is a good idea, because it reduces the memory footprint of the userver, and at no noticeable cost to processing time.  

Friday, 20 July 2012

Types of for loops - part two


In part one I talked about the forlist statements and how these could be used to iterate through Uniface lists for easily.  The next thing I want to talk about is looping through entity occurrences. Personally I've always done this using a combination of setocc and $curocc, something like this...



  setocc "ent",1
  while ( $status > 0 )
    ;do something
    setocc "ent",$curocc(ent)+1
  endwhile



This has the advantage of not needing to use any variables for the loop.  However, it may be better for performance if a similar loop was used, but using variables to control it...


  count = 0
  stat = $hits(ent)
  while ( count < stat )
    count = count+1
    setocc "ent",count
    ;do something
  endwhile



Another alternative would be to use the new statement forentity, which was also added in Uniface 9.5...


  forentity "ent"
    ;do something
  endfor



As you can see, the code is much more concise.  There is no need to initialise the count variable or  $status, everything is done as part of the forlist statement, and the incrementing and extracting are done automatically.  The "count" variable is optional, if you don't need it then you don't need to include it.

So let's test these three blocks of code over 65,000 iterations...

  • while ($curocc) = 00:00.47, 00:00.47, 00:00.47 (about half a second)
  • while (variables= 00:04.62, 00:04.25, 00:05.10 (about 5 seconds)
  • forentity = 00:00.37, 00:00.34, 00:00.35 (about a third of a second)


As you can see, the new forentity is more concise code and also performs better, fairly significantly over some alternatives.

I was surprised by how slow the second method was compared to the first.  The only explanation I have for this is that $hits is taking a long time to complete the hit-list before the loop starts, rather than completing the hit-list as it goes through the loop.  Turns out I’ve been doing it a pretty efficient way all along, but I like the simplicity of the new forentity statement.  Which leads me to an almost identical summary as in part one. 

Summary: Whilst I have previously always used while loops, I shall now be considering switching the forentity loops, for iterating through entity occurrences.