Difference between revisions of "Code Racer"

From Second Life Wiki
Jump to navigation Jump to search
(link with Code Sizer - count bytes of code, and link with Efficiency Tester - count milliseconds of run time)
m (<lsl> tag to <source>)
 
(9 intermediate revisions by 3 users not shown)
Line 5: Line 5:
'''Q: Want to see if one version of code usually runs faster than another?'''
'''Q: Want to see if one version of code usually runs faster than another?'''


'''A:''' Run your code inside a test harness such as the example code here to race both versions time and again. Along the way, declare the winners, measuring each win by the change in [[llGetTimestamp]].
'''A:''' Run your code inside a test harness such as the example code here to race two or more versions time and again. Along the way, declare the winners, measuring each win by the change in the [[llGetTime]] dilated script time.


= Sample Results =
= Sample Results =
Line 11: Line 11:
<pre>
<pre>


14255 == llGetFreeMemory at default state entry
Click Running = No to stop this script after you've seen enough ...
Click No = Running to stop this script after you've seen enough ...
2007-10-25T02:33:12.806088Z
2007-10-17T04:38:07.893983Z


37.103798 ms on average won 8 times for version 1
~4.237066 ms elapsed in version 1 to place at 1 by 8 of 10 votes
39.045620 ms on average won 3 times for version 0
~10.047700 ms elapsed in version 2 to place at 2 by 8 of 10 votes
2007-10-17T04:38:09.348014Z
2007-10-25T02:33:15.009855Z


38.025761 ms on average won 6 times for version 0
~3.089838 ms elapsed in version 1 to place at 1 by 44 of 100 votes
48.356247 ms on average won 5 times for version 1
~5.549734 ms elapsed in version 2 to place at 2 by 57 of 100 votes
2007-10-17T04:38:10.776157Z
2007-10-25T02:33:35.422763Z


38.604069 ms on average won 7 times for version 1
~1.333064 ms elapsed in version 1 to place at 1 by 55 of 100 votes
42.263889 ms on average won 4 times for version 0
~3.434490 ms elapsed in version 2 to place at 2 by 64 of 100 votes
2007-10-17T04:38:12.292078Z
2007-10-25T02:33:56.083810Z


39.734879 ms on average won 51 times for version 0
~0.760552 ms elapsed in version 1 to place at 1 by 49 of 100 votes
40.321190 ms on average won 50 times for version 1
~1.922230 ms elapsed in version 2 to place at 2 by 63 of 100 votes
2007-10-17T04:38:25.212252Z
2007-10-25T02:34:16.564139Z


38.497734 ms on average won 52 times for version 1
~1.981575 ms elapsed in version 1 to place at 1 by 481 of 1000 votes
38.419685 ms on average won 49 times for version 0
~5.013962 ms elapsed in version 2 to place at 2 by 597 of 1000 votes
2007-10-17T04:38:37.462428Z
2007-10-25T02:37:40.371221Z


40.511398 ms on average won 60 times for version 0
~1.475276 ms elapsed in version 1 to place at 1 by 498 of 1000 votes
41.545677 ms on average won 41 times for version 1
~3.177954 ms elapsed in version 2 to place at 2 by 596 of 1000 votes
2007-10-17T04:38:50.413441Z
2007-10-25T02:41:07.869815Z


41.040203 ms on average won 523 times for version 1
~0.773118 ms elapsed in version 1 to place at 1 by 467 of 1000 votes
40.605671 ms on average won 478 times for version 0
~1.858705 ms elapsed in version 2 to place at 2 by 597 of 1000 votes
2007-10-17T04:40:58.638995Z
2007-10-25T02:44:25.003959Z
 
40.874664 ms on average won 501 times for version 0
40.492779 ms on average won 500 times for version 1
2007-10-17T04:43:08.773074Z
 
40.991032 ms on average won 507 times for version 1
41.352806 ms on average won 494 times for version 0
2007-10-17T04:45:18.649320Z
</pre>
</pre>


= Code =
= Code =


<pre>
<source lang="lsl2">
// Race a few version of code in dilated script time as measured by llGetTime.
// http://wiki.secondlife.com/wiki/Code_Racer
// http://wiki.secondlife.com/wiki/Code_Racer


version0()
// List every runnable version.
 
list theRunners = [0, 1, 2];
 
// Count runnable versions.
 
integer theRunnable;
 
// Count votes to place.
 
list theVotes;
 
// Sum the run times observed.
 
list theSums;
 
// Count the races that ran without time running backwards.
 
integer theAverageable;
 
// Race no code alongside the other code in order to measure overhead.
 
runner0()
{
{
}
}


version1()
// Race a few versions of code.
 
runner1()
{
{
    integer spinning;
    for (spinning = 0; spinning < 25; ++spinning)
        ;
}
}


// Count the wins.
runner2()
{
    integer spinning;
    for (spinning = 0; spinning < 50; ++spinning)
        ;
}


integer wins0;
runner3()
integer wins1;
{
}


// Sum the times.
runner4()
{
}


float tsum0;
runner5()
float tsum1;
{
}
 
// Run the chosen runner once.
// Run thru an equal time while choosing any runner.
 
run(integer runner)
{
    if (runner == 0) { runner0(); }
    if (runner == 1) { runner1(); }
    if (runner == 2) { runner2(); }
    if (runner == 3) { runner3(); }
    if (runner == 4) { runner4(); }
    if (runner == 5) { runner5(); }
}


// Start up.
// Start up.
Line 79: Line 120:
startup()
startup()
{
{
        llOwnerSay("");
    llOwnerSay("");
    llOwnerSay("Click Running = No to stop this script after you've seen enough ...");
    llOwnerSay(llGetTimestamp());
}


        llOwnerSay((string) llGetFreeMemory() +
// Measure the race in calendar time elapsed since the minute began,
            " == llGetFreeMemory at default state entry");
// if called in place of llGetTime.
//
// Note: "YYYY-MM-DDThh:mm:ss.ff..fZ" = llGetTimestamp();
// Note: 17 = 0 + llStringLength("YYYY-MM-DDThh:mm:")
// Note: -2 = -1 - llStringLength("Z")


        llOwnerSay("Click No = Running to stop this script after you've seen enough ...");
float getTime()
        llOwnerSay(llGetTimestamp());
{
    return (float) llGetSubString(llGetTimestamp(), 17, -2); // "ss.ff..f"
}
}


// Count time elapsed since the minute began.
// Race the runners and return the times when each ran.
// Run in llGetTime dilated script time or in getTime calendar time.


float elapsed()
list eachRunnerRun()
{
{
     string chars = llGetTimestamp(); // "YYYY-MM-DDThh:mm:ss.ff..fZ"
     list befores = [];
     return (float) llGetSubString(chars, 17, -2); // "ss.ff..f"
    integer runnablePlus = theRunnable + 1;
// return llGetSubString(chars,
    integer running;
//      llStringLength("YYYY-MM-DDThh:mm:"),
     for (running = 0; running < runnablePlus; ++running)
//      -1 - llStringLength("Z"));
    {
        integer running = llList2Integer(theRunners, running);
        befores += llGetTime(); // choose script llGetTime or calendar getTime here
        run(running);
    }
    return befores;
}
}


// Say less time wins, else odd/even wins.
// Return elapsed run time per runner,
// else return [] if time ran backwards.


integer chooseWinner(float tv0, float tv1, integer runneds)
list getRuntimesElseNone(list befores)
{
{
     if (tv0 < tv1)
     list runtimes = [];
    float before = llList2Float(befores, 0);
    integer timing;
    for (timing = 0; timing < theRunnable; ++timing)
     {
     {
         return 0;
         float after = llList2Float(befores, timing + 1);
        float runtime = after - before;
        if (runtime < 0.0) return [];
        runtimes += runtime;
        before = after;
     }
     }
     else if (tv1 < tv0)
     return runtimes;
}
 
// Add to a listed float.
 
list addFloat(list sums, integer index, float addition)
{
    list results = [];
    integer summable = llGetListLength(sums);
    integer summing;
    for (summing = 0; summing < summable; ++summing)
     {
     {
         return 1;
         float result = llList2Float(sums, summing);
        if (index == summing)
        {
            result += addition;
        }
        results += result;
     }
     }
     else // if (tv0 == tv1) // rare
     return results;
}
 
// Add to a listed integer.
 
list addInteger(list sums, integer index, integer addition)
{
    list results = [];
    integer summable = llGetListLength(sums);
    integer summing;
    for (summing = 0; summing < summable; ++summing)
     {
     {
         return (runneds & 1);
         integer result = llList2Integer(sums, summing);
        if (index == summing)
        {
            result += addition;
        }
        results += result;
     }
     }
    return results;
}
}


// Count the wins and sum the times.
// Race the runners once, vote to place, and sum run time per runner.


declareWinner(float tv0, float tv1, integer runneds)
runRace()
{
{


     // See negative delta time as rollover of 60 seconds per minute.
     // Race the runners once.
   
 
     if (tv0 < 0) { tv0 += 60;}
     list runtimes = getRuntimesElseNone(eachRunnerRun());
    if (tv1 < 0) { tv1 += 60;}
//  llOwnerSay("[" + llDumpList2String(runtimes, ", ") + "] == runtimes");
 
    // Sort the runtimes into places.


     // Count the wins.
     list places = llListSort(runtimes, 1, TRUE); // sort least to most
    integer placeable = llGetListLength(places); // 0 or theRunnable


     integer winner = chooseWinner(tv0, tv1, runneds);
     // Sum run time per runner.
    wins0 += (1 - winner);
    wins1 += winner;


     // Sum the times.
     integer adding;
    for (adding = 0; adding < placeable; ++adding)
    {
        integer running = llList2Integer(theRunners, adding);
        float runtime = llList2Float(runtimes, adding);
        theSums = addFloat(theSums, running, runtime);
        ++theAverageable;


    tsum0 += tv0;
        // Vote to place at each equal runtime.
    tsum1 += tv1;
 
        integer placing;
        for (placing = 0; placing < placeable; ++placing)
        {
            float placed = llList2Float(places, placing);
            if (runtime == placed)
            {
                integer flat = (running * placeable) + placing;
                theVotes = addInteger(theVotes, flat, 1);
            }
        }
    }
}
}


// Run one race between two versions of code.
// Start up a burst of races.


runRace(integer runneds)
zeroBurst()
{
{
     if (runneds & 1)
     theSums = theVotes = [];
    theRunnable = llGetListLength(theRunners);
    integer placing;
    for (placing = 0; placing < theRunnable; ++placing)
     {
     {
         float t0 = elapsed();
         theSums += 0.0;
         version1();
         integer running;
         float t1 = elapsed();
         for (running = 0; running < theRunnable; ++running)
        version0();
         {
        float t2 = elapsed();
            theVotes += 0;
       
         }
         declareWinner(t1 - t0, t2 - t1, runneds);
    }
    else
    {
        float t0 = elapsed();
        version0();
        float t1 = elapsed();
         version1();
        float t2 = elapsed();
       
        declareWinner(t2 - t1, t1 - t0, runneds);
     }
     }
}
}


// Report the result for one version in a burst of races.
// Report a burst of races.


reportSpeed(list averages, list wins, integer winningVersion)
reportBurst(integer scaling)
{
{
    llOwnerSay(llList2String(averages, winningVersion) +
        " ms on average won " +
        (string) llList2String(wins, winningVersion) + " times" +
        " for version " + (string) winningVersion);
}


// Report the result of a burst of races.
    // Consider each place.
 
    integer placing;
    for (placing = 0; placing < theRunnable; ++placing)
    {
        list votes = llList2ListStrided(llList2List(theVotes, placing, -1), 0, -1, theRunnable);
        integer winner = llList2Integer(llListSort(votes, 1, TRUE), -1);
 
        // Find the winner (or the winners) of that place.
 
        integer running;
        for (running = 1; running < theRunnable; ++running)
        {
            integer vote4place = llList2Integer(votes, running);
            if (vote4place == winner)
            {
 
                // Describe a winner.


reportRace(integer scale)
                float summed = llList2Float(theSums, running) - llList2Float(theSums, 0);
{
                float average = 1000.0 * (summed / theAverageable);
    list wins = [wins0, wins1];
   
    list averages = [
        (1000.0 * tsum0) / scale,
        (1000.0 * tsum1) / scale];


    llOwnerSay("");
                llOwnerSay("~" +
    integer winningVersion = (wins0 <= wins1); // bias to 1
                    (string) average + " ms elapsed in version " + (string) running +
    reportSpeed(averages, wins, winningVersion);
                    " to place at " + (string) placing +
    reportSpeed(averages, wins, 1 - winningVersion);
                    " by " + (string) vote4place + " of " + (string) scaling + " votes"
    llOwnerSay(llGetTimestamp());
                );
            }
        }
    }
}
}


// Run several bursts of races.
// Run a few bursts of races at each scale, for indefinitely increasing scale.


raceRepeatedly(integer scale)
runRaceRepeatedly()
{
{
       
     integer scaleable = 10;
     // Repeat any measurement at least three times.
     integer repeatable = 2; // decide how often to repeat the first burst
   
     while (TRUE)
     integer repeateds;
     for (repeateds = 0; repeateds < 3; ++repeateds)
     {
     {
          
         integer repeating;
         // Run a burst of races.
         for (repeating = 0; repeating < repeatable; ++repeating)
        {
            zeroBurst();
 
            integer scaling;
            for (scaling = 0; scaling < scaleable; ++scaling)
            {
                runRace();
                theRunners = llList2List(theRunners, -1, -1) + llList2List(theRunners, 0, -2);
            }


        wins0 = wins1 = 0;
            llOwnerSay("");
        tsum0 = tsum1 = 0.0;
            reportBurst(scaling);
       
            llOwnerSay((string) llGetRegionTimeDilation() + " dilation @ " + llGetTimestamp());
        integer runneds;
        for (runneds = 0; runneds < scale; ++runneds)
        {
            runRace(runneds);
         }
         }
          
          
         // Resolve near ties in favour of lesser average.
         scaleable *= 10;
       
         repeatable = 3; // decide how often to repeat the other bursts
        wins0 += (tsum0 < tsum1);
         wins1 += (tsum1 < tsum0);
       
        // Report frequently to pacify the human operator.
       
        reportRace(scale);
     }
     }
}
}
Line 233: Line 344:
     {
     {
         startup();
         startup();
         integer scale = 10;
         runRaceRepeatedly();
        while (TRUE)
        {
            raceRepeatedly(scale);
            scale *= 10;
        }
     }
     }
}
}
</pre>
</source>


= Instructions =
= Instructions =


This instrument quickly & accurately measures the run time difference between two fragments of code. For example, the code presented here measures the zero difference between the two empty fragments of code found inside the functions named version0 and version1. To measure other code, insert the code you want to compare into the version0 and version1 functions.
This instrument quickly & accurately measures the run time difference between a number of fragments of code. For example, the code presented here measures the difference between the two fragments of code found inside the functions named runner1 and runner2. To measure other code, insert the code you want to compare into the runner1, runner2, runner3, etc. functions. See the source line that assigns [0, 1, 2] to theRunners? List all the runners you want to run there. Leave runner0 empty so that the harness correctly measures and subtracts out its own overhead.


= Alternatives & Caveats =
= Alternatives & Caveats =


This instrument compares two run times quickly, like 100X faster than the Efficiency Tester instrument. This instrument runs two fragments of code at a time, gives you immediate results and then progressively more accurate results over time, like when you slowly fetch a detailed image from the web. This instrument burns thru hugely much run time, like as much as fifty milliseconds per version raced. Providing immediate feedback and finishing 100X faster makes work proceed when the work would otherwise be too hard & boring to attract enough volunteers.  
This instrument compares run times quickly, like 100X faster than the Efficiency Tester instrument. This instrument runs a number of fragments of code at a time, gives you immediate results and then progressively more accurate results over time, like when you slowly fetch a detailed image from the web. This instrument burns thru hugely much run time, so that it can provide immediate feedback to make work proceed when the work would otherwise be too hard & boring to attract enough volunteers.  


The [[Efficiency Tester]] instrument serves a different purpose. That instrument adds accuracy to a measure of a range of observed run times in as little time as possible. By simple arithmetic, running thru 200ms for 1,000 times necessarily takes at least 200s, aka, more than 3 minutes. That instrument runs one fragment of code at a time, but then runs that fragment many many times to try and average out any distractions that may hit the server during the run. That instrument actually can measure 200ms in as little as 10 minutes, but that instrument gives you no answer at all until after 10,000 runs and no final answer until after 30,000 runs.
The [[Efficiency Tester]] instrument serves a different purpose. That instrument adds accuracy to a measure of a range of observed run times in as little time as possible. By simple arithmetic, running thru 200ms for 1,000 times necessarily takes at least 200s, aka, more than 3 minutes. That instrument runs one fragment of code at a time, but then runs that fragment many many times to try and average out any distractions that may hit the server during the run. That instrument actually can measure 200ms in as little as 10 minutes, but that instrument gives you no answer at all until after 10,000 runs and no final answer until after 30,000 runs.
Line 258: Line 364:


= See Also =
= See Also =
'''Functions'''
[[llGetTime]] - fetch the (often zeroed) dilated time in seconds and fractional seconds
[[llGetTimestamp]] - fetch the ISO 8601 "YYYY-MM-DDThh:mm:ss.ff..fZ" string that names the calendar date and time that is now


'''Scripts'''
'''Scripts'''
Line 265: Line 377:
[[Efficiency Tester]] - run as long as you please to count approximate milliseconds of run time with every more accuracy
[[Efficiency Tester]] - run as long as you please to count approximate milliseconds of run time with every more accuracy


{{Resource Conservation Portal Nav|cat=process}}
{{LSLC|Library}}{{LSLC|Examples}}
{{LSLC|Library}}{{LSLC|Examples}}

Latest revision as of 14:17, 24 January 2015

Introduction

Q: Want to see if one version of code usually runs faster than another?

A: Run your code inside a test harness such as the example code here to race two or more versions time and again. Along the way, declare the winners, measuring each win by the change in the llGetTime dilated script time.

Sample Results


Click Running = No to stop this script after you've seen enough ...
2007-10-25T02:33:12.806088Z

~4.237066 ms elapsed in version 1 to place at 1 by 8 of 10 votes
~10.047700 ms elapsed in version 2 to place at 2 by 8 of 10 votes
2007-10-25T02:33:15.009855Z

~3.089838 ms elapsed in version 1 to place at 1 by 44 of 100 votes
~5.549734 ms elapsed in version 2 to place at 2 by 57 of 100 votes
2007-10-25T02:33:35.422763Z

~1.333064 ms elapsed in version 1 to place at 1 by 55 of 100 votes
~3.434490 ms elapsed in version 2 to place at 2 by 64 of 100 votes
2007-10-25T02:33:56.083810Z

~0.760552 ms elapsed in version 1 to place at 1 by 49 of 100 votes
~1.922230 ms elapsed in version 2 to place at 2 by 63 of 100 votes
2007-10-25T02:34:16.564139Z

~1.981575 ms elapsed in version 1 to place at 1 by 481 of 1000 votes
~5.013962 ms elapsed in version 2 to place at 2 by 597 of 1000 votes
2007-10-25T02:37:40.371221Z

~1.475276 ms elapsed in version 1 to place at 1 by 498 of 1000 votes
~3.177954 ms elapsed in version 2 to place at 2 by 596 of 1000 votes
2007-10-25T02:41:07.869815Z

~0.773118 ms elapsed in version 1 to place at 1 by 467 of 1000 votes
~1.858705 ms elapsed in version 2 to place at 2 by 597 of 1000 votes
2007-10-25T02:44:25.003959Z

Code

// Race a few version of code in dilated script time as measured by llGetTime.
// http://wiki.secondlife.com/wiki/Code_Racer

// List every runnable version.

list theRunners = [0, 1, 2];

// Count runnable versions.

integer theRunnable;

// Count votes to place.

list theVotes;

// Sum the run times observed.

list theSums;

// Count the races that ran without time running backwards.

integer theAverageable;

// Race no code alongside the other code in order to measure overhead.

runner0()
{
}

// Race a few versions of code.

runner1()
{
    integer spinning;
    for (spinning = 0; spinning < 25; ++spinning)
        ;
}

runner2()
{
    integer spinning;
    for (spinning = 0; spinning < 50; ++spinning)
        ;
}

runner3()
{
}

runner4()
{
}

runner5()
{
}

// Run the chosen runner once.
// Run thru an equal time while choosing any runner.

run(integer runner)
{
    if (runner == 0) { runner0(); }
    if (runner == 1) { runner1(); }
    if (runner == 2) { runner2(); }
    if (runner == 3) { runner3(); }
    if (runner == 4) { runner4(); }
    if (runner == 5) { runner5(); }
}

// Start up.

startup()
{
    llOwnerSay("");
    llOwnerSay("Click Running = No to stop this script after you've seen enough ...");
    llOwnerSay(llGetTimestamp());
}

// Measure the race in calendar time elapsed since the minute began,
// if called in place of llGetTime.
//
// Note: "YYYY-MM-DDThh:mm:ss.ff..fZ" = llGetTimestamp();
// Note: 17 = 0 + llStringLength("YYYY-MM-DDThh:mm:")
// Note: -2 = -1 - llStringLength("Z")

float getTime()
{
    return (float) llGetSubString(llGetTimestamp(), 17, -2); // "ss.ff..f"
}

// Race the runners and return the times when each ran.
// Run in llGetTime dilated script time or in getTime calendar time.

list eachRunnerRun()
{
    list befores = [];
    integer runnablePlus = theRunnable + 1;
    integer running;
    for (running = 0; running < runnablePlus; ++running)
    {
        integer running = llList2Integer(theRunners, running);
        befores += llGetTime(); // choose script llGetTime or calendar getTime here
        run(running);
    }
    return befores;
}

// Return elapsed run time per runner,
// else return [] if time ran backwards.

list getRuntimesElseNone(list befores)
{
    list runtimes = [];
    float before = llList2Float(befores, 0);
    integer timing;
    for (timing = 0; timing < theRunnable; ++timing)
    {
        float after = llList2Float(befores, timing + 1);
        float runtime = after - before;
        if (runtime < 0.0) return [];
        runtimes += runtime;
        before = after;
    }
    return runtimes;
}

// Add to a listed float.

list addFloat(list sums, integer index, float addition)
{
    list results = [];
    integer summable = llGetListLength(sums);
    integer summing;
    for (summing = 0; summing < summable; ++summing)
    {
        float result = llList2Float(sums, summing);
        if (index == summing)
        {
            result += addition;
        }
        results += result;
    }
    return results;
}

// Add to a listed integer.

list addInteger(list sums, integer index, integer addition)
{
    list results = [];
    integer summable = llGetListLength(sums);
    integer summing;
    for (summing = 0; summing < summable; ++summing)
    {
        integer result = llList2Integer(sums, summing);
        if (index == summing)
        {
            result += addition;
        }
        results += result;
    }
    return results;
}

// Race the runners once, vote to place, and sum run time per runner.

runRace()
{

    // Race the runners once.

    list runtimes = getRuntimesElseNone(eachRunnerRun());
//  llOwnerSay("[" + llDumpList2String(runtimes, ", ") + "] == runtimes");

    // Sort the runtimes into places.

    list places = llListSort(runtimes, 1, TRUE); // sort least to most
    integer placeable = llGetListLength(places); // 0 or theRunnable

    // Sum run time per runner.

    integer adding;
    for (adding = 0; adding < placeable; ++adding)
    {
        integer running = llList2Integer(theRunners, adding);
        float runtime = llList2Float(runtimes, adding);
        theSums = addFloat(theSums, running, runtime);
        ++theAverageable;

        // Vote to place at each equal runtime.

        integer placing;
        for (placing = 0; placing < placeable; ++placing)
        {
            float placed = llList2Float(places, placing);
            if (runtime == placed)
            {
                integer flat = (running * placeable) + placing;
                theVotes = addInteger(theVotes, flat, 1);
            }
        }
    }
}

// Start up a burst of races.

zeroBurst()
{
    theSums = theVotes = [];
    theRunnable = llGetListLength(theRunners);
    integer placing;
    for (placing = 0; placing < theRunnable; ++placing)
    {
        theSums += 0.0;
        integer running;
        for (running = 0; running < theRunnable; ++running)
        {
            theVotes += 0;
        }
    }
}

// Report a burst of races.

reportBurst(integer scaling)
{

    // Consider each place.

    integer placing;
    for (placing = 0; placing < theRunnable; ++placing)
    {
        list votes = llList2ListStrided(llList2List(theVotes, placing, -1), 0, -1, theRunnable);
        integer winner = llList2Integer(llListSort(votes, 1, TRUE), -1);

        // Find the winner (or the winners) of that place.

        integer running;
        for (running = 1; running < theRunnable; ++running)
        {
            integer vote4place = llList2Integer(votes, running); 
            if (vote4place == winner)
            {

                // Describe a winner.

                float summed = llList2Float(theSums, running) - llList2Float(theSums, 0);
                float average = 1000.0 * (summed / theAverageable);

                llOwnerSay("~" +
                    (string) average + " ms elapsed in version " + (string) running +
                    " to place at " + (string) placing +
                    " by " + (string) vote4place + " of " + (string) scaling + " votes"
                );
            }
        }
    }
}

// Run a few bursts of races at each scale, for indefinitely increasing scale.

runRaceRepeatedly()
{
    integer scaleable = 10;
    integer repeatable = 2; // decide how often to repeat the first burst
    while (TRUE)
    {
        integer repeating;
        for (repeating = 0; repeating < repeatable; ++repeating)
        {
            zeroBurst();

            integer scaling;
            for (scaling = 0; scaling < scaleable; ++scaling)
            {
                runRace();
                theRunners = llList2List(theRunners, -1, -1) + llList2List(theRunners, 0, -2);
            }

            llOwnerSay("");
            reportBurst(scaling);
            llOwnerSay((string) llGetRegionTimeDilation() + " dilation @ " + llGetTimestamp());
        }
        
        scaleable *= 10;
        repeatable = 3; // decide how often to repeat the other bursts
    }
}

// Race to measure relative run-time cost of multiple versions of code.
// Produce unreasonable answers when the run-time cost measured equals or exceeds 60 seconds.

default
{
    state_entry()
    {
        startup();
        runRaceRepeatedly();
    }
}

Instructions

This instrument quickly & accurately measures the run time difference between a number of fragments of code. For example, the code presented here measures the difference between the two fragments of code found inside the functions named runner1 and runner2. To measure other code, insert the code you want to compare into the runner1, runner2, runner3, etc. functions. See the source line that assigns [0, 1, 2] to theRunners? List all the runners you want to run there. Leave runner0 empty so that the harness correctly measures and subtracts out its own overhead.

Alternatives & Caveats

This instrument compares run times quickly, like 100X faster than the Efficiency Tester instrument. This instrument runs a number of fragments of code at a time, gives you immediate results and then progressively more accurate results over time, like when you slowly fetch a detailed image from the web. This instrument burns thru hugely much run time, so that it can provide immediate feedback to make work proceed when the work would otherwise be too hard & boring to attract enough volunteers.

The Efficiency Tester instrument serves a different purpose. That instrument adds accuracy to a measure of a range of observed run times in as little time as possible. By simple arithmetic, running thru 200ms for 1,000 times necessarily takes at least 200s, aka, more than 3 minutes. That instrument runs one fragment of code at a time, but then runs that fragment many many times to try and average out any distractions that may hit the server during the run. That instrument actually can measure 200ms in as little as 10 minutes, but that instrument gives you no answer at all until after 10,000 runs and no final answer until after 30,000 runs.

See the LSL Script Efficiency article for much discussion of the Efficiency Tester instrument, including recommendations on how to avoid distracting the server into spending run time running other code in parallel. Those same recommendation apply to any llGetTimestamp harness, including this instrument.

Please do try to find deserted places to run such benchmarks and remember to turn them off when you finish! Else naturally you'll be rudely lagging the sim for the other people sharing the sim with you, for however long you run the benchmark.

See Also

Functions

llGetTime - fetch the (often zeroed) dilated time in seconds and fractional seconds

llGetTimestamp - fetch the ISO 8601 "YYYY-MM-DDThh:mm:ss.ff..fZ" string that names the calendar date and time that is now

Scripts

Code Sizer - count bytes of code with perfect accuracy

Efficiency Tester - run as long as you please to count approximate milliseconds of run time with every more accuracy