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.

Monday 18 June 2012

Undocumented feature - interrupt - part nine

I know that I said I was done after part eight, but I've had a report of some additional functionality that I thought I should add.



5 - Uniface "usys" directory


As previously discussed, using numeric code "5" returns the working directory.  However, you can also use it to display the Uniface "usys" directory...   

  interrupt(5,"USYS")
  uniface = $result



EDIT: Thank you to James Rodger for pointing out the alternative in his comment...


  uniface = $valuepart($fileproperties("usys:","FULLPATH"))


This in itself seems to be an undocumented feature; I can't find any reference to "usys:" being accessible like this in $fileproperties or $lfileproperties.


I tried to access some other Uniface directories in a similar way, such as "adm" and "bin", but it seems that you can only access "usys".


Thank you to Mark R for point this one out.

Saturday 16 June 2012

Undocumented feature - interrupt - part eight


Continuing from part seven where we looked at "989", I will now do a full summary of the known functionality that we've looked at in this series of posts...


Code Description Example Alternative
0File information
interrupt(0,"test.txt")
if ( $status < 0 )
  ;file doesn't exist
else
  list = $result
  name = $item("Name",list) ;"test"
  type = $item("Type",list) ;"txt"
  vers = $item("Version",list) ;""
  attr = $item("Attrib",list) ;"UUU"
endif
This doesn't seem to return any useful information now.


Could use:
$fileproperties or
$lfileproperties


These return lots more file information, but not the same.
2Extract filenameinterrupt(2,"C:\temp\test.txt")
filename = $result ;"test.txt"
Use string manipulation
(although interrupt takes 60% of the time)
3Extract file pathinterrupt(3,"C:\temp\test.txt")
filename = $result ;"C:\temp\"
Use string manipulation
(although interrupt takes 60% of the time)
5Working directoryinterrupt(5,"")
working = $result ;"C:\temp\"
Use:
$ldir
5Uniface "usys" directoryinterrupt(5,"USYS")
uniface = $result ;"C:\uniface\"
Use:
$fileproperties of "usys:"
6Concatenate file path$result "C:\temp"
interrupt(6,"test")
filepath = $result ;"C:\temp\test\"
Use string manipulation
(although interrupt takes 70% of the time)
7Parent directoryinterrupt(7,"C:\temp\test\")
parent = $result ;"C:\temp\"
Use string manipulation
(although interrupt takes 33% of the time)
10Directory listing (files)interrupt(10,"C:\temp")
list = $result ;"test.txt·;test2.txt"
count = $status ;2
Use:
$dirlist or
$ldirlist (with "FILE" topic)
11Directory listing (folders)interrupt(11,"C:\temp")
list = $result ;"test·;test2"
count = $status ;2
Use:
$dirlist or
$ldirlist (with "DIR" topic)
989Application focusinterrupt(989,"APPLFOCUS") No alternative
(doesn't seem to work anyway)
989Format string
$result "RPL"
interrupt(989,"FORMAT")
string = $result
Use string manipulation
(doesn't seem to work anyway)
989Undo gold characters$result "R·;P·!L"
interrupt(989,"UNDOGOLD")
string = $result ;"R;P!L"
Use:
$replace



It seems that the interrupt command isn't really very useful anymore.  The functionality has either been replaced by new Uniface functions, or can be achieved by string manipulation.  Given the performance improvements, it may be worth considering using interrupt if you are heavily handling file paths, but given the number of iterations needed to see the difference, I'd be surprised if this was ever worthwhile.


Given the numeric nature of the code, I'm sure that the unknowns (1, 4, 8 and 9) must do something too - I'd love to know what!  Probably some of 12-988 do something as well, so I'm sure there are some hidden gems within the interrupt command.  If you know of any, please let me know in the comments.

Undocumented feature - interrupt - part seven


Continuing from part six where we looked at "10", "11" and "12" onwards, we're now going to look at "989".  This has a number of different uses, which are used by the debugger, so should be relatively safe to use moving forwards.


989 - Application focus


When the debugger kicks in, maybe caused by a debug statement in your code, the focus is immediately set to the debugger.  Apparently this is done using the interrupt command...


interrupt(989,"APPLFOCUS")


As far as I can tell this doesn't work, maybe because I'm using Windows 7 for my testing.  When you hit a debug statement it does bring the debugger into focus, but if my Uniface application calls the interrupt command above whilst another application has focus, it does not come into focus as I would expect.





989 - Format string


According to PUUU, this is supposed to format a string by adding carriage returns in.  It's not clear on the details, but something like this...


$result "RPL"
interrupt(989,"FORMAT")
string = $result


However, I couldn't get this to work for me, $result remained unaffected.  If you wanted to do something like this then you could use string manipulation, but I'm not sure why you'd want to.  Again, this is supposed to be used by the debugger, but I can't recall seeing an behaviour like this myself.





989 - Undo gold


When the debugger displays lists, it always uses semi-colon (;) and exclamation mark (!) as the delimiter, instead of their gold equivalents.  Apparently it uses the following command to do it...



$result "R·;P·!L"
interrupt(989,"UNDOGOLD")
string = $result

This replaces the gold delimiters with their normal equivalents.  It does not replace the wildcard characters, only the delimiters.  This one does actually work!  It's easy to find an alternative using $replace however...

string = $replace($replace("R·;P·!L",1,"·;",";",-1),1,"·!","!",-1)

This code is probably slightly easier to read, as you can easily see which characters are included in the "undo".  How about the performance though, over 2,000,000 iterations...

  • interrupt = 00:07.89, 00:07.64, 00:07.73 (almost 8 seconds)
  • $replace = 00:07.95, 00:07.83, 00:07.88 (almost 8 seconds)
So as you can see, it takes pretty much exactly the same amount of time either way, so I'll definitely be sticking with the alternative, instead of using interrupt.

Summary: Numeric code "989" has multiple uses, but it seems that they either don't work any more, or are easily replaced with alternatives.  I'll do a full summary of the interrupt command in the next post.




Friday 15 June 2012

Undocumented feature - interrupt - part six



Continuing from part five where we looked at "7", "8" and "9"...



10 - Directory listing (files)


This sets $result to be a Uniface list of all the files in the specified directory.  It also sets $status to be the count of files returned, like this...


  interrupt(10,"C:\temp")
  list = $result
  count = $status


In my test case it sets $result to be "test.txt·;test2.txt" and $status to be 2.  At last, there's a nice Uniface function to replace this one; $ldirlist or $dirlist can be used.  These actually return folders and files by default, but you can specify the "FILE" topic to limit it.  Both of these methods allow you to use wildcards.  The functions do not return the number of files found, but as they do return the list, we can easily use $itemcount if you want this as well, like this...

  list = $ldirlist("C:\temp","FILE")
  count = $itemcount(list)


This is much cleaner code, very concise and readable.  So how does it perform, I hear you cry! I tested over 200,000 iterations...

  • interrupt = 00:17.10, 00:16.89, 00:16.96 (around 17 seconds)
  • $ldirlist = 00:14.68, 00:14.72, 00:14.67 (almost 15 seconds)

So there's not a lot in it, but it does performs better.  I think the alternative is definitely the way to go in this case.


11 - Directory listing (folders)


This sets $result to be a Uniface list of all the folders in the specified directory, similar to "10".  It also sets $status to be the count of folders returned, like this...


  interrupt(11,"C:\temp")
  list = $result
  count = $status

In my test case it sets $result to be "test·;test2" and $status to be 2.  Similarly $ldirlist or $dirlist can be used, but specifying the "DIR" topic this time, like this...

  list = $ldirlist("C:\temp","DIR")
  count = $itemcount(list)


Let's test the performance over 200,000 iterations again...

  • interrupt = 00:15.31, 00:15.30, 00:15.24 (over 15 seconds)
  • $ldirlist = 00:13.33, 00:13.37, 00:13.45 (over 13 seconds)

Again there's not a lot in it, but it does performs better.  I think the alternative is definitely the way to go in this case as well.


More unknowns


Having no indication of what they did, I ran some basic tests on the codes "12" to "988" (see the next post for why I stopped there).  Mostly it was pretty uninteresting, generally speaking they set $status to -1 and leave $result unaffected.  There were some that left $status unaffected as well, and most interestingly "18" sets $result to a blank string.  Unfortunately I couldn't work out anything more for any of them, but I'd like to investigate this further.


Summary: Numeric codes "10" and "11" are useful but have been replaced by $ldirlist and $dirlist which do the same thing, but better.  All the codes beyond that, seem to be useless.  Except "989" which we will tackle in the next post.




Undocumented feature - interrupt - part five


Continuing from part four where we looked at "4", "5" and "6"...


7 - Parent directory


This sets $result to be the parent directory of the file path that you specify, like this...


  interrupt(7,"C:\temp\test\")
  parent = $result



In this case $result would be set to "C:\temp\".  You could do this with string manipulation, but you need to be careful that you check to see if the last character is a delimiter or not...

  filepath = "C:\temp\test\"
  if ( $scan(filepath,"/") < 1 ) ;calculate delimiter
    del = "\"
  else
    del = "/"
  endif
  pos = $length(filepath)
  if ( filepath[pos] = del ) ;remove last character if delimiter
    filepath = filepath[1:pos-1] 
  endif
  pos = $rscan(filepath,del) ;find last delimiter
  filepath = filepath[1,pos] ;truncate string

As you can see, this isn't very concise either, and uses $rscan as well as $scan, so it's not likely to perform very well.  As with "6", I'll test with and without the code which calculates the delimiter, over 2,000,000 iterations...

  • interrupt = 00:05.16, 00:05.20, 00:05.09 (just over 5 seconds)
  • alternative = 00:17.74, 00:17.52, 00:17.67 (over 17 seconds)
  • without $scan = 00:15.62, 00:15.39, 00:15.41 (over 15 seconds)

Looking at these times, I'm struggling to argue for using the alternative!  I'm thinking I must be doing something wrong, so I'm going to try and refactor my alternative, without $rscan.  

I've just tried using $replace to convert the file path into a list, remove the last item (or two) and then convert it back again - this took slightly longer!  I'm going to have to give up with this for now, but I hope to tackle this again.


8 - Unknown


I haven't found any indication of what functionality this might be, but it does behave differently to "1" and "4", which are also unknown.  In this case it always returns a $status of 0 and $result is set to the string that you pass in.  I've tried all sorts of weird and wonderful things without getting anywhere, I'm giving up on this one for now too.



9 - Unknown


I haven't found any indication of what functionality this might be either.  This also behaves differently, as it always returns a $status of 0 but $result remains unaffected. 


Summary: Numeric code "7" could be very useful for finding the parent directory quickly, but "8" and "9" remain unknown.



Wednesday 13 June 2012

Undocumented feature - interrupt - part four


Continuing from part three where we looked at "2" and "3"...


4 - Unknown


I haven't found any indication of what functionality this might be, but it does behave differently to "1", which was also unknown.  In this case it always returns a $status of 0 and $result is a blank string.  I tried folders, subfolders, filenames and a blank string, with no difference in behaviour.  I'm giving up, for now.



5 - Working directory


As discussed in part one, using numeric code "5" returns the working directory...

  interrupt(5,"")
  working = $result

The alternative is to use $ldir...

  working = $ldir()

The alternative is more maintainable, safer and also performs better.


6 - Concatenate file path

This takes the file path which is currently in $result and then concatenates the path you pass in, automatically adding the folder delimiters where necessary.  Here's some example code...


  filepath = "C:\temp"
  $result filepath
  interrupt(6,"test")
  filepath = $result



This will then populate $result with "C:\temp\test\".  As you can see, I had excluded the delimiter from the end of both the original $result value and the value I passed it, but the interrupt command fixed that.  Again, this is another string manipulation example, I came up with something like this...



  filepath = "C:\temp" 
  if ( $scan(filepath,"/") < 1 ) ;calculate delimiter
    del = "\"
  else
    del = "/"
  endif
  pos = $length(filepath)
  if ( filepath[pos] != del ) ;check filepath ends with delimiter
    filepath = "%%filepath%%del%%%"
  endif
  filepath = "%%filepath%%%test%%del%%%" ;concatenate filepath



As you can see, this isn't very concise, primarily because I'm accounting for Windows and Unix environments again, meaning that I have to first calculate the "del" local variable.  You could of course use $concat instead of using substitution, like I have.


I tested these two methods over 2,000,000 iterations with the following results...

  • interrupt = 00:06.67, 00:06.75, 00:06.72 (almost 7 seconds)
  • alternative = 00:12.02, 00:12.00, 00:12.04 (about 12 seconds)
  • without $scan = 00:09.04, 00:09.78, 00:09.74 (almost 10 seconds)

I added a third option which was the alternative but with a hardcoded delimiter instead of calculating it, so the performance can be improved by having a global register that holds this.  Still, in this case the interrupt command is better for performance and is less lines of code.  

Summary: Numeric code "4" remains unknown for now, "5" has been replaced by a better alternative and "6" seems to still be useful, although I think I'd still opt for string manipulation which I know that I can control over an undocumented feature.


Tuesday 12 June 2012

Undocumented feature - interrupt - part three

Continuing from part two where we looked at "0" and "1"...



2 - Extract filename


This seems to extract the filename part of a full path, removing the directory structure.  Here's some example code for you...



interrupt(2,"C:\temp\test.txt")
filename = $result

In this case "filename" will be populated with "text.txt".  This does seem to be a simple case of string manipulation, but if you support both Windows and Unix filesystems (which the company I work for do, and I expect a lot of people will if they are doing any web development) then maybe it's not so simple...



filename = "C:\temp\test.txt"
pos = $rscan(filename,"\";for Windows
if ( pos < 1 )
  pos = $rscan(filename,"/";for Unix
endif
filename = filename[pos+1]



So this requires a numeric local variable and a few extra lines of code to achieve.  I tested for 2,000,000 iterations...



  • interrupt = 00:09.35, 00:09.30, 00:09.33 (over 9 seconds)
  • $rscan (Windows first) = 00:14.72, 00:14.75, 00:14.66 (under 15 seconds)
  • $rscan (Unix first) = 00:16.38, 00:16.41, 00:16.31 (over 16 seconds)

Unfortunately $rscan is a pretty slow function, but as they could be multiple folder delimiters in the string, I don't see how it can be avoided.  You have to choose in your code whether to put Windows or Unix first, unless you have a global setting somewhere which tells you which the application is running on, in which case you could limit this to a single $rscan.  

Whilst interrupt does perform better in this case, I would say that it is more than likely negligible, given the number of iterations.  Therefore, I'd choose to use the alternative, as I know that I can rely on it working in future.

3 - Extract file path


This seems to extract the file path part of a full path, removing the filename.  Here's an example...



interrupt(3,"C:\temp\test.txt")
filepath = $result

In this case "filepath" will be populated with "C:\temp\".  The alternative to this is therefore going to consist of a similar bit of string manipulation...

filepath = "C:\temp\test.txt"
pos = $rscan(filepath,"\";for Windows
if ( pos < 1 )
  pos = $rscan(filepath,"/";for Unix
endif
filepath = filepath[1:pos]


So there's no need to measure the performance of this, the different will be very similar.

Summary: Numeric codes "2" and "3" do seem to be useful and perform better than the alternatives.  However, the performance difference is probably not enough to consider using the interrupt command, as it will be less maintainable and risky moving forwards.  I'll continue in the next part.

Monday 11 June 2012

Undocumented feature - interrupt - part two

Carrying on from part one, I want to look at the functionality available using the undocumented interrupt command and see if I can find alternatives, preferably documented ones. So let's go through the known numeric codes...


0 - File information


Always a good place to start.  In this case you pass in the filename in the second parameter and $result is populated with a list of information about the file.  If the file does not exist the $status will be -1, so it could also be used for this check, but assuming it does exist then it will be 0.  


In the information on PUUU, $result will return the following list of properties...

  • Path
  • DiskDir
  • Name
  • Type
  • Version
  • Attrib
  • RecSize
  • Filesize

Here's some example code for you...

  interrupt(0,"test.txt")
  if $status < 0 )
    ;file doesn't exist
  else
    list = $result
    name = $item("Name",list)
    type = $item("Type",list)
    vers = $item("Version",list)
    attr = $item("Attrib",list)
  endif

You may notice that I am only referencing a subset of the properties in this example.  This because I've tested this in Uniface 9.5 and I only get these properties in the list.  This is the problem with undocumented features.  Assuming the information on PUUU was accurate when tested in Uniface 7.2, this functionality has changed and some properties are no longer being returned.  I hope no one was relying on them!

So what if we didn't have the interrupt command, could we still access this information?  My first though was "yes", using $fileproperties or $lfileproperties, which are well documented.  This has the following properties (or "topics")...

  • FILETYPE
  • FILESIZE
  • FULLPATH
  • CREATIONDATE
  • MODIFICATIONDATE
  • ACCESSDATE
  • FILEATTRIBUTES
  • COMPRESSEDSIZE
  • CHECKSUM
  • METHOD
  • ZIPFILENAME

Unfortunately when I compare the output of the two, I don't really get any correlation at all.  For my "test.txt" file...

  • Name = "test"
  • Type = "txt"
  • Version = ""
  • Attrib = "UUU"

I have no idea what the attributes "UUU" means, but the topic FILEATTRIBUTES returns a much more sensible "A", meaning that the file is archived and not hidden or system, etc.  Also version is blank, so I don't know if that genuinely is the version, or whether this property is no longer being populated correctly, making it very hard to investigate.

When it comes to name and type, these seem to be a simple case of cutting up the filename, rather than actually being file properties in themselves.  I could compare the performance of the string manipulation over the call to interrupt, but I think in this case I'm going to accept that this command doesn't return any useful information, and disregard it.


1 - Unknown


I have not found any indication of what functionality this might be, but given the incremental nature of the numeric code I have to assume it does something.  My first thought was that it might be the same as "0" but for folders instead of files, unfortunately it always returns a $status of -1 and $result is unaffected.  I tried folders, subfolders, filenames and a blank string, with no difference in behaviour.  I'm giving up, for now.


Summary: Numeric codes "0" and "1" don't seem to be useful.  I'll continue in the next part.