Showing posts with label if. Show all posts
Showing posts with label if. Show all posts

Saturday, 2 May 2015

Performance of getitem

I've previously posted about how scanning is slow, and I stand by that.  But I've recently discovered that in some situations, it can be less slow than using getitem.  

I was doing a code review of some old code and found that it was using $scan in a situation where I thought you'd usually use getitem, and wondered why someone would have done it this way.  But before I replaced it, I wanted to check the performance to see what difference I would be making by changing it, and I was surprised by the results!

The code was designed to check if a variable matched one of a reasonably large number of items.  For this example, if stuck with 10 items...

  if ( temp = "ONE" | temp = "TWO" | temp = "THREE" | temp = "FOUR" | temp = "FIVE" | temp = "SIX" | temp = "SEVEN" | temp = "EIGHT" | temp = "NINE" | temp = "TEN" )
    ;testing condition
  endif

I could have used a line continuation marker, but you get the idea.

What the developer had done is replaced this set of conditions with a single $scan, like this...

  list = "|ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN|"
 
if ( $scan(list,"|%%temp%%%|") > 0 )
    ;testing condition
  endif

Note that this is not a Uniface list, a "bar" or "pipe" character has been used as the delimiter - this is placed at the beginning and end of the value to ensure it is not found as a sub-part of another longer value.  It This takes up a lot less space, and is perfectly readable, but I was concerned about performance.  

To test this, I wanted to make sure it was fair, so I decided to always check both the first and the last item in the list in each iteration.  I also wanted to test in a few different ways, and I came up with 4...

1) Set of conditions

  temp = "ONE"
  if ( temp = "ONE" | temp = "TWO" | temp = "THREE" | temp = "FOUR" | temp = "FIVE" | temp = "SIX" | temp = "SEVEN" | temp = "EIGHT" | temp = "NINE" | temp = "TEN" )
    ;testing condition
  endif
  temp = "TEN"
  if ( temp = "ONE" | temp = "TWO" | temp = "THREE" | temp = "FOUR" | temp = "FIVE" | temp = "SIX" | temp = "SEVEN" | temp = "EIGHT" | temp = "NINE" | temp = "TEN" )
    ;testing condition
  endif

2) $scan a bar delimited string

  list = "|ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN|"
 
if ( $scan(list,"|ONE|") > 0 )
    ;testing condition
  endif
  list = "|ONE|TWO|THREE|FOUR|FIVE|SIX|SEVEN|EIGHT|NINE|TEN|"
  if ( $scan(list,"|TEN|") > 0 )
    ;testing condition
  endif

3) getitem/id a Uniface list

  list = "ONE·;TWO·;THREE·;FOUR·;FIVE·;SIX·;SEVEN·;EIGHT·;NINE·;TEN"
  getitem/id temp,list,"ONE"
  if ( $status > 0 )
    ;testing condition
  endif
  list = "ONE·;TWO·;THREE·;FOUR·;FIVE·;SIX·;SEVEN·;EIGHT·;NINE·;TEN"
  getitem/id temp,list,"TEN"
  if ( $status > 0 )
    ;testing condition
  endif

4) $item a Uniface list

  list = "ONE·;TWO·;THREE·;FOUR·;FIVE·;SIX·;SEVEN·;EIGHT·;NINE·;TEN"
  if ( $item("ONE",list) != "" )
    ;testing condition
  endif
  list = "ONE·;TWO·;THREE·;FOUR·;FIVE·;SIX·;SEVEN·;EIGHT·;NINE·;TEN"
  if ( $item("TEN",list) != "" )
    ;testing condition
  endif


I wasn't really sure what I was expecting, but I thought the $scan would be the worst performance.  I tested over 2,000,000 iterations, and here's what I got...

1) Set of conditions: 37.30, 36.95, 37.83 = 37.36 secs
2) $scan a bar delimited string: 21.76, 21.97, 21.24 = 21.66 secs
3) getitem/id a Uniface list: 24.46, 24.22, 24.89 = 24.52 secs
4) $item a Uniface list: 22.65, 23.25, 23.47 = 23.12 secs

So the slowest was the set of conditions.  I didn't test it, but knowing that if statements shortcut I figure that if the item was always passing the first condition it would be quick, but because half of my test items were only passing the last condition, it would have to check each of the conditions in the set before it passed.

Although there wasn't a big difference, what surprised me is that the getitem/id and $item were actually slower than the $scan in this case.  I then remembered back to a conversation on the Uniface-L mailing list, which talked about how Uniface handles lists in the background.  The description there indicates that an array is built in the background, which means there is an upfront cost for calling getitem/id (or $item) once, but then if you're looping through it is much quicker to access the rest, because the array can be used.  However, because I'm rebuilding the list each time, that means the array needs to be rebuilt each time.  

This means that actually $scan can be used to improve the performance when checking that an item is in a list, as long as you're only checking this list once and it's not going to be re-used.  I expect there to be a point at which the number of times the list is re-used means that using getitem/id (or $item) would become better for performance.

Also, I've not tested different lengths of lists.  However, given the results and my reasoning for why the results ended up this way, I would have thought extending the list would simply emphasize the results.

Summary:  Checking if an item is in a list can be done a number of ways, and if it's being done a lot of times, performance can be eeked out by using a $scan, surprisingly!

Thursday, 8 May 2014

Boolean values in an if statement

Last week I wrote a post about casting in if statements, focusing on the difference between equality and identity.  Well that post started out in my mind as being about boolean values, but then I found I needed to cover that ground first.

Boolean is a rather interesting datatype in Uniface.  The description in the manuals goes like this....

The Boolean data type is interpreted as either TRUE or FALSE. An empty value, and the values 0, F, f, N, and n are interpreted as FALSE. All other values are interpreted as TRUE. 

The Uniface default packing code for boolean stores the value as "T" or "F", but a number of different packing codes can be used...


  • B - Optimum DBMS Boolean default
  • B1 - ASCII Boolean (0 or 1)
  • B2 - ASCII Boolean (F or T) *Uniface default
  • B3 - ASCII Boolean (N or Y)
  • B4 - Binary Boolean (0 or 1)
  • B5 - Binary Boolean (0 or -1)


This means that it is always safest to reference a boolean in an if statement without a relational operator, like this...

  variables
    boolean b
  endvariables

  if ( b )
    putmess "b is True"
  endif
  if ( !b )
    putmess "b is False"

  endif

As empty values are interpreted as false, this example would output "b is False".

This is fine, as long as you know that you've got a boolean datatype.  However, I've seen developers get into trouble when they use this type of if statement (without a relational operator) but with other datatypes.

For example, to check a blank string in javascript, you could easily do something like this...

  var s = "";
  if(!s) {
    alert "s is blank";

  }

If you translate this directly into Uniface, then you get something like this...

  variables
    string s
  endvariables

  s = ""
  if ( !s )
    putmess "s is blank"

  endif

And this works, no problem here.  Empty string is interpreted as false and you get the same result.

Consider this though; what if s is set to "0"?  

In javascript, we would not get the alert - this is because the string is not blank, so quite right!  However, in Uniface we do get the message saying that s is blank, even though it's not.  This is because "0" is interpreted as false.

Summary: In an if statement, if you've got a boolean datatype then don't use a relational operator, but if you've got any other datatype then do use one.

Wednesday, 30 April 2014

Casting in an if statement (equality versus identity)

Uniface is a tightly typed language - this means that when we declare variables, we also declare the datatype, such as string or numeric, like this...

variables
  string s
  numeric n
endvariables

This is very different from a loosely typed language, such as javascript, which does not require variables to even be declared, let alone declare them with a datatype.

As mentioned in previous posts about operators (part one and part two), Uniface uses a single equals (=) as the assignment operator and either single (=) or double (==) equals as the relational operator.  So for equality, you can use either = or == interchangeably.  

Both of these will automatically cast values/variables in order to check their equality.  For example...

s = "1"
n = 1
if ( s = n )
  putmess "True!"
endif
if ( s == n )
  putmess "Also true!"
endif

In both cases, Uniface is saying that the string "1" and the numeric 1 are equal to each other, because it is casting the variables to the same type and then saying that they equal each other.  This is fine, and can be very useful.

Originally I assumed that Uniface would be casting both of the variables to string, but in this case I believe it is actually casting them both to numeric, because this example also works...

s = "001"
n = 1
if ( s = n )
  putmess "True!"
endif

There is a specific section in the manuals about this...

Numeric Data, Empty Strings, and Relational Operators
In general, when two operands are compared using a relational operator and one of those operands is numeric (that is, data type Numeric or Float), the other operand is usually converted into numeric data (for the purpose of the comparison only). There are, however, special considerations when a numeric operand is an empty string ("") or contains a space.

In javascript (for example) there is another operator, a triple equals (===) which can be used to check identity.  In this case, no casting takes place, so the if statement would return false, because the types do not match.  Unfortunately, Uniface does not support this operator, you get a compile error when you try...


Because of this, if you want to check identity in Uniface, you need to be very explicit about making the types the same.  Here are a few examples...

if ( s = $string(n) )
  ;string = string
endif
if ( s = "%%n%%%" )
  ;string = string
endif
if
( $number(s) = n )
  ;numeric = numeric
endif
if ( s*1 = n )
  ;numeric = numeric
endif

Summary: Uniface checks equality but not identity, so you need to be careful about the types of your variables to ensure that the condition behaves the way you expect.

Wednesday, 25 July 2012

User-defined functions

One of the things which I initially found a little odd, although very simple to grasp, was the way that Uniface allows you to create modules of code as an entry, not a function.  This entry has any number of parameters (well, there probably is a limit) which can be defined as "in", "out", or "inout".  The only thing that is returned from this entry is a numeric status, which you return and this sets $status accordingly.  I think it's fairly common that developers use a status of "0" to indicate successful and positive for additional successful status, then a negative status for errors.  Any other type of value could be returned in an "out" or "inout" parameter.


This is a very simple mechanism and it works nicely.  However, sometimes it can lead to some rather verbose code, as you have to put the call to the entry on a separate line, you can't nest the call within an if or while statement as part of the condition, for example.  Here's a simple entry which converts kilometres into miles...


  entry kms_to_mls_entr
  params
    numeric kms : in
    numeric mls : out
  endparams
    mls = kms*5/8
    return 0
  end


To convert two values using the entry and then add them, it takes 3 lines, like this...

  call kms_to_mls_entr(kms1,mls1)
  call kms_to_mls_entr(kms2,mls2)
  total = mls1 + mls2



From version 9 onwards (sorry, I can't remember the minor version number) you have another option.  It took me a while to find any information in the manuals, but it's there; they're called "User-Defined Functions".  Here's an example...

  entry kms_to_mls_func
  returns numeric
  params
    numeric kms : in
  endparams
    return kms*5/8
  end



Notice that there is a returns statement before the params, which determines what datatype should be returned.  In this case it is numeric, but it doesn't need to be.  In this case, to convert the two values using this function, it takes a single line...

  total = kms_to_mls_func(kms1) + kms_to_mls_func(kms2)

This can make the code a bit neater.  But of course I'm obsessed with performance, so my next step is the test these two methods over 2,000,000 iterations...


  • entry = 00:17.74, 00:17.69, 00:17.60 (under 18 seconds)
  • function = 00:13.64, 00:13.64, 0:13.67 (under 14 seconds)

So as you can see, the code is more concise and it performs better.  It also relies on less variables defined, so it's a win-win all round really.  You can even do this with a global procedure.



A bit of a side note; I thought you always had to specify a datatype when defining a parameter, and that this was always a local variable.  I discovered recently that you can also use a component variable or a painted field name, in which case you don't specify the datatype.  For example...


  entry kms_to_mls_entr
  params
    numeric kms : in
    $miles$ : out
  endparams
    $miles$ = kms*5/8
    return 0
  end


Summary: It is possible to create a "user-defined function", which is an entry that returns a specific datatype, allowing you to create more concise code, that also performs better. 

Friday, 22 June 2012

Performance of selectcase

I have a colleague who doesn't really like to use the selectcase construct, despite it being very useful.  It can be used to do a different action based on the value of a variable or field, something like this...


  temp = "5"

  selectcase ( temp )
    case "1"
      temp = ""
    case "2", "3"
      temp = ""
    case "4"
      temp = ""
    case "5"
      temp = ""
    elsecase
      temp = ""
  endselectcase



This sort of conditionality can of course be replicated using a series of if statements, or nested if and elseif statements, which is what he prefers, like this...


  temp = "5"
  if ( temp = "1" )
    temp = ""
  elseif ( temp = "2" | temp = "3" )
    temp = ""
  elseif ( temp = "4" )
    temp = ""
  elseif ( temp = "5" )
    temp = ""
  else
    temp = ""
  endif



What I've been trying to tell him is that a selectcase is more readable, because it's easy to see that the condition is always the same but with a different value, although of course it doesn't need to be, but is in this situation.  


I also think it performs better, the reason being that the condition is only checked once and then the correct branch of code is used, rather than having to check each nested condition until it finds one that matches.  This is certainly true of the javascript equivalent (switch), so I thought I should test it out.  I did so over 5,000,000 iterations...



  • if/elseif/endif = 00:31.21, 00:29.60, 00:28.73 (around 30 seconds)
  • selectcase/endselectcase = 00:25.73, 00:23.53, 00:24.22 (around 25 seconds)

As you can see, it does perform better.  Admittedly, not by as much as I would have hoped, but it's still quicker!

Additionally, both of these constructs all you to have a "catch all" case which will run if none of the other values match (elsecase for selectcase and simply else for if).  

Summary: It is better to use a selectcase when appropriate, over a number of nested if statements, in order to provide a conditional statement that involves the same field with multiple values.

Friday, 25 May 2012

Are "if" statements breaking or non-breaking?

An interesting feature in Javascript is the ability to improve the performance of your "if" statements using a technique known as breaking, or sometimes shortcutting.  This is where you have multiple parts to the condition, which are specifically ordered.  You can use the "&" and "|" (bitwise) operators which do not shortcut, or "&&" and "||" (logical) operators which do shortcut.  For example...


    a = 1;
    b = 2;
    c = 3;
    d = 4;
    if ( a==b & c==d ) alert("true"); //bitwise
    if ( a==b && c==d ) alert("true"); //logical


The first "if" statement is working out that a does not equal b and thinking of this as 0, then working out that c does not equal d and thinking of this as 0, then working out that 0 and 0 is 0, therefore false and no alert appears.  


The second "if" statement is working out that a does not equal b and thinking of this as false, then stopping.  I knows that if the first part of the condition is false then anything after that is always going to result in false, so it breaks (or "shortcuts") and no alert appears.


I won't go into the difference between bitwise and logical, because this is a Uniface blog and not a Javascript one.  Suffice to say, in this situation they almost always behave the same, because 0 equates to false and 1 equates to true.  The important thing to note is the difference in whether they shortcut or not. 


A question I've wanted to know for a long time but never stopped to work out... Does Uniface shortcut when processing "if" statements?  Time to find out!


First I tested to see if there was a difference between "&" and "&&" in Uniface.  I'd noticed that both worked the way I expected logically, but never determined the difference.  The way I did this was with the following two blocks of code...

  • &
      count = 0
      total = 10000
      while ( count < total )
        count = count + 1
        if ( 1 = 0 & $itemnr(-1,list_field.dummy) = "" )    
          ;testing the condition itself
        endif
      endwhile

  • &&
      count = 0
      total = 10000
      while ( count < total )
        count = count + 1
        if ( 1 = 0 && $itemnr(-1,list_field.dummy) = "" )    
          ;testing the condition itself
        endif
      endwhile

I then populated the non-database "list.dummy" field with a list of 20,000 items.  As you may have read in one of my earlier posts, accessing the last item in a large string held in a non-database field is a relatively costly thing to do, certainly compared with referencing a numeric value.  This means that I expect the first part of the condition to process much quicker than the second part of the condition.

I won't give you the timings, it was quite boring, they both behaved exactly the same.  There was no difference between the timings, even when I upped the number of iterations from 10,000 to 1,000,000.  

I then moved on to trying to determine whether or not the second part of the condition was being processed or not, by also testing the following blocks of code...

  • False
      count = 0
      total = 10000
      while ( count < total )
        count = count + 1
        if ( 1 = 0 & $itemnr(-1,list.dummy) = "" )    
          ;testing the condition itself
        endif
      endwhile

  • True
      count = 0
      total = 10000
      while ( count < total )
        count = count + 1
        if ( 1 = 1 & $itemnr(-1,list.dummy) = "" )    
          ;testing the condition itself
        endif
      endwhile

Now these results I did find interesting...

  • False = 01:07.31, 01:08.92, 01:07.30 (just over a minute)
  • True = 01:49.12, 01:50.31, 01:49.52 (almost 2 minutes)

This means that Uniface is definitely shortcutting, which is great!  This means that performance can be gained by ordering the parts of the condition carefully.  Of course you could argue that you could split this up to achieve the same affect, like this...

      if ( 1 = 0 )    
        if ( $itemnr(-1,list.dummy) = "" 
          ;testing the condition itself
        endif    
      endif

I wouldn't have a leg to stand on, you'd be right.  However, what about "or" instead of "and"?  In this case, if the first part is true then there's no need to process the second part, because you already know that the final result will be true also.  Again, you could split it up like this...

      if ( 1 = 0 )    
        ;testing the condition itself
      elseif $itemnr(-1,list.dummy) = "" 
        ;testing the condition itself
      endif

...but this would mean duplicating whatever code was inside the "if" statement, and it's not nearly as readable as a single "if" statement.  So I tried again, but this time with "|" instead of "&"...

  • False = 01:49.51, 01:49.08, 01:49.84 (almost 2 minutes)
  • True = 01:07.02, 01:08.22, 01:07.19 (just over a minute)

Again, this means that Uniface is definitely shortcutting, which is great!  I expected the results to be opposite (true to be faster than false) because the logic of "and" and "or" are opposite (well, sort of, technically "and" and "exclusive-or" are, but we won't go into that!). 

Of course, if you had three or four or five parts, this could mean even greater performance gains could be achieved by thinking carefully about which part you put first.  Any function which you might use in a condition, like $length or $scan should always be used in later parts if possible.

Summary: There is no difference between "&" and "&&" or "|" and "||" as far as I can tell, but Uniface does shortcut when there are multiple parts to the condition in an "if" statement, therefore you should think careful about the order of the parts.