C - есть простой цикл, который выполняет арифметические вычисления; профайлер показывает, что это узкое место. Как его ускорить?

Мой первый пост здесь. Отличный сайт и ресурс.

Я немного поискал и просмотрел вопросы с похожими названиями, но не смог найти что-то конкретно об этом.

Я пытаюсь удалить избыточность и раздутость из библиотеки астрономических расчетов на C, которую использует моя программа на C++. Я запустил простой профайлер (VerySleepy).

Вот код, который профилировщик показал как использующий больше всего времени (не считая функций библиотеки Си sprintf и т.д.):

double swi_echeb(const double x, const double* const coef, const int ncf)
{
    int j = ncf - 1;
    double x2, br, brp2, brpp;
    x2 = x * 2.;
    br = 0.;
    brp2 = 0.;  /* dummy assign to silence gcc warning */
    brpp = 0.;

    for (; j >= 0; --j) {                 // <-- 0.39s
        brp2 = brpp;                      // <-- 0.01s
        brpp = br;                        // <-- 0.32s
        br = x2 * brpp - brp2 + coef[j];  // <-- 3.49s ***
    }                                     // <-- 0.14s

    return (br - brp2) * .5;              // <-- 0.06s
}                                         // <-- 0.05s

Эта конкретная функция глубоко встроена в другие, и основная "стартовая" функция, которую вызывает моя программа, вызывается тысячи раз.

Вы можете видеть, что выделяющееся утверждение с временем 3,49 с намного больше, чем время всех остальных утверждений. Я знаю, что есть способы ускорить арифметику C, используя умножение вместо деления, когда это возможно. Но я не знаю намного больше, чем это.

Например:

  1. Не лучше ли разбить это утверждение на более мелкие части:

    br = x2 * brpp;

    br -= brp2;

    br += coef[j];

Любые другие идеи или критика. Я не писал этот код, хотя я добавил const к параметрам функции, так как я люблю const-корректность.

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

Я знаю, что люди скажут: "Попробуй!". Так что я попробую, и буду обновлять результаты, если это поможет кому-то с похожими арифметическими вопросами.

EDIT: Выкладываю результаты, которые я проверил из предложенных

В порядке от самого быстрого к самому медленному, вот что я нашел на данный момент. Профилировщик - VerySleepy. Компилятор - Visual Studio 2008 Pro Ed. Параметры компиляции для библиотеки и моего приложения:

Debug, формат C7, /O2 /Ob2 /Oi /Ot /Oy /GT /GL /GF /FD /MTd /GS- /Gy /fp:fast /FAs

Ниже приводится предложение Эндрю о выполнении "4 итераций на цикл". Это было самым быстрым на данный момент.

ОБЩЕЕ ВРЕМЯ, проведенное в функции (время других утверждений в функции здесь не показано) = 2,08 секунды

for (; index >= 3; index -= 4) {                    // 0.02s
    brp2    = brpp;
    brpp    = br;                                   // 0.02s
    br      = x2 * brpp - brp2 + coef[index];       // 0.25s
    brp2    = brpp;
    brpp    = br;                                   // 0.13s
    br      = x2 * brpp - brp2 + coef[index - 1];   // 0.33s
    brp2    = brpp;
    brpp    = br;                                   // 0.13s
    br      = x2 * brpp - brp2 + coef[index - 2];   // 0.34s
    brp2    = brpp;
    brpp    = br;                                   // 0.14s
    br      = x2 * brpp - brp2 + coef[index - 3];   // 0.42s
}

for (; index >= 0; --index) {                 // 0.03s
    brp2    = brpp;                           // 0.03s
    brpp    = br;
    br      = x2 * brpp - brp2 + coef[index]; // 0.11s
}

Следующим по скорости был оригинальный неизмененный код, с общим временем 2,39 секунды внутри функции, опять же включая утверждения вне цикла. Обратите внимание, что это меньше, чем в моем первоначальном сообщении. Мой первоначальный пост был неоптимизированным кодом, но поскольку все предложили это, все мои тесты были впоследствии оптимизированы настолько, насколько я мог получить в VS08:

for (j = ncf - 1; j >= 0; j--) {      // 0.02s
    brp2 = brpp;                      // 0.03s
    brpp = br;                        // 0.07s
    br = x2 * brpp - brp2 + coef[j];  // 2.14s
}

После этого первоначального кода, следующим самым быстрым была идея Дрю установить указатель заранее и использовать это. Общее время, проведенное внутри функции, составило 2.49 секунды, включая время от операторов вне цикла:

for (; index >= coef; --index) {         // 0.01s
    brp2    = brpp;
    brpp    = br;                        // 0.06s
    br      = x2 * brpp - brp2 + *index; // 2.24s
}

Я также попробовал сочетание разворачивания цикла Эндрю и использования указателя Дрю, но это заняло 2.39 секунды, столько же, сколько и неизмененный код.

Судя по результатам, разворачивание цикла является оптимальным для моего использования.

18
задан nedshares 1 January 2012 в 02:58
поделиться