Реализация javascript Ньютона против деления пополам

Из любопытства я хочу убедиться, что Ньютон действительно быстрее, чем деление пополам (для случаев, когда он успешно сходится) для решения нелинейных уравнений.

Я реализовал оба из textbook algorithms. Проверяемая функция:

 f(x) = 5*(x-0.4)*(x^2 - 5x + 10), with a simple real root 0.4

Точность сходимости установлена ​​на 1e-4. Ньютон начинается с x0 = 0.5, converges in 2 iterations. деление пополам начинается с interval [0,1], сходится в 14 iterations.

Я использую performance.now() для измерения прошедшего времени обоих методов. УДИВИТЕЛЬНО, но со многими попытками Ньютон всегда медленнее, чем деление пополам.

Newton time: 0.265 msec: [0.39999999988110857,2]
bisection time: 0.145 msec: [0.399993896484375,14]

Я перенес программу на C (визуальный C): Newton намного быстрее, чем деление пополам.

Эти числовые коды настолько просты, что я не могу заметить ничего странного. Кто-нибудь может помочь?

http://jsfiddle.net/jmchen/8wvhzjmn/

// Horner method for degree-n polynomial
function eval (a, t) {

    // f(x) = a0+ a1x + ... + anxn
    var n = a.length - 1;// degree (n)
    var b = [];
    var c = [];
    var i, k;
    for (i = 0; i <= n; i++)
        b.push(0), c.push(0);

    b[n] = a[n];
    c[n] = b[n];
    for (k = n-1; k >= 1; k--) {
        b[k] = a[k] + t*b[k+1];
        c[k] = b[k] + t*c[k+1];
    }
    b[0] = a[0] + t*b[1];

    return [b[0],c[1]];
}

// simple Newton
function Newton (eval, x0, epsilon) {
    var eps = epsilon || 1e-4;
    var imax = 20;
    for (var i = 0; i < imax; i++) {
        var fdf = eval (coeff, x0);
        x1 = x0 - fdf[0]/fdf[1];
        if (Math.abs(x1 - x0) < eps)
            break;
        x0 = x1;
    }
    return [x1, i];  // return [approx. root, iterations]
}

// simple bisection
function bisection (func, interval, eps) {
    var xLo = interval[0];
    var xHi = interval[1];

    fHi = func(coeff,xHi)[0];   // fb
    fLo = func(coeff,xLo)[0];   // fa
    if (fLo * fHi > 0)
        return undefined;

    var xMid, fHi, fLo, fMid;
    var iter = 0;
    while (xHi - xLo > eps) {
        ++iter;
        xMid = (xLo+xHi)/2;
        fMid = func(coeff,xMid)[0];  // fc

        if (Math.abs(fMid) < eps)
            return [xMid, iter];

        else if (fMid*fLo < 0) { // fa*fc < 0 --> [a,c]
            xHi = xMid;
            fHi = fMid;
        } else {  // fc*fb < 0 --> [c,b]
            xLo = xMid;
            fLo = fMid;
        }
    }

    return [(xLo+xHi)/2, iter];
}

// f(x) = 5x^3 - 27x^2 + 60x - 20
//      = 5*(x-0.4)*(x^2 - 5x + 10)
var coeff = [-20,60,-27,5];  

var t0 = performance.now();
var sol1 = Newton (eval, 0.5, 1e-4);
var t1 = performance.now();
var sol0 = bisection (eval, [0,1], 1e-4);
var t2 = performance.now();

console.log ('Newton time: '+ (t1-t0).toFixed(3) +  ': ' + sol1);
console.log ('bisection time: '+ (t2-t1).toFixed(3) + ': ' + sol0);

person Jyun-Ming Chen    schedule 27.10.2015    source источник


Ответы (1)


Есть много внешних факторов, которые могут повлиять на этот тест, в том числе порядок, в котором ваш код компилируется JIT, и кэширование. Измерение времени на таком небольшом количестве итераций не имеет особого смысла, поскольку эти внешние факторы могут оказаться больше, чем то, что вы пытаетесь измерить.

Например, если вы инвертируете порядок, чтобы он вычислял бисекцию раньше, чем вычислял Ньютон, вы получите противоположный результат.

Если вы хотите сделать это лучше, возможно, запустите оба варианта один раз, затем сделайте цикл, чтобы запустить оба снова N раз, и измерьте, сколько времени требуется для запуска этого цикла.

person Gio    schedule 27.10.2015