|
Performance of Function Subprocedures
Published: June 27, 2007
by Ted Holt
Note: The code accompanying this article is available for download here.
Last week, I wrote about performance issues when parameters are passed to subprocedures. This week, I write about a similar matter. If a subprocedure returns a value to the caller (i.e., the subprocedure serves as a function), the computer has to store the returned value somewhere in memory. The fact that the system must allocate memory for returned values means that performance may be affected.
To test the performance of function subprocedures, I wrote two substring functions. The first one, SubString, works like the %SUBST built-in function that IBM gives us as part of the RPG compiler, but it differs in two ways:
- If the length of the substring from the beginning position exceeds the end of the string, the remainder of the string is returned. This is the same behavior %SUBST exhibits when it receives no third parameter. Fewer characters may be returned to the caller than the number requested in the third parameter.
- If the beginning position and/or length parameter is invalid, the function returns an empty string.
Here's a version of SubString that works with 64-byte strings:
P SubString b
D pi 64a varying
D inString 64a varying const
D inPosition 10i 0 const
D inLength 10i 0 const
D
D EmptyString c const('')
/free
if inPosition <= *zero
or inLength <= *zero
or inPosition > %len(inString);
return EmptyString;
endif;
if (inPosition + inLength - 1) > %len(inString);
return %subst(inString: inPosition);
endif;
return %subst(inString: inPosition: inLength);
/end-free
P e
Notice that all three parameters are passed by read-only reference, so there is no parameter overhead of any significance. For those of you who are still fuzzy about subprocedures, the second line is the one that defines the return value.
D pi 64a varying
The second subprocedure, SubStringThru, differs from %SUBST in the definition of the third parameter. In both functions, the first parameter is the string from which the substring is to be derived, and the second parameter is the beginning position. However, SubStringThru's third parameter is the ending position of the substring, not the length of the returned string. For example, SubStringThru (SomeString: 4: 10) means "the characters in positions four through ten of SomeString", which is equivalent to %SUBST(SomeString: 4: 7).
I wrote two versions of SubStringThru. Version 1 of SubStringThru uses a modular programming technique my professors pounded into my head when I was getting my computer science degree--build new routines from existing routines, otherwise known as "don't reinvent the wheel." Version 1 uses the SubString subprocedure to do its grunt work. Here is the subprocedure defined to return a 64-byte value.
P SubStringThru b
D pi 64a varying
D inString 64a varying const
D inBeginPos 10i 0 const
D inEndPos 10i 0 const
D
D Length s 10i 0
D EmptyString c const('')
/free
Length = inEndPos - inBeginPos + 1;
return SubString(inString: inBeginPos: Length);
/end-free
P e
Version 2 does its own grunt work.
P SubStringThru b
D pi 64a varying
D inString 64a varying const
D inBeginPos 10i 0 const
D inEndPos 10i 0 const
D
D Length s 10i 0
D EmptyString c const('')
/free
Length = inEndPos - inBeginPos + 1;
if inBeginPos <= *zero
or Length <= *zero
or inBeginPos > %len(inString);
return EmptyString;
endif;
if (inBeginPos + Length - 1) > %len(inString);
return %subst(inString: inBeginPos);
endif;
return %subst(inString: inBeginPos: Length);
/end-free
P e
Now for the tests. I used the same method I used in the previous article. That is, rather than use some mechanism that measures in fractions of seconds (e.g., PEX), I chose to measure performance in CPU seconds. I chose this method because I believe it gives a truer picture of how subprocedures perform in production. I've included the code in a zip file, in case you'd like to do your own testing.
The first performance test consisted of 500,000 calls to the various substring techniques. I used two lengths of return values--64 bytes and 64-kilobytes. Take a look at this table.
|
Technique
|
64-byte return values
|
64-kilobyte return values
|
|
SubString
|
1
|
9
|
|
SubStringThru version 1
|
1
|
18
|
|
SubStringThru version 2
|
1
|
9
|
The numbers indicate elapsed CPU seconds. Here are the same figures for 2,500,000 calls.
|
Technique
|
64-byte return values
|
64-kilobyte return values
|
|
SubString
|
1
|
45
|
|
SubStringThru version 1
|
2
|
89
|
|
SubStringThru version 2
|
1
|
45
|
The last two lines of the table tell why I wrote the second version of SubStringThru. The first version performed terribly because each invocation generated a second subprocedure invocation, an invocation of SubString.
The lessons I learned from these tests are similar to those I shared with you in the previous article.
- The size of the return value is important. The larger the return value, the longer the run-time. Returning a value from a function subprocedure is similar to passing a parameter by value-the system has to allocate memory. The more memory, the longer the allocation and deallocation time.
- Second, consider the frequency of the call. If a subprocedure is likely to be invoked frequently during a program, keep the return value as short as possible.
- Third, as wonderful as modular programming is, building routines from other routines can have a terrible effect on performance.
I still like subprocedures and recommend that developers use them. But I have decided that I will try to be more judicious about the way I implement them.
I have yet more to cover concerning performance and subprocedures. Maybe next week or the week after.
RELATED STORY
Parameter Passing and Performance
Post this story to del.icio.us
Post this story to Digg
Post this story to Slashdot
|