1
\$\begingroup\$

I'm optimising our codebase and currently, the slowest part of the code is a method called calculateResourceZ which is used in projections. I'm looking for suggestions on areas of improvement. It needs the accuracy so it has to use doubles.

double calculateResourceZ(const double resource0, const double fwAtWicket, const double decimalOversRemaining, const double lambdaLocal, const double power) const {
    if (fwAtWicket == 0) {
        return 0;
    }

    const double resourceZPart1 = resource0 * fwAtWicket * pow(lambdaLocal, (power + 1.0));
    const double fg = calculateFunctionG(lambdaLocal, decimalOversRemaining);

    const double resourceZPart2 = (1.0 - exp(-((decimalOversRemaining * expDecay * fg * pow(lambdaLocal, -power)) / fwAtWicket)));
    return resourceZPart1 * resourceZPart2;
}

double calculateFunctionG(const double lambda, const double oversRemaining) const {
    if (version < Version::OCT2014) {
        return 1;
    }

    if (oversRemaining == 0 || lambda == 0) {
        return 0;
    }

    const double alphaLambda = calculateAlphaLambda(lambda);
    const double betaLambda = calculateBetaLambda(lambda);

    return pow((oversRemaining / 50.0), -(1.0 + alphaLambda + betaLambda * oversRemaining));
}

double calculateAlphaLambda(const double lambda) const {
    return -1.0 / (1.0 + c1 * (lambda - 1) * exp(-c2 * (lambda - 1.0)));
}

double calculateBetaLambda(const double lambda) const {
    return -c3 * (lambda - 1.0) * exp(-c4 * (lambda - 1.0));
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I'm not experienced enough in optimization at this level to truly answer your question. These functions look very reasonable. If you can determine exactly what it is about this function that slows down everything, that's your best shot at improving your performance (e.g. can you use vectorized instructions, does the processor think there are data dependencies where there are not, etc). Things you could try: operating on n elements at a time, where n = cache size / sizeof(double), removing branches, turning on --ffast-math (make sure you know what you're doing if you do so). \$\endgroup\$ – Justin Jul 4 '17 at 4:28
  • 2
    \$\begingroup\$ Do you know the value distribution for lambda parameter in calculateAlpha[Beta]Lamdba() function? You might consider to use table lookups instead of calling library functions exp(). The same approach could be used for pow() function that accepts power parameter in calculateResourceZ(). Of course you should evaluate how the memory consumption will grow... \$\endgroup\$ – Roman Ananyev Jul 4 '17 at 5:51
  • \$\begingroup\$ Can you try to explain what kind of projection you're doing here? \$\endgroup\$ – Daniel Jour Jul 4 '17 at 5:52
  • 1
    \$\begingroup\$ Not that it helps speed, but you might want to take a look at expm1. \$\endgroup\$ – Deduplicator Jul 4 '17 at 8:03
  • \$\begingroup\$ "turning on --ffast-math" If I could target certain methods then maybe I could use this. "explain what kind of projection" It's used for projecting cricket scores. \$\endgroup\$ – Daniel Ryan Jul 4 '17 at 21:35
3
\$\begingroup\$

This doesn't do much, so if it's the bottleneck it must be because you're calling it a lot. The first thing to do is to look at the calling pattern. If there are lots of calls with the same value of lambda then you may be able to calculate alpha and beta once rather than each time; you may even be able to cache fg and share it between lots of calls with the same lambda and oversRemaining.

On the level of microoptimisation, pow(lambdaLocal, power + 1.0) is mathematically equal to lambdaLocal * pow(lambdaLocal, power) and * pow(lambdaLocal, -power) is mathematically equal to / pow(lambdaLocal, power); I don't see a numerical reason that pulling that pow out would hurt, and it might shave a couple of cycles off. Then (... / lambdaPower) / fwAtWicket is (...) / (fwAtWicket * lambdaPower) which is another common subexpression.

There's potentially another algebraic tweak: decimalOversRemaining * fg could be refactored by including that extra product in the method:

double calculateFunctionGPrime(const double lambda, const double oversRemaining) const {
    if (version < Version::OCT2014) {
        return oversRemaining;
    }

    if (oversRemaining == 0 || lambda == 0) {
        return 0;
    }

    const double alphaLambda = calculateAlphaLambda(lambda);
    const double betaLambda = calculateBetaLambda(lambda);

    return 50 * pow((oversRemaining / 50.0), -(alphaLambda + betaLambda * oversRemaining));
}

which saves a multiplication in the exceptional cases and an addition in the primary case. Then since the calculations of both alpha and beta have a leading negation those can be removed along with the negation in that last pow:

double calculateFunctionGPrime(const double lambda, const double oversRemaining) const {
    if (version < Version::OCT2014) {
        return oversRemaining;
    }

    if (oversRemaining == 0 || lambda == 0) {
        return 0;
    }

    const double alphaLambda = calculateAlphaLambda(lambda);
    const double betaLambda = calculateBetaLambda(lambda);

    return 50 * pow((oversRemaining / 50.0), alphaLambda + betaLambda * oversRemaining);
}

double calculateAlphaLambda(const double lambda) const {
    return 1.0 / (1.0 + c1 * (lambda - 1) * exp(-c2 * (lambda - 1.0)));
}

double calculateBetaLambda(const double lambda) const {
    return c3 * (lambda - 1.0) * exp(-c4 * (lambda - 1.0));
}
\$\endgroup\$
  • \$\begingroup\$ fdiv is more expensive than fmul so keeping the - in the exponent (which compiles to a xor) may be faster. \$\endgroup\$ – ratchet freak Jul 4 '17 at 11:35
  • \$\begingroup\$ @ratchetfreak, I'm not 100% sure what you're referring to, but if it's the second paragraph then read the paragraph to the end. \$\endgroup\$ – Peter Taylor Jul 4 '17 at 11:40
  • \$\begingroup\$ "This doesn't do much, so if it's the bottleneck it must be because you're calling it a lot. " Yes, that is correct. I might create another question later to optimise those parts. "If there are lots of calls with the same value" There are a lot of values here. Not sure if I could cache without using a lot of memory. This gets used on mobile devices. \$\endgroup\$ – Daniel Ryan Jul 4 '17 at 21:33
  • \$\begingroup\$ Those 'calculateG' optimisations didn't work. Seems like there is a bug in there somewhere. \$\endgroup\$ – Daniel Ryan Oct 2 '17 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.