I am currently in the process of looking at our application performance and i have a question.
Does the ABL compiler do any kind of code optimization?
The question came up since i found that function/method calls are pretty expensive and function inlining would give some parts of my application a pretty significant speed boost.
I know i can counter this problem with includes but thats not a very pretty solution.
Ok it seems that there is no optimizazion at all.
Why is
ASSIGN
lcTest1 = "test"
lcTest2 = "test"
.
faster than
lcTest1 = "test".
lcTest2 = "test".
Are the any plans to integrate at least the most basic optimizations in the compiler?
While I agree that some level of optimization in the compiler would be nice... it is pretty rare that I find the actual cost of function calls to be the main issue with an application. And I wouldn't hold my breath for compiler optimization to show up.
In isolated tests it is easy to see the difference but rarely in the real world. Almost always it comes down to DB access and/or network traffic. I am not saying that your app isn't one of those exceptions but I am curious as to how big of an issue you found and what kind of percentages we are talking about. I assume you ran the Profiler Tool with your application to see exactly how much time was spent on each line of code?
I don't think my app is a big exception, and such optimisations will of course not result in a magical leap in performance. But in the whole scheme of things, optimisations like this would speed up nearly every OpenEdge application. Maybe only by a few percent (or less) but thats still a huge gain in some cases.
There is a reason why nearly every compiler does stuff like this. and as far as i am concerned there is nothing that speaks against optimisations like this.
I agreed that optimization is a good thing. But Progress hasn't implemented it in the last 30 years so I wouldn't expect it to happen soon.
Especially since it would only result in minimal gains when there are many things that are more important on the to-do list. Note I am not speaking for Progress here... just what I consider real world business decisions that they are making.
You can raise the issue again in the suggestions forum here and try and get people to vote it into one of the next releases.
One has to see these things in perspective. One periodically sees some thread about how slow X is in ABL compared to C or whatever, but most of the time the operation in question is one that one would have to do in a tight loop with 100,000 iterations to even notice the amount of time that it took. Users are simply not going to notice that kind of difference by comparison with poorly written queries or poorly tuned databases where one can measure the time taken on a watch with no second hand or numbers. Don't sweat differences that are going to be lost in the noise. Instead, glory in how quickly you can implement changed requirements in ABL compared to those other languages.
No i can't accept that.
There is potential to speed up the ABL runtime with things that nearly every other language has already done (with a good reason).
Yes the user won't notice a few ms faster loops, but the time adds up and in the whole scheme of things you save seconds or even minutes for long running programs.
I can't praise how easy it is to program in ABL when the users complain about performance and i find that there is no kind of optimization at all.
(That is ABL performance and not databse, which isn't great by itself without the Appserver)
Optimizations such as those you mention would not be noticeable to the vast majority of applications. And, as you say, if there is a specific case where it makes a difference there are ways to get the results that you need,
I have profiled a lot of application code. Those sorts of items have never risen above the noise.
The things that really matter are almost always data access.
Shaving a few microseconds here and there pales in comparison to the whole seconds, minutes or even hours that focusing on data access can save.
String manipulation functions and math routines sometimes make it onto the list if something is being done a lot of times in a loop.
The thing that speaks against these optimizations is simple -- allocation of development resources. Progress has limited resources. Optimizing something that doesn't actually matter is a poor use of that time. Yes, it would be "nice" if the compiler did some of these things but, personally, until the development team is funded like Microsoft, Oracle or a Congressional Fact Finding Tour, I would much rather that those resources be directed in more beneficial directions.
Sublam,
>> I can't praise how easy it is to program in ABL when the users complain about performance and i find that
>> there is no kind of optimization at all.
You are throwing this out without proving your case. How about providing us a real world program which your users are telling you performs poorly along with exact information on how "poorly" is defined by them (or you)? We can then help you in determining if there are ways to resolve the performance issue(s) using the product as it now exists. There are lots of very experienced developers here who know how to get large scale systems with extremely large databases to perform exceedingly well. Take advantage of that.
Sincerely, Brian Maher
If you have a program that runs for two hours is anybody going to care that it now runs in 1 hour and 59 minutes?
Those are the times when you need to be addressing the root problem where most of the time is spent.
ABL performance is usually just fine. Running Client Server (which is a data access issue btw) is another issue but there are numerous things you can do to speed that up. Same with DB access in general.
In this thread (so far) you have about 90 years of combined experience tuning some of the largest Progress installs in the world.... telling you these kind of optimizations don't matter that much in the real world.
EDIT: Ok... now Brian has posted... we have to revise the 90 years up a little bit :-)
Amdahl's Law: The performance enhancement possible with a given improvement is limited by the fraction of the execution time that the improved feature is used.
If it is possible to improve performance, a method must deliver that improvement. It is unacceptable for a performance remedy to require significant investment input but produce imperceptible or negative end- user impact.
TheMadDBA,
Add +20 to the years with PSC (official as of 5/15/15) and untold numbers of support cases resolving performance issues in code. :-)
That's a good link Tom and something often overlooked by developers. Business impact counts for much.
Nice, Tom!
A long time ago one of my coworkers described it as trying to swat mosquitoes off your arm while an elephant was sitting on your chest.
> The question came up since i found that function/method calls are pretty expensive and function inlining would give some parts of my application a pretty significant speed boost.
Can you be more specific? What parts? What portion of total execution time is made up of those parts? How much of an improvement? What is the business impact of the proposed improvements?
Optimizations such as those you mention would not be noticeable to the vast majority of applications. And, as you say, if there is a specific case where it makes a difference there are ways to get the results that you need,
I have profiled a lot of application code. Those sorts of items have never risen above the noise.
The things that really matter are almost always data access.
Shaving a few microseconds here and there pales in comparison to the whole seconds, minutes or even hours that focusing on data access can save.
String manipulation functions and math routines sometimes make it onto the list if something is being done a lot of times in a loop.
The thing that speaks against these optimizations is simple -- allocation of development resources. Progress has limited resources. Optimizing something that doesn't actually matter is a poor use of that time. Yes, it would be "nice" if the compiler did some of these things but, personally, until the development team is funded like Microsoft, Oracle or a Congressional Fact Finding Tour, I would much rather that those resources be directed in more beneficial directions.
Flag this post as spam/abuse.
I can give you a real live example.
I have a static class with a static method (overloaded for each datatype) which checks if a variable is "empty", for example with a character variable it checks if it is ? or ""
This method is used everywhere in my application.
I checked the clientlog of the initial start of my application an this method gets called about 2000 times.
I wrote a small benchmark that checks the execution of 2000 inline calls and 2000 method calls.
The inline call takes 1 ms, the method call takes ~400 ms
/* ******************** */
DO liCount = 1 TO 2000:
IF lcTest = ? OR lcTest = "":U THEN DO:
/* foo */
END.
END.
VS
DO liCount = 1 TO 2000:
IF Checks:EmptyVar(lcTest) THEN DO:
/* foo */
END.
END.
/* ******************** */
This is nearly half a second i lose in startup time because there is no optimization, and this is not the only function that is called many times.
This is not some unrealistic 1000000 call loop this is a real live example where compiler optimization would bring a notable performance improvement.
I can give you a real live example.
I have a static class with a static method (overloaded for each datatype) which checks if a variable is "empty", for example with a character variable it checks if it is ? or ""
This method is used everywhere in my application.
I checked the clientlog of the initial start of my application an this method gets called about 2000 times.
I wrote a small benchmark that checks the execution of 2000 inline calls and 2000 method calls.
The inline call takes 1 ms, the method call takes ~400 ms
/* ******************** */
DO liCount = 1 TO 2000:
IF lcTest = ? OR lcTest = "":U THEN DO:
/* foo */
END.
END.
VS
DO liCount = 1 TO 2000:
IF Checks:EmptyVar(lcTest) THEN DO:
/* foo */
END.
END.
/* ******************** */
This is nearly half a second i lose in startup time because there is no optimization, and this is not the only function that is called many times.
This is not some unrealistic 1000000 call loop this is a real live example where compiler optimization would bring a notable performance improvement.
Flag this post as spam/abuse.
Sry here it is:
METHOD PUBLIC STATIC LOGICAL EmptyVar(cVar AS CHARACTER):
RETURN cVar = "":U OR cVar = ?.
END METHOD.
You wrote:
DO liCount = 1 TO 2000:
IF lcTest = ? OR lcTest = "":U THEN DO:
/* foo */
END.
END.
It is obvious that this code does nothing. Instead of doing 2000 times nothing, it might as well do .. nothing. You say it takes 1 millisecond. That's a bit more than nothing, but the conclusion of that speed MIGHT be that the compiler has an optimizer that decided to do nothing.
DO liCount = 1 TO 2000:
IF Checks:EmptyVar(lcTest) THEN DO:
/* foo */
END.
END.
There is no way of knowing what the implementation of method EmptyVar is. Perhaps it writes a record in database-table EmptyVar. The compiler cannot see that from here, so it cannot optimize it. The compiler has no other choice than to call this method 2000 times.
What I am trying to say is, how do you know that the compiler does not optimize? I don't know, but the example snippets could suggest otherwise.
Hello,
I personally like compiler optimizations.
I tested the code with 11.5.1 on a Windows laptop and obtained 2-3 ms for the test with the code inline and 16-17 ms when calling the method.
However, occasionally for the test calling the method, I would get 5 ms. This seemed to point to another variable that just the plain execution of the code.
I tried to avoid a potential PROPATH search and looked at Process Monitor but did not see any file activity.
I also tested by comparing using > "" but did not notice a performance difference.
I did not test with the code inline using include files.
I am including below the code that I used to test.
I hope this helps.
DEFINE VARIABLE liCount AS INTEGER NO-UNDO.
DEFINE VARIABLE lcTest AS CHARACTER NO-UNDO.
DEFINE VARIABLE checks AS Checks NO-UNDO.
DEFINE VARIABLE liTests AS INTEGER NO-UNDO.
DEFINE VARIABLE liTime AS INTEGER NO-UNDO.
DEFINE VARIABLE liTotalTime AS INTEGER NO-UNDO.
checks = NEW Checks().
REPEAT:
ETIME(YES).
DO liCount = 1 TO 2000:
IF Checks:EmptyVar(lcTest) THEN DO:
/* foo */
END.
/*
IF lcTest = "":U OR lcTest = ? THEN DO:
/* foo */
END.
*/
/*
IF lcTest > "":U THEN DO:
END.
ELSE DO:
/* foo */
END.
*/
END.
liTime = ETIME.
MESSAGE liTime.
liTests = liTests + 1.
liTotalTime = liTotalTime + liTime.
PAUSE MESSAGE "Press spacebar to repeat test or ESC to exit".
END.
MESSAGE "Tests run:" liTests "Average time:" (liTotalTime / liTests).
[quote user="sublam"]
I can give you a real live example.
[/quote]
I'll politely disagree, since real-life code usually doesn't have empty loops that do nothing.
[quote user="sublam"]
The inline call takes 1 ms, the method call takes ~400 ms
[/quote]
If I test this in 11.5.1, I see timings of 3ms vs 14 ms.
I know our developers *have* been working to optimize the performance of method calls in pretty much every release since we introduced support for classes though, because that was an area where (time spent * number of times invoked) was significant portions of total execution time.
So if you're on an older release, worse timings aren't surprising.
[quote user="sublam"]
This is nearly half a second i lose in startup time
[/quote]
And what is your total startup time ?
I would like to see compiler optimizations as well. But as many others have already said, if you really want to improve performance there are usually bigger bottlenecks to tackle first.
In 10.2B08 I see differences of 21ms and 6ms. That is the oldest version I have installed.
Show us your PROPATH... is the code locally stored on the PC or is it stored on a network share? Same thing for your temporary files ( -T) and the OE executables.
Something else is going on. I am not even going to question why your application would need to check 2000 different variables to start up.
FYI, this is the code of the class that I am using to test. I tested with EmptyVar() as a static method and also calling the method in an instance. I see about the same time.
CLASS Checks:
METHOD PUBLIC /* STATIC */ LOGICAL EmptyVar(cVar AS CHARACTER):
RETURN cVar = "":U OR cVar = ?.
END METHOD.
END CLASS.
Sublam,
I ran the following code on 64 bit 11.5.1 running on Windows 7 in a VMware VM running on an iMac. The VM has 2 CPUs running at 2.7 Ghz (Intel I5 CPU) with 5 Gigs of RAM.
The results I get without using -q are values of 6 / 16 for each DO block and values of 5 / 14 for each DO block when I use the -q parameter.
That is massively different than the ~400 ms you are saying you see.
Please provide details as to the version of Progress/OpenEdge you are using along with details on your operating system and hardware.
Code:
/* Custom.p */ DEFINE VARIABLE liCount AS INT64 NO-UNDO. DEFINE VARIABLE lcTest AS CHARACTER NO-UNDO. ETIME(TRUE). DO liCount = 1 TO 2000: IF lcTest = ? OR lcTest = "":U THEN DO: /* foo */ END. END. DISPLAY ETIME. ETIME(TRUE). DO liCount = 1 TO 2000: IF TestClass:DoTest(lcTest) THEN DO: /* foo */ END. END. DISPLAY ETIME. /* TestClass.cls */ USING Progress.Lang.*. CLASS TestClass: METHOD PUBLIC STATIC LOGICAL DoTest(INPUT pcValue AS CHARACTER): RETURN pcValue = "":U OR pcValue = ?. END METHOD. END CLASS.
Brian
Also... instead of messing around with clientlog and trying to guess where the time is spent just download the Profiler Tool and let it tell you exactly where the time is being spent. Having line by line timings will reveal everything that you need to know.