|
Preamble
Below is a series of recorded conversations, links and code samples presented by a number of members from the Database Advisors List.
Most of the comments and sample code were provided by Gustav Brock, Shamil Salakhetdinov and Stuart McLachlan; Jim DeMarco, Fred Hooper and Marty Connelly also contributed.
The communications centered on the performance levels that could be obtained through various methods code optimization using languages like MS Visual basic, Fortran, PowerBasic, FreeBasic, C#, VB.Net and C++
All comments are actual conversation so editing has been kept to a minimum, so as not to change the authors’ comments.
The following composite was assembled and installed on the DBA site by Jim Lawrence and the grammar, spelling and composition checked By Susan Harkins
Array Performance
Gustav Brock started
the conversation with the following question:
Hi all
I have some routines that manipulate data in and between some arrays.
The arrays are not large but they are recalculated about 3 mio times which runs on my machine to 90 seconds.
I was wondering if there would be any significant speed increase by moving these routines to a DLL. I have no experience in writing such libraries but guess I could find as the routines are uncomplicated.
Variables are Long mostly, only one Single.
It would take me some time to program these, of course, and if the speed increase only could be expected to be, say, 30%, it would hardly be worth it.
Does anyone (Drew, Stuart?) have experience about what to expect?
/gustav
Response from Jim
DeMarco
Gustav,
I'd expect some speed increase from compiled code but how much is hard to say. It's doubtful that it would cut the time in half but it's certainly a possibility.
Are your routines in class modules? If not and you can write them into a class or classes I'd be glad to compile them for you so you could test.
Jim DeMarco
Response form Stuart McLachlan
For something like that I'd definitely build a real DLL in PowerBasic. PB has built in array manipulation functions which are highly optimized and the speed gains compared to doing this sort of thing in VB/VBA would be dramatic.
Create a VB "DLL" is likely to have little effect on speed, it could even slow it down because of the calling overhead.
I'm leaving town in a couple of minutes for the weekend, but email me off list with your current array procedures and I'll take a look at building a simple DLL for you when I get back.
Still, if anyone with PowerBasic or other compiler could make similar tests and publish the results,
Stuart
Commented from Stuart
McLachlan
The DLL was programmed and compiled in FreeBASIC and the FBIde which I earlier posted links for.
Setting lngLoopMax to 10^6 returns these running times for the two functions:
ArrayTimeLocal: 34 seconds
ArrayTimeDLL: 2.2 seconds (measured with 10^7 loops)
Thus this simple tool gives a speed improvement of 15.5 to 1 ... not bad!
Response from Gustav
Brook:
Hi Stuart
Thanks! This is a bit surprising; I had expected it to run faster.
/gustav
Further response from
Stuart McLachlan:
On my machine with the same code compiled to DLL in PowerBasic:
ArrayTimeLocal: 27 (10^6)
ArrayTimeDLL: 2.7 (actually 27 secs for 10^7)
An improvement of 10:1 .
Looks like for this set of operations FreeBASIC does a better job than PowerBASIC.
Response from Marty
Connelly:
Just one hint for VBA code speed on this line since everything is being done by integers
If alngTmp(lngItem, 1) / 10 = 100 Then
On a run of 50 seconds
If I use integer division with the "\" operator, I save around 1 second
If alngTmp(lngItem, 1) \ 10 = 100 Then
If I use multiplication instead of division, I save 5-6 seconds It does a type conversion to a long, but I have never seen a full documented description of this from MS so I don't trust it, so I would use CLng which would add overhead.
If alngTmp(lngItem, 1) * 0.1 = 100 Then
Some optimizing compilers have options that might change division by a constant to multiplication of it's inverse.
Response from Fred
Hooper:
Hi Gustav,
His son, who now works with him, *likes* to program in Assembler (otherwise, he seems normal).
They used it to replace the millions-of-times-called routines in a ray tracer, which replaces the vector screen display if enough time elapses to finish the computations. The product is for kitchen design and, like the others but better, shows what the kitchen will look like. It's interesting to watch the screen change, it's as though the display changed to a picture.
Fred
Response from Gustav
Brock:
Hi Fred
Sounds interesting and in line with the comments from Mr. PowerBasic (Stuart).
By the way, how did your brother-in-law write the Assembler version? With a tool of some sort or directly? Are you saying he obtained a 50:1 improvement in total compared to VB? I could probably handle PowerBasic without much trouble, but Assembler is far away.
/gustav
And further comment
to Fred's message by Gustav Brock:
Hi Fred
That son deserves respect!
It is years ago I gave away my original plastic box with Microsoft Assembler to some local computer nerds collecting rarities.
But how can you handle Assembler today?
I located these links which may be of interest to some of you:
About HLA, High Level Assembly (contradicting?):
http://www.linuxjournal.com/article/8408
Main page of HLA which is free:
http://webster.cs.ucr.edu/AsmTools/HLA/index.html
Writing DLLs with HLA:
http://webster.cs.ucr.edu/AsmTools/HLA/HLADoc/HTMLDoc/dll.html
An IDE for HLA, RadASM:
http://radasm.visualassembler.com
For the next step, MASM32:
If this is too much, how about FreeBASIC, a free QBasic compiler that can compile DLLs and use in-line assembler:
And an IDE for this, FBIde:
/gustav
Response from Gustav
Brock to Jim DeMarco:
Hi Jim
Sounds great!
No they are not classes. I simply pass ByRef an array to a function which shuffles around the content of the array ccording to some rules and other params. Would I need to wrap such a function in a class?
/gustav
Comment added from
Marty Connelly:
Some of these replacements for VB intrinsic functions might speed things up.
Make sure you read the accompanying notes before using some may require a TLB
http://www.xbeat.net/vbspeed/index.htm
But it sounds like you are using mostly math functions.
Trig functions can be enhanced from basic VBA calls but would require C.
Sometimes you can get a bit more speed from multidimensional arrays by the order of looping on the indexes.
For example you are not going to get much speed increase by using *0.5 instead of /2 maybe 1-2%
Comment from Jim DeMarco:
Private Declare Sub ShuffleArray
Lib "gustavsdll" (ByVal
p1 As Long,
ByVal p2 As Long, ByRef aSomeArray()
As Long)
Versus:
Dim oMyArrayTool as gustavdll.ArrayTool
Set oMyArrayTool = new ArrayTool
oMyArrayTool.GoGetThatArray(lngP1, lngP2, MyArray())
Admittedly more code but not that complicated (same result either way). One an API note I have seen components that had COM and API style interfaces. I don't know if API style access is inherent in COM objects though. Could those developers have created two interfaces to the components? Maybe someone else can answer that.
Jim D.
Response from Gustav
Brock:
Hi Jim
I see now what you mean. No, that is indeed not complicated.
/gustav
... and ...
If you are working with really for sure definitely absolutely fixed size arrays in VB6 dll you can turn off the Remove Array Bounds Checks option for about 25% speed performance increase.
With the chance of a GPF (Note: General Protect fault errors are caused when using code that does not consistently manage it's variable sizes. This results in application or/and OS failures.)
/gustav
Response from Shamil Salakhetdinov:
Jim,
If you set Instancing property of ArrayTool custom class to GlobalMultiUse then you can just write:
GoGetThatArray lngP1, lngP2, MyArray()
...in client code.
Shamil
Comment from Marty Connelly:
Personally if I am using arrays or a lot of matrix math like Fourier Transforms. I develop in Intel Fortran compiler for Windows & Visual Fortran but I can write Fortran in my sleep. It is COM compliant and callable from VBA. I have written DOS assembler a long time ago what I could do in a day in Fortran would take me 6 weeks in assembler just because I would have to relearn so much. Microsoft VB with arrays has a lot of overhead of error, type and range checking.
Come to think of it IBM Fortran compilers and maybe others used to produce an optional ASM listing that might help you out.
For Intel Fortran Note: it also optimizes on array loops for parallel processing assuming multi cores.
http://www.intel.com/cd/software/products/asmo-na/eng/compilers/fwin/219725.htm
Response from Gustav
Brock:
Hi Marty
Thanks! For those of you working with dot net note that this compiler (also) integrates with Visual Studio. However, it is so many years since I touched Fortran as a happy student that I have forgotten it 99%.
It certainly looks like a perfect choice for heavy floating point operations - which I don't need - at a fair cost.
It could be interesting to see how the computing time would be in VB(A) for the sinus approximation sample here:
http://www.intel.com/software/products/compilers/fwin/docs/Getting_Started.htm
/gustav
Comment from Gustav
Brock in response from comments from Marty Connelly, Shamil
Salakhetdinov, Fred Hooper etc...
Hi Marty, Shamil, Fred et all
Well, I did a small and quite surprising test inspired by Shamil. Here is a test routine which can be seen as typical for what I do. The array is not large but it is read from and written to a bunch of times.
Here is the local test using standard VBA:
Function ArrayTimeLocal(ByVal lngLoopMax
As Long) As Long
Const lngItems
As Long = 100
Dim alngTmp(1
To lngItems, 1 To 2)
As Long
Dim lngLoop As Long
Dim lngItem As Long
Dim lngResult As Long
Dim lngSeconds
As Long
Dim dblStart As Double
Dim dblStop As Double
dblStart
= Timer
For lngLoop = 1 To lngLoopMax
For lngItem = 1 To lngItems
alngTmp(lngItem, 1) = lngLoop * 10
If alngTmp(lngItem, 1) / 10 = 100 Then
lngResult = 1
Else
lngResult = 0
End If
Next
Next
dblStop
= Timer
lngSeconds = CLng(dblStop - dblStart)
ArrayTimeLocal = lngSeconds
End Function
And this is the test function using a DLL containing nearly identical code:
Declare Function ArrayTime Lib
"arraydll.dll" Alias "ArrayTime@4" (ByVal
lngLoops As Long) As Long
Function ArrayTimeDLL(ByVal lngLoopMax
As Long) As Long
ArrayTimeDLL = ArrayTime(lngLoopMax)
End Function
The DLL was programmed and compiled in FreeBASIC and the FBIde which I earlier posted links for.
Setting lngLoopMax to 10^6 returns these running times for the two functions:
ArrayTimeLocal: 34 seconds
ArrayTimeDLL: 2.2 seconds (measured with 10^7 loops)
Thus this simple tool gives a speed improvement of 15.5 to 1 ... not bad!
Nothing comes free, and the trouble is - as far as I can see - that the dll has to be programmed to run rock stable. If it errors out, Access halts or simply quits - no errors, no GPF, just poof away.
With this result I think I'll leave the assembler stuff for now. Still, if anyone with PowerBasic or other compiler could make similar tests and publish the results, we could rank these.
/gustav
Response from Shamil Salakhetdinov:
Hi Gustav,
With switched off array bounds check and integer overflow check your sample code runs in less than 1 second when in VB6 bActiveX dll.
Similar code in C++(VS.NET 2003) runs instantaneously.
C++ allows to measure time when 10^6 cycles are additionally cycled 10^7 times!
Below is sample C++ code. Maybe I did make some mistakes -
10^7 * 10^6 loops in just two seconds looks incredible speed gain under C++ - these are
989,999,010,000,000 cycles...
And in 19 seconds 9,899,990,100,000,000 cycles can be executed.
long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt);
int _tmain(int argc, _TCHAR* argv[]) {
long dblStart; long dblStop; long lngResult; long lngCnt = 0;
__int64 totalCnt = 0;
time(&dblStart);
for (int i = 1; i<=10000000; i++) {
lngResult = ArrayTimeLocal(1000000,
lngCnt);
totalCnt += ((__int64)lngCnt);
if ((i % 1000000) == 0)
printf("Result
= %ld, Counter = %I64d\n",
lngResult, totalCnt);
}
time(&dblStop);
lngResult = (long)difftime(dblStop,dblStart); printf("Final Result = %ld, Final Counter =
%I64d\n",
lngResult, totalCnt);
}
long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt)
{
const long lngItems = 100;
long
alngTmp[lngItems-1][1];
long lngLoop;
long lngItem;
long lngResult;
long lngSeconds;
long dblStart;
long dblStop;
lngTotalCnt = 0;
time(&dblStart);
for (lngLoop=1; lngLoop < lngLoopMax; lngLoop++) {
for (lngItem=1; lngItem < lngItems; lngItem++) {
alngTmp[lngItem-1][0]
= lngLoop * 10;
if
(alngTmp[lngItem-1][0] / 10 == 100) lngResult = 1;
else lngResult = 0;
lngTotalCnt++;
}
}
time(&dblStop);
lngSeconds = (long)difftime(dblStop,dblStart);
return lngSeconds;
}
And here is result of this code test run:
Result = 0, Counter = 98999901000000
Result = 0, Counter = 197999802000000
Result = 0, Counter = 296999703000000
Result = 0, Counter = 395999604000000
Result = 0, Counter = 494999505000000
Result = 0, Counter = 593999406000000
Result = 0, Counter = 692999307000000
Result = 0, Counter = 791999208000000
Result = 0, Counter = 890999109000000
Result = 0, Counter = 989999010000000
Final Result = 2, Final Counter = 989999010000000
Shamil
Comment from Gustav
Brock:
Hi Shamil
I just read your post following this, and your conclusion about C# seems to be true.
Your results from C++ is an eye-opener. I hardly can believe that speed improvement!
But for a true comparison, could I persuade you to compile it to an DLL and call that from VB(A) the same way I did with the DLL compiled in FreeBASIC?
You would probably need 10^7 loops or - if you measurement holds - 10^8 loops.
/gustav
Code sample from Shamil Salakhetdinov:
Gustav,
I think if you say that you have forgotten 99% Fortran then you can convert your code into C++ with same efforts as on Fortran. I mean - if your code is just "arrays shuffling" as you say then VB(A)-> C++ conversion is almost line by line...
I did recheck - your code runs just in 5 seconds on VB6 with array bounds checks and integer overflow checks switched off. These are 100,000,000 loops.
I did also test your sample code on C# and VB.NET - and it runs in 5-6 seconds with switched off integer overflow checks (it looks that there is no way to switch off array bounds checks for C# and VB.NET) .
Here is VB.NETcode I used for testing:
VB.NET
Module testArrays
Sub
Dim lngResult As Long = 0
Dim lngCnt As Long = 0
Dim lngTotalCnt As Long = 0
lngResult = ArrayTimeLocal(1000000,
lngTotalCnt)
lngTotalCnt += lngCnt
Console.WriteLine(String.Format( _
"Final
Result = {0:#,##0}, Final Counter = {1:#,##0}", _
lngResult, lngTotalCnt))
End Sub
Function ArrayTimeLocal(ByVal lngLoopMax As Long, ByRef lngTotalCnt As
Long) As Long
Const lngItems As Long = 100
Dim alngTmp(lngItems - 1, 1) As Long
Dim lngLoop As Long
Dim lngItem As Long
Dim lngResult As Long
Dim lngSeconds As Long
Dim dblStart As Double
Dim dblStop As Double
Dim lngCnt As Long = 0
dblStart
= Timer
For lngLoop = 0 To lngLoopMax - 1
For lngItem = 0 To lngItems - 1
alngTmp(lngItem, 1) = lngLoop * 10
If alngTmp(lngItem, 1) / 10 = 100 Then
lngResult = 1
Else
lngResult = 0
End
If
lngCnt += 1
Next
Next
dblStop
= Timer
lngSeconds = CLng(dblStop - dblStart)
lngTotalCnt = lngCnt
ArrayTimeLocal = lngSeconds
End Function
End Module
Output
Final Result = 5, Final Counter = 100,000,000
C#
using System;
using Microsoft.VisualBasic;
namespace testArraysCS
{
class TestArrays
{
[STAThread]
unsafe
static void
{
long lngTotalCnt = 0;
long lngResult = 0;
lngResult = ArrayTimeLocal(1000000,
ref lngTotalCnt);
Console.WriteLine(String.Format(
"Final
Result = {0:#,##0}, Final Counter = {1:#,##0}",
lngResult, lngTotalCnt));
}
unsafe
static long ArrayTimeLocal(long lngLoopMax,
ref long lngTotalCnt) {
const long lngItems = 100;
long[,] alngTmp = new
long[lngItems,1] ;
long lngLoop;
long lngItem;
long lngResult=0;
double dblStart;
double dblStop;
long lngSeconds;
Console.WriteLine("Array looping test started...");
dblStart = Microsoft.VisualBasic.DateAndTime.Timer;
lngTotalCnt = 0;
for (lngLoop=0; lngLoop < lngLoopMax; lngLoop++) {
for (lngItem=0; lngItem < lngItems; lngItem++) {
alngTmp[lngItem,0]
= lngLoop * 10;
if (alngTmp[lngItem,0] / 10 ==
100) lngResult = 1;
else lngResult = 0;
lngTotalCnt++;
}
}
dblStop = Microsoft.VisualBasic.DateAndTime.Timer;
Console.WriteLine("Array looping test ended.");
lngSeconds = (long)(dblStop - dblStart);
return lngSeconds;
}
}
}
Output
Array looping test started...
Array looping test ended.
Final Result = 6, Final Counter = 100,000,000
Recapitulation:
===========
C++ & Fortran (and COBOL?) rule and rock forever! :)
Shamil
Additional response
from Shamil Salakhetdinov:
Gustav,
And finally here is managed C++, which runs instantaneously:
#include "stdafx.h"
#using <mscorlib.dll>
#using <Microsoft.VisualBasic.dll>
using namespace System;
static long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt);
int _tmain() { long lngTotalCnt =
0;
long lngResult = 0;
lngResult = ArrayTimeLocal(1000000,
lngTotalCnt); Console::WriteLine(String::Format(
S"Final Result = {0:#,##0},
Final Counter = {1:#,##0}",
__box(lngResult), __box(lngTotalCnt))); }
static long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt)
{ const long lngItems
= 100; long
alngTmp[lngItems-1][2]; long lngLoop; long lngItem; long lngResult=0; double dblStart; double
dblStop; long lngSeconds;
Console::WriteLine("Array
looping test started..."); dblStart =
Microsoft::VisualBasic::DateAndTime::Timer;
lngTotalCnt
= 0;
for (lngLoop=0; lngLoop < lngLoopMax; lngLoop++) {
for (lngItem=0; lngItem < lngItems; lngItem++) {
alngTmp[lngItem][0] = lngLoop * 10;
if (alngTmp[lngItem][0]
/ 10 == 100) lngResult = 1;
else lngResult = 0;
lngTotalCnt++;
}
}
dblStop
= Microsoft::VisualBasic::DateAndTime::Timer;
Console::WriteLine("Array looping test
ended."); lngSeconds
= (long)(dblStop - dblStart);
return lngSeconds;
}
Output
Array looping test started...
Array looping test ended.
Final Result = 0, Final Counter = 100,000,000
And it takes ~18 seconds on my PC to run this test ADDITIONALLY looped 100 times....
Shamil
Comment from Gustav
Brock:
Hi Shamil
Oh my ... could you create a DLL with the essentials from your ArrayTimeLocal skipping the console parts? It just need to return the lngSeconds.
Looks like I need some C++ stuff for this project!
/gustav
Response from Shamil Salakhetdinov:
Gustav,
Yes, I can make a C++ dll but maybe you first (re)define your test function call interface because even if you call very quick C++ function 1000 times from VB6 you can loose all the advantages this C++ function gives.
Currently - here is C++ (non managed) console applications results for a function similar to yours:
10,000,000,000 your cycles in 13 seconds (it looks like I did some mistakes in my previous time calculations - I have to recheck)
=====================================
Final Result = 13, Final Counter = 10000000000
=====================================
Here is the C++ code
for the last test:
#include "stdafx.h"
#include <time.h>
static long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt,
bool useTracePrinting =
false);
int _tmain(int argc, _TCHAR* argv[]) {
_int64 lngTotalCnt = 0;
long lngCnt = 0;
time_t
/*double*/ dblStart;
time_t
/*double*/ dblStop;
long lngSeconds = 0;
time(&dblStart);
for (int i=1; i<=10;
i++) {
ArrayTimeLocal(10000000,
lngCnt);
lngTotalCnt += lngCnt;
}
time(&dblStop);
lngSeconds = (long)difftime(dblStop,dblStart);;
printf("Final Result =
%ld, Final Counter = %I64d\n",
lngSeconds, lngTotalCnt);
}
static long ArrayTimeLocal(long
lngLoopMax, long& lngTotalCnt,
bool
useTracePrinting) {
const long lngItems = 100;
long
alngTmp[lngItems][2];
long lngLoop;
long lngItem;
long lngResult = 0;
time_t
/*double*/ dblStart;
time_t
/*double*/ dblStop;
long lngSeconds;
long lngCnt = 0;
if
(useTracePrinting) printf("Array
looping test started..\n"); time(&dblStart); for (lngLoop=0;
lngLoop < lngLoopMax;
lngLoop++) {
for (lngItem=0; lngItem < lngItems; lngItem++) {
alngTmp[lngItem][0] = lngLoop * 10;
if (alngTmp[lngItem][0]
/ 10 == 100) lngResult = 1;
else lngResult = 0;
lngCnt++;
}
}
time(&dblStop);
if
(useTracePrinting) printf("Array
looping test ended.\n"); lngSeconds =
(long)difftime(dblStop,dblStart);;
if
(useTracePrinting) printf("Result
= %lf, Counter = %ld\n",
lngSeconds, lngTotalCnt);
lngTotalCnt
= lngCnt;
return lngSeconds;
}
I will try to move now all variable from stack to static memory - I expect it may bring even more speed execution gains. We will see.
As soon as you define your final call interface I will make a test C++ .dll...
Shamil
Response from Gustav
Brook:
Hi Shamil
For "redefining the interface" you mean that a Long is not enough to specify a count of loops large enough to obtain a running time of some seconds? That's the only parameter I wish to pass. The only needed value to return is the running duration in seconds.
If so, couldn't we expand with an additional loop to multiply the count of loops:
for (lngSequence=0;
lngSequence < lngSequences;
lngSequence++) {
for (lngLoop=0; lngLoop
<
lngLoopMax; lngLoop++)
{
for (lngItem=0; lngItem < lngItems; lngItem++) {
alngTmp[lngItem][0] = lngLoop * 10;
if (alngTmp[lngItem][0]
/ 10 == 100) lngResult = 1;
else lngResult = 0;
lngCnt++;
}
}
}
and then add lngSequences as a separate parameter:
static long ArrayTimeLocal(long
lngSequences, long lngLoopMax,
long& lngTotalCnt, bool
useTracePrinting = false);
That would be great.
/gustav
Further comment from Shamil Salakhetdinov:
With variables moved to (static) memory out of stack the code works for ~6 seconds slower (yes, that was stupid of me to expect it running faster - when getting/putting variable from/to stack Intel processors should work faster). Well, one can see 6 seconds difference on 10,000,000,000 cycle runs only - for usual programming this difference doesn't matter. Here is C++ sample code, which runs 19 seconds on my PC:
#include "stdafx.h"
#include <time.h>
typedef long COUNTER_TYPE;
const long lngItems = 100;
long alngTmp[lngItems][2];
long lngLoop;
long lngItem;
long lngResult = 0;
time_t /*double*/ dblStart;
time_t /*double*/ dblStop;
long lngSeconds;
COUNTER_TYPE lngCnt;
static long ArrayTimeLocal(long
lngLoopMax, COUNTER_TYPE& lngTotalCnt,
bool useTracePrinting =
false); int _tmain(int argc,
_TCHAR* argv[]) {
_int64 lngTotalCnt = 0;
COUNTER_TYPE lngCnt = 0;
time_t
/*double*/ dblStart;
time_t
/*double*/ dblStop;
long lngSeconds = 0;
time(&dblStart);
for (int i=1; i<=100;
i++) {
ArrayTimeLocal(1000000,
lngCnt);
lngTotalCnt += lngCnt;
}
time(&dblStop);
lngSeconds = (long)difftime(dblStop,dblStart);;
printf("Final Result =
%ld, Final Counter = %I64d\n",
lngSeconds, lngTotalCnt);
}
static long ArrayTimeLocal(long
lngLoopMax, COUNTER_TYPE& lngTotalCnt,
bool
useTracePrinting) {
lngCnt=0;
if
(useTracePrinting) printf("Array
looping test started..\n"); time(&dblStart); for (lngLoop=0;
lngLoop < lngLoopMax;
lngLoop++) {
for (lngItem=0; lngItem < lngItems; lngItem++) {
alngTmp[lngItem][0] = lngLoop * 10;
if (alngTmp[lngItem][0]
/ 10 == 100) lngResult = 1;
else lngResult = 0;
lngCnt++;
}
}
time(&dblStop);
if
(useTracePrinting) printf("Array
looping test ended.\n"); lngSeconds =
(long)difftime(dblStop,dblStart);;
if
(useTracePrinting) printf("Result
= %lf, Counter = %ld\n",
lngSeconds, lngTotalCnt);
lngTotalCnt
= lngCnt;
return lngSeconds;
}
Shamil Salakhetdinov
responds to Gustav: