Coding Challenge I

Pages: 1 2 3 4 5 6 7 8 9

Solution from HP

I spent a couple of hours this evening putting together my entry, which I have attached. I did it in Delphi, mostly because I figured no one else would bother with a Delphi submission so I wanted my favorite compiler represented

program pi;

//Included binary is compiled on Delphi 6 update 2
//with the following compiler switches

{$O+} //turn on optimizations
{$R-} //turn off range checking
{$I-} //turn off I/O checking
{$Q-} //turn off Overflow checking
{$V-} //turn off shortstring type checking
{$P-} //turn off open params (not needed)
{$D-} //turn off debugging info
{$C-} //turn off assertions

{$APPTYPE CONSOLE}

{
  Changes made:
  1. All calls to Low(A) replaced with constant 0.
     Low(A) will always equal to zero with a dynamic
     array. Low() and High() function calls are not
     free in Delphi and incur overhead.
  2. Unrolled the first loop inside of the main
     outer loop. According to my profiling, the program
     spends the majority of its time inside of this loop.
     Tried different levels of unlooping and
     gauged performance, the best level seemed to be to
     unloop by a factor of 4, anything more than this
     had very little affect. This appears to be the only loop
     which would benefit from unrolling (of course
     I could be wrong about that) It may have been possible
     to get a minor speedup by unrolling the outerloop, but odds
     are it would have made it worse, and it would have resulted
     in some HORRIBLE looking code, so I declined to even try.
  3. Reorganized if-then-else statement on Q var to
     place the most common code path first by move the most
     common case (Q <> 9 or 10) and placed other two conditions
     within a case statement. This is pretty much a Delphi
     specific optimization as in Delphi Case Statements are
     quite a bit more efficient than if-than-else statements
     strung together. This had very littel if any measurable
     effect, but it didnt hurt so I left it in.
  4. Re-arranged the way the worker function is called. The
     original code passed the variable NumDigits, which represented
     how many digits of pi to compute. This variable was used to
     then determine the size of the array. This was not optimal
     for register use, because NumDigits was never actually used
     inside of any loops, but the arraysize is. So I reversed it. Now
     numDigits is a global var, and the arraysize is passed into the
     working function. This allows Delphi (theoretically) to place
     this var in a register. It also eliminates mulitple calls to
     High(A) which are now no longer needed (and incur overhead, see 1.)

  Note: All these optimizations result in a VERY small speedup. I only
        took a wack at it because I figured it unlikely anyone else
        would even submit a Delphi entry. I honestly believe there
        is little more that could be done without either resorting
        to assembly code or ending up with some really ugly and hard to
        read code. Delphi simply isnt built for speed, it is built for ease
        of use and doing all these tricks and such to speed it up for such
        a minor improvement are probably a waste of time in most cases. Also
        my system is an Athlon XP 1800+ and that is what I optimized on. These
        optimizations may not be effective on a P4 system, and may even slow it
        down. I dont really know as I dont have one…
}

var
  NumDigits: Integer;
  Code: Integer;
  F: TextFile;
  A: array of LongInt;

function ComputePi(ArrayHigh: Integer): string;
var
  I, J, K, P, Q, X, Nines, Predigit: Integer;
  PiLength: Integer;
begin
  SetLength(Result, NumDigits+1);
  PiLength := 1;
  for I := 0 to ArrayHigh do
    A[I] := 2;
  Nines := 0;
  Predigit := 0;
  for J := 0 to NumDigits-1 do
  begin
    Q := 0;
    P := 2 * ArrayHigh + 1;
    I := ArrayHigh;
    while ((I+1) mod 4) <> 0 do
    begin
      X := 10*A[I] + Q*(I+1);
      A[I] := X mod P;
      Q := X div P;
      dec(P,2);
      dec(I);
    end;
    while I > -1 do
    begin
      X := 10*A[I] + Q*(I+1);
      A[I] := X mod P;
      Q := X div P;
      dec(P,2);
      X := 10*A[I-1] + Q*(I);
      A[I-1] := X mod P;
      Q := X div P;
      dec(P,2);
      X := 10*A[I-2] + Q*(I-1);
      A[I-2] := X mod P;
      Q := X div P;
      dec(P,2);
      X := 10*A[I-3] + Q*(I-2);
      A[I-3] := X mod P;
      Q := X div P;
      dec(P,2);
      dec(i,4);
    end;
    A[0] := Q mod 10;
    Q := Q div 10;
    if ((Q < 9) or (Q > 10)) then
    begin
      Result[PiLength] := Chr(Predigit + Ord(‘0’));
      Predigit := Q;
      for K := 1 to Nines do
        Result[PiLength+K] := ‘9’;
      PiLength := PiLength + Nines + 1;
      Nines := 0;
    end
    else
    case Q of
      9: Inc(Nines);
      10:
      begin
        Result[PiLength] := Chr(Predigit + 1 + Ord(‘0’));
        for K := 1 to Nines do
          Result[PiLength+K] := ‘0’;
        PiLength := PiLength + Nines + 1;
        Predigit := 0;
        Nines := 0;
      end;
    end;
  end;
  Result[PiLength] := Chr(Predigit + Ord(‘0’));
end;

begin
  if ParamCount = 0 then
    WriteLn(‘usage: pi #DIGITS [FILE]’)
  else
  begin
    Val(ParamStr(1), NumDigits, Code);
    if Code <> 0 then
    begin
      WriteLn(‘Invalid # digits: ‘, ParamStr(1));
      Halt(1);
    end;
    SetLength(A, 10*NumDigits div 3);
    if ParamCount > 1 then
    begin
      AssignFile(F, ParamStr(2));
      Rewrite(F);
      WriteLn(F, ComputePi(High(A)));
      CloseFile(F);
    end
    else
      WriteLn(ComputePi(High(A)));
  end;
end.

Pages: « Prev  1 2 3 4 5 6 7 8 9  

Discuss (16 comments)