оптимизация цикла массива в c - PullRequest
3 голосов
/ 24 мая 2011

Я посмотрел в Интернете и в своих книгах, но я не могу этого понять.Меня попросили оптимизировать небольшую часть программы.В частности, чтобы взять массив и добавить его содержимое в течение небольшого промежутка времени, с помощью vi и gcc, без использования встроенного оптимизатора.Я пробовал развертывание цикла и несколько других оптимизаций, предназначенных для продуктов.Не могли бы вы помочь?

int length = ARRAY_SIZE;
int limit = length-4;
for (j=0; j < limit; j+=5) {
    sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4];
}
for(; j < length; j++){
    sum += array[j];    
}

Значения массива не постоянны int с, и все значения были инициализированы.

Ответы [ 7 ]

9 голосов
/ 24 мая 2011

Создание сумм, которые затем складываются в сумму.

Вот базовая версия того, как это может выглядеть

for (j=0; j < limit; j+=4) {
    sum1 += array[j];
    sum2 += array[j+1];
    sum3 += array[j+2];
    sum4 += array[j+3];
}
sum = sum1 + sum2 + sum3 + sum4;

Это позволяет избежать некоторых зависимостей чтения после записито есть вычисление sum2 в каждой итерации цикла не должно ожидать результатов sum1 для выполнения, и процессор может планировать обе строки в цикле одновременно.

3 голосов
/ 24 мая 2011

используйте набор sse / mmx:

__m128i sum;
for (j=0; j < limit; j+=4) {
    sum = _mm_add_epi32(sum, array+j);
}
1 голос
/ 24 мая 2011

Как есть, цикл уже развернут на 5.

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

Первый цикл можно заменить на:

int* p = array;
for (j = 0; j < ARRAY_SIZE - 4; j += 5, p += 5){
  sum += p[0] + p[1] + p[2] + p[3] + p[4];
}

, поэтому он не выполняет индексирование (умножение j на sizeof(int) и добавление его к адресу).

Добавлено: Конечно, поскольку ARRAY_SIZE предположительно является известной константой, это, вероятно, самый быстрый код, но вам может потребоваться написать генератор кода (или умный макрос), чтобы сделать его:

sum += array[0];
sum += array[1];
...
sum += array[ARRAY_SIZE - 1];

Пример такого макроса: если ARRAY_SIZE имеет степень 2, например 64, вы можете получить:

#define FOO64(i) FOO32(i); FOO32((i)+32)
#define FOO32(i) FOO16(i); FOO16((i)+16)
#define FOO16(i) FOO8(i); FOO8((i)+8)
#define FOO8(i) FOO4(i); FOO4((i)+4)
#define FOO4(i) FOO2(i); FOO2((i)+2)
#define FOO2(i) FOO1(i); FOO1((i)+1)
#define FOO1(i) sum += array[i]

FOO64(0);

Вы могли бы сделать ту же идею для других сил, например 10.

0 голосов
/ 24 мая 2011

Вы можете повысить производительность, предварительно выбрав данные внутри свернутого цикла.
Я буду опираться на ответ Дрю:

register int value1, value2, value3, value4;
or (j=0; j < limit; j+=4)
{
    // Prefetch the data
    value1 = array[j];
    value2 = array[j + 1];
    value3 = array[j + 2];
    value4 = array[j + 4];

    // Use the prefetched data
    sum1 += value1;
    sum2 += value2;
    sum3 += value3;
    sum4 += value4;
}
sum = sum1 + sum2 + sum3 + sum4;

Идея состоит в том, чтобы процессор загружал непрерывные данные в кэш, а затем работал с кэшированными данными. Чтобы это было эффективным, компилятор не должен оптимизировать предварительную выборку; это можно сделать, объявив временные переменные как volatile. Я не знаю, можно ли объединить volatile с register.

Поиск в Интернете по запросу «Дизайн, управляемый данными».

0 голосов
/ 24 мая 2011

Поскольку в выборке, по-видимому, число добавлений за один раз составляет пять, я делаю это и здесь. Обычно вы делаете это с силой 2, как предложил Дрю Хоскинс. Получив модуль по умолчанию в начале и шагнув в другом направлении, может потребоваться меньшее количество значений. Вычисления в другом порядке - это то, что часто выгодно в научных вычислениях, а не только для индексации. Чтобы понять, насколько удачной является оптимизация, необходимо провести тестирование.

int sum1, sum2, sum3, sum4;

for(j = ARRAY_SIZE; j%5; j--){
    sum += array[j]; 
}
sum1 = sum2 = sum3 = sum4 = 0;
for (; j; j-=5) {
    sum += array[j-1];
    sum1 += array[j-2];
    sum2 += array[j-3];
    sum3 += array[j-4];
    sum4 += array[j-5];
}
sum += sum1+sum2+sum3+sum4;
0 голосов
/ 24 мая 2011

Я не уверен , почему вы не можете использовать оптимизатор, поскольку, по моему опыту, он, как правило, будет генерировать более быстрый код, чем подавляющее большинство "желающих" ручных оптимизаторов :-) В Кроме того, вы должны убедиться, что этот код является на самом деле проблемной областью - нет смысла оптимизировать код, который уже близок к максимальной скорости, и при этом вам не следует беспокоиться о том, что составляет 0,01% времени, затрачиваемого на в другом месте может быть код, ответственный за 20%.

Оптимизация должна быть нацелена, иначе это бесполезное усилие.

Любое решение, кроме наивного "просто сложить числа", скорее всего, будет использовать специальные функции в целевом ЦП.


При условии, что вы готовы к небольшому удару при каждом обновлении массива (и это может быть не вариант, учитывая ваш комментарий "все значения были инициализированы"), вы можете получить сумму в очень быстрое время. Используйте "класс", чтобы поддерживать массив и сумму рядом. Псевдокод вроде:

def initArray (sz):
    allocate data as sz+1 integers
    foreach i 0 thru sz:
        set data[i] to 0

def killArray(data):
    free data

def getArray (data,indx):
    return data[indx+1]

def setArray (data,indx,val):
    data[0] = data[0] - data[indx] + val
    data[indx+1] = val

def sumArray(data):
    return data[0]

должен сделать трюк.


Следующая полная программа на C показывает очень грубый первый разрез, который вы можете использовать в качестве основы для более надежного решения:

#include <stdio.h>
#include <stdlib.h>

static int *initArray (int sz) {
    int i;
    int *ret = malloc (sizeof (int) * (sz + 1));
    for (i = 0; i <= sz; i++)
        ret[i] = 0;
    return ret;
}

static void killArray(int *data) {
    free (data);
}

static int getArray (int *data, int indx) {
    return data[indx+1];
}

static void setArray (int *data, int indx, int val) {
    data[0] = data[0] - data[indx] + val;
    data[indx+1] = val;
}

static int sumArray (int *data) {
    return data[0];
}

int main (void) {
    int i;
    int *mydata = initArray (10);
    if (mydata != NULL) {
        setArray (mydata, 5, 27);
        setArray (mydata, 9, -7);
        setArray (mydata, 7, 42);
        for (i = 0; i < 10; i++)
            printf ("Element %d is %3d\n", i, getArray (mydata, i));
        printf ("Sum is %3d\n", sumArray (mydata));
    }
    killArray (mydata);
    return 0;
}

Вывод этого:

Element 0 is   0
Element 1 is   0
Element 2 is   0
Element 3 is   0
Element 4 is   0
Element 5 is  27
Element 6 is   0
Element 7 is  42
Element 8 is   0
Element 9 is  -7
Sum is  62

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


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

0 голосов
/ 24 мая 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...