On Thursday, 27 February 2020 at 19:46:23 UTC, Basile B. wrote:
Yes please, post the benchmark method. You see the benchmarks I run with your version are always slowest. I'm aware that rndGen (and generaly any uniform rnd func) is subject to a bias but I dont thing this bias maters much in the case we talk about.

The code below is the test jig that I'm using currently. It is adopted from yours but has added the -d=distribution command line option.

I have omitted the function bodies for the entries in the funcs[] table. You can cut and paste there at will but I wanted to spare any other readers the bulk.

The timings I get from the below are entirely consistent with my earlier reporting and, I believe, with yours.

I urge you to investigate the difference in timings between the original, and currently defaulted, distribution and the -d=edig distribution. If you don't understand what's going on there, or if you still don't believe input distributions matter, please get in touch. After all, it's possible that I'm the one with the misunderstanding.

Have fun!

<code>
const funcs = [
    &decimalLength9_0, &decimalLength9_1, &decimalLength9_2,
    &decimalLength9_3, &decimalLength9_4, &decimalLength9_5
];

ulong runOriginalFuncCount(ubyte funcIdx, ulong count)
{
    GC.disable;

    uint sum;
    foreach (const ulong i; 0 .. count)
    {
        sum += funcs[funcIdx](rndGen.front());
        rndGen.popFront();
    }
    return sum;
}

ulong runFuncCountDist(ubyte funcIdx, ulong count, string dist)
{
    enum perDigit = 1000;
// NOTE: this is a gross distortion given uint.max but it is "the spec"
    enum maxDigits = 9;
    uint[perDigit * maxDigits] samples = void;
    const chunks = count / samples.length;
    const leftOvers = count % samples.length;

    if (dist == "prng")
    {
        foreach (ref val; samples[])
        {
            val = rndGen.front();
            rndGen.popFront();
        }
    }
    else if (dist == "edig" || dist == "edigsorted")
    {
        // for reference, this is the "6 LOC IIRC"
        uint value = 1;
        foreach (dig; 0 .. maxDigits)
        {
samples[dig * perDigit .. (dig + 1) * perDigit] = value;
            value *= 10;
        }

        if (auto shuffle = dist != "edigsorted")
            randomShuffle(samples[]);
    }
    else
    {
assert(0, "dist option should be in {oprng, prng, edig, edigsorted}");
    }
    const func = funcs[funcIdx];

    if (count < 1) // late check gives us a handle on setup time
        return 0;
    // OK, enough with the setup, time to roll
    ulong sum = 0;
    foreach (chunk; 0 .. chunks)
    {
        foreach (sample; samples[])
            sum += func(sample);
    }
    foreach (sample; samples[$ - leftOvers .. $])
        sum += func(sample);
    return sum;
}

// This is a timing test jig for functions that accept a uint
// and return the number of decimal digits needed to represent
// that uint (except that 10 digit values are lumped in with
// 9 digit values currently).
//
// The command line options here are:
// -c=count (the number of values to present to the function selected)
//   -s=seed (the value supplied to rndGen.seed)
// -f=funcIndex (identifies the function under test, see source funcs[])
//   -d=distribution (one of: oprng, prng, edig, edigsorted)
//
// Distributions explained:
// oprng: original prng distribution, a rndGen.popFront per function call // prng: prng distribution, also rndGen but coming from a large, pre-filled, array
//   edig:  same number of 1-digit, 2-digit, ... numbers, shuffled
// edigsorted: edig values but sorted to highlight the branch predictor impact
//
// This test jig could evolve in at least six ways:
// 1) the full digit range could (arguably should) be accounted for // 2) additional distributions, like "favor small numbers", could be added
//  3) additional implementation ideas can be explored
//  4) the input range could be made variable
//  5) the number base could be templatized
// 6) inlined performance could be tested by aliasing Func (runtime switch to template)
//
// Final note: the main benefit of working on this problem is better understanding, not
//  shaving nanoseconds on this particular function.

void main(string[] args)
{

    ulong count;
    uint seed;
    ubyte func;
// NOTE: oprng writeln output will usually not equal that of -d=prng
    enum defaultDistribution = "oprng";
    string dist = defaultDistribution;

    GC.disable;
getopt(args, config.passThrough, "c|count", &count, "f|func", &func,
            "s|seed", &seed, "d|distribution", &dist);
    rndGen.seed(seed);
    const original = dist == "oprng";
const sum = original ? runOriginalFuncCount(func, count) : runFuncCountDist(func, count, dist);
    writeln(sum);
}

<code>




Reply via email to