2017-01-23 3 views
1

Я написал небольшой фрагмент кода, который я использую для накопления значений вектора, который быстрее, чем std :: accumulate, потому что он позволяет функции векторизация. Основной предпосылкой для функции является то, что вектор больше не используется после накопления. Код выглядит следующим образом:Для вектора вектора вектора с использованием указателей стиля C, но не с использованием итераторов

template <typename floatType> 
template <typename Iterator> 
double Numeric_class<floatType>::AmDestructiveAccumulate(Iterator A, size_t length) 
{ 
    if (length == 1) 
    { 
     return A[0]; 
    } 

    Iterator temp_; 
    while (length > 1) 
    { 
     if (length & 1) // odd 
     { 
      A[0] += A[length - 1]; // We add the last value which would otherwise be lost. 
      length >>= 1; 
      temp_ = A+length; 
      for (int i = 0; i < length; i++) 
      { 
       A[i] += temp_[i]; 
      } 
     } 
     else // even 
     { 
      length >>= 1; 
      temp_ = A+length; 
      for (int i = 0; i < length; i++) 
      { 
       A[i] += temp_[i]; 
      } 
     } 
    } 
    return A[0]; 
} 

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

Я использовал эту функцию с std::vector<double> data. Если я называю это A, то data.data(). Сказки векторизации располагаются так, как ожидалось, и я также получаю значительное увеличение скорости выполнения. Если я использую data.begin(), векторизация не выполняется. Я скомпилировал код с помощью VC2015 с полной оптимизацией. Есть ли причина, по которой было бы незаконно векторизовать версию итератора кода, или VC просто не делает этого, хотя это будет законным.

+0

TBH это звучит как ужасный алгоритм, и впечатляет, что оптимизатору удается спасти хотя бы некоторые версии. Вы превратили алгоритм O (N) линейного доступа в вариант O (N log N) с нелинейным доступом. – MSalters

+0

@MSalters Это неверно. Доступ по-прежнему равен O (N). Это N + N/2 + N/4 + N/8 .... Этот геометрический ряд сходится к N * 2. Кроме того, доступ является линейным на каждой итерации. И последнее, но не менее важное: профилирование показывает увеличение производительности для соответствующего N. –

+0

Справедливая точка, она выглядела как две внутренние петли длины N/2, для нечетных и четных, но используется только одна из двух, поэтому это не 'N + 2 * (N/2) + 4 * (N/4) + ... '. – MSalters

ответ

2

Основной проблемой будет A[i] += temp_[i];. Обратите внимание, что A и temp являются псевдонимами друг друга, но ваш выбор времени выполнения [i] означает, что это только теоретическое. Теперь, что означает [i]? Если A является указателем, это просто сокращение для *(A+i), но когда A является итератором, это вызов функции.

Эффективная векторизация требует, чтобы компилятор обнаружил, что запись в A[i] не влияет на последующие чтения с temp[i], что является нетривиальным наблюдением. Нет никакой зависимости от цикла, но оптимизатор должен быть в состоянии это доказать.

+0

Другими словами, в случае итератора нет ничего незаконного, но для оптимизатора это просто сложно понять. Честно говоря, для двойного случая выигрыш довольно мал. Однако для поплавков, когда 4 значения обрабатываются одновременно блоком вектора, эффект составляет примерно в 2 раза. –

+1

@PaulR: Лично я бы написал его как старомодное линейное дополнение, использующее AVX. Все, кто пишет 'A []' будет больно; для такой простой операции вы будете ограничены полосой пропускания. – MSalters

+0

Звук многообещающий. Я проверю это. –