Code Racer

From Second Life Wiki
Revision as of 07:24, 20 October 2007 by Ppaatt Lynagh (talk | contribs) (→‎Sample Results: update results to match new code that harnesses llGetTime rather than llGetTimestamp)
Jump to navigation Jump to search

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 both versions time and again. Along the way, declare the winners, measuring each win by the change in llGetTimestamp.

Sample Results


14256 == llGetFreeMemory at default state entry
Click Running = No to stop this script after you've seen enough ...
2007-10-20T14:20:09.718320Z

0.000000 ms on average won 6 times for version 1
2.209222 ms on average won 5 times for version 0
2007-10-20T14:20:09.849289Z

2.773666 ms on average won 6 times for version 1
2.792112 ms on average won 5 times for version 0
2007-10-20T14:20:09.963519Z

0.000000 ms on average won 7 times for version 1
2.151334 ms on average won 4 times for version 0
2007-10-20T14:20:10.094450Z

0.000000 ms on average won 53 times for version 1
0.703163 ms on average won 48 times for version 0
2007-10-20T14:20:10.809173Z

0.000000 ms on average won 52 times for version 0
0.395192 ms on average won 49 times for version 1
2007-10-20T14:20:11.477273Z

0.748504 ms on average won 52 times for version 0
1.155981 ms on average won 49 times for version 1
2007-10-20T14:20:12.186930Z

0.737245 ms on average won 503 times for version 0
0.800317 ms on average won 498 times for version 1
2007-10-20T14:20:18.775741Z

0.714376 ms on average won 510 times for version 0
1.004643 ms on average won 491 times for version 1
2007-10-20T14:20:25.486886Z

0.713001 ms on average won 503 times for version 1
0.815969 ms on average won 498 times for version 0
2007-10-20T14:20:32.022393Z

0.782364 ms on average won 5017 times for version 1
0.812907 ms on average won 4984 times for version 0
2007-10-20T14:21:37.415875Z

0.776211 ms on average won 5002 times for version 0
0.774247 ms on average won 4999 times for version 1
2007-10-20T14:22:43.154070Z

0.748674 ms on average won 5021 times for version 0
0.755177 ms on average won 4980 times for version 1
2007-10-20T14:23:49.317798Z

Code

// http://wiki.secondlife.com/wiki/Code_Racer

// Race two versions of code in dilated time as measured by llGetTime.

version0()
{
}

version1()
{
}

// Count the wins.

integer wins0;
integer wins1;

// Sum the times.

float tsum0;
float tsum1;

// Start up.

startup()
{
        llOwnerSay("");

        llOwnerSay((string) llGetFreeMemory() +
            " == llGetFreeMemory at default state entry");

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

// Measure the race instead
// 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"
}

// Say less time wins, else odd/even wins.

integer chooseWinner(float tv0, float tv1, integer which)
{
    if (tv0 < tv1)
    {
        return 0;
    }
    else if (tv1 < tv0)
    {
        return 1;
    }
    else // if (tv0 == tv1) // rare
    {
        return (which & 1);
    }
}

// Count the wins and sum the times.

declareWinner(float tv0, float tv1, integer which)
{

    // Count the wins.

    integer winner = chooseWinner(tv0, tv1, which);
    wins0 += (1 - winner);
    wins1 += winner;

    // Sum the times.

    tsum0 += tv0;
    tsum1 += tv1;
}

// Run one race between two versions of code.

runRace(integer which)
{
    if (which & 1)
    {
        float t0 = llGetTime();
        version1();
        float t1 = llGetTime();
        version0();
        float t2 = llGetTime();
        
        if (!((t0 <= t1) && (t1 <= t2))) return;
         
        declareWinner(t1 - t0, t2 - t1, which);
    }
    else
    {
        float t0 = llGetTime();
        version0();
        float t1 = llGetTime();
        version1();
        float t2 = llGetTime();
        
        if (!((t0 <= t1) && (t1 <= t2))) return;
        
        declareWinner(t2 - t1, t1 - t0, which);
    }
}

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

reportSpeed(list averages, list wins, integer winningVersion)
{
    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.

reportRace(integer scale)
{
    list wins = [wins0, wins1];

    list averages = [
        (1000.0 * tsum0) / scale,
        (1000.0 * tsum1) / scale];

    llOwnerSay("");
    integer winningVersion = (wins0 <= wins1); // bias to 1
    reportSpeed(averages, wins, winningVersion);
    reportSpeed(averages, wins, 1 - winningVersion);
    llOwnerSay(llGetTimestamp());
}

// Run several bursts of races.

raceRepeatedly(integer scale)
{
        
    // Repeat any measurement at least three times.
     
    integer repeatable;
    for (repeatable = 0; repeatable < 3; ++repeatable)
    {
        
        // Run a burst of races.

        wins0 = wins1 = 0;
        tsum0 = tsum1 = 0.0;
        
        integer which;
        for (which = 0; which < scale; ++which)
        {
            runRace(which);
        }
        
        // Resolve near ties in favour of the lesser average time.
         
        wins0 += (tsum0 < tsum1);
        wins1 += (tsum1 < tsum0);
        
        // Report frequently to pacify the human operator.
        
        reportRace(wins0 + wins1 - 2);
    }
}

// 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();
        integer scale = 10;
        while (TRUE)
        {
            raceRepeatedly(scale);
            scale *= 10;
        }
    }
}

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.

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.

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

llGetTimestamp - fetch the ISO 8601 "YYYY-MM-DDThh:mm:ss.ff..fZ" string that names the 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