burn-console Part III: The Most Efficient MSH Script

Wed, Dec 7, 2005 5-minute read

Now that we have our script profiler up and running, we instrument our script a little to mark regions we are concerned about.  You can download that starting script here: [burn-console-1.profiler.msh]   After running the script profiler, we get the performance breakdown:

Breakdown by line:
----------------------------
 15%: Line  123 -             if($colour -lt 20) { $colour -= 1 }
 14%: Line  122 -             if($colour -le 70) { $colour -= 3 }
 11%: Line  124 -             if($colour -lt 0) { $colour = 0 }
 10%: Line  128 -             $tempWorkingBuffer[$baseOffset -
  8%: Line  117 -             $colour /= 4.0
  7%: Line  121 -             if($colour -gt 70) { $colour -= 1 }
  6%: Line  154 -             $nextScreen[$row, $column] = `
  6%: Line  113 -             $colour = $screenBuffer[$baseOffset]
  6%: Line  115 -             $colour += $screenBuffer[$baseOffset + 1]
  6%: Line  116 -             $colour += $screenBuffer[$baseOffset +
  5%: Line  114 -             $colour += $screenBuffer[$baseOffset - 1]
  5%: Line  109 -             $baseOffset = ($windowWidth * $row) +
  0%: Line   90 -         if($random.NextDouble() -ge 0.20)
  0%: Line  152 -         for($column = 0; $column -lt $windowWidth;
  0%: Line   94 -             $screenBuffer[($windowHeight - 2) *

Breakdown by marked regions:
----------------------------
  6%: updateScreen
  0%: startFireLastRow
 93%: propigate
  0%: Unmarked
  0%: main

It looks like we’re spending 93% of our time propagating the fire.  When we investigate how we’re spending the time, none of the hot spots are individually egregious.  Since we’re in such a tight loop, we’re spending most of the time comparing colours.  We can be a little smarter with our ‘if’ statements, attempting to minimize the number of comparisons and variable assignments.  Before optimization, we have 4 checks per loop, and (a weighted average of) just over 1 assignment per loop.  If we write it as below, we reduce that to (a weighted average of) 1 check per loop, and (a weighted average even closer to) 1 assignment per loop.

if($colour -gt 70)
{ 
    $colour -= 1 
}
else
{
    $colour -= 3
    
    if($colour -lt 1)
    { 
        $colour = 0 
    }
    elseif($colour -lt 20)
    { 
        $colour -= 1 
    }
}

This brings us up to 0.57 frames per second.  A great improvement, but we’re obviously not done yet.  We run the profiler again, and see that the hotspots have moved:

Breakdown by line:
----------------------------
 13%: Line  141 -             $tempWorkingBuffer[$baseOffset -
  9%: Line  113 -             $colour = $screenBuffer[$baseOffset]
  9%: Line  117 -             $colour /= 4.0
  9%: Line  115 -             $colour += $screenBuffer[$baseOffset + 1]
  9%: Line  131 -                     $colour = 0
  8%: Line  129 -                 if($colour -lt 1)
  8%: Line  127 -                 $colour -= 3
  7%: Line  121 -             if($colour -gt 70)
  7%: Line  116 -             $colour += $screenBuffer[$baseOffset +
  7%: Line  114 -             $colour += $screenBuffer[$baseOffset - 1]
  5%: Line  109 -             $baseOffset = ($windowWidth * $row) +
  5%: Line  167 -             $nextScreen[$row, $column] = `
  3%: Line  244 -             $bufferCell = `
  1%: Line  240 -             $character =
  0%: Line  252 -             $paletteIndex++
  0%: Line  241 -             $fgColour =
  0%: Line  242 -             $bgColour =
  0%: Line  165 -         for($column = 0; $column -lt $windowWidth;

Breakdown by marked region:
----------------------------
  5%: updateScreen
  0%: startFireLastRow
 90%: propigate
  0%: Unmarked
  5%: main

There’s not too much we can do, but there are a few lines representing access and manipulation of the $colour variable as we compute the average.  So we’ll put those into one line:

$colour = ($screenBuffer[$baseOffset] +
    $screenBuffer[$baseOffset - 1] +
    $screenBuffer[$baseOffset + 1] +
    $screenBuffer[$baseOffset + $windowWidth]) / 4.0

The frame rate barely changes, so let’s see what the new hot spots are:

Breakdown by line:
----------------------------
 19%: Line  140 -             $tempWorkingBuffer[$baseOffset -
 13%: Line  113 -             $colour = ($screenBuffer[$baseOffset] +
 12%: Line  109 -             $baseOffset = ($windowWidth * $row) +
 11%: Line  126 -                 $colour -= 3
 11%: Line  128 -                 if($colour -lt 1)
 11%: Line  120 -             if($colour -gt 70)
 10%: Line  130 -                     $colour = 0
  9%: Line  166 -             $nextScreen[$row, $column] = `
  1%: Line  240 -             $fgColour =
  1%: Line  243 -             $bufferCell = `
  0%: Line  239 -             $character =
  0%: Line  122 -                 $colour -= 1
  0%: Line  251 -             $paletteIndex++
  0%: Line  106 -         for($column = 1; $column -lt ($windowWidth - 1);
  0%: Line  219 -                 if($apparentBrightness -gt

Breakdown by marked region:
----------------------------
  9%: updateScreen
  0%: startFireLastRow
 87%: propigate
  0%: Unmarked
  4%: main

There’s really not much left for us to optimize.  We seem to be spending a lot of time computing the $baseOffset variable, though.  Although we can’t improve the efficiency of the statement, we can execute it less frequently.   Rather than compute it from scratch each time via multiplication, we’ll simply increment it once per column.

We can continue with this iterative refinement, giving an end result of about 0.68 frames per second – 70% faster than when we started.  You can download the optimized version here: [burn-console-2.working.msh].  The problems have now shifted to things we have no control over:

Breakdown by line:
----------------------------
 20%: Line  171 -             $nextScreen[$row, $column] = `
 20%: Line  111 -             $colour = ($screenBuffer[$baseOffset] +
 18%: Line  141 -             $tempWorkingBuffer[$baseOffset -
 17%: Line  144 -             $baseOffset++
 17%: Line  118 -             if($colour -gt 0)
  1%: Line  120 -                 if($colour -gt 70)
  1%: Line  248 -             $bufferCell = `
  1%: Line  126 -                     $colour -= 3
  1%: Line  122 -                     $colour -= 1
  0%: Line  134 -                         $colour -= 1
  0%: Line  128 -                     if($colour -lt 1)
  0%: Line  246 -             $bgColour =
  0%: Line   94 -             $screenBuffer[($windowHeight - 2) *
  0%: Line  130 -                         $colour = 0
  0%: Line  162 -     $nextScreen =

Breakdown by marked region:
----------------------------
 20%: updateScreen
  1%: startFireLastRow
 77%: propigate
  0%: Unmarked
  3%: main

To solve this problem, we’ll delve into a trick from the Demo Scene.  Did you ever use inline assembler in C?  We’ll do something similar, but with inline C# in MSH.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]