Я написал оптимизированный код для алгоритма, вычисляющего вектор величин. Я рассчитал это до и после различных попыток получить данные, вычисленные в функции из функции. Я думаю, что ни специфика вычислений, ни природа вектора величин не имеет значения. Схема кода, тайминги и подробности приведены ниже.
Весь код был скомпилирован со следующими флагами:
g ++ -Wall -Wextra -Werror -std = c ++ 11 -pedantic -O3
У меня есть такой класс:
#ifndef C_H
#define C_H
#include <iostream>
#include <iterator>
#include <vector>
Class c {
public:
void doWork( int param1, int param2 ) const {
std::array<unsigned long,40> counts = {{0}};
// LOTS of branches and inexpensive operations:
// additions, subtractions, incrementations, and dereferences
for( /* loop 1 */ ) {
// LOTS MORE branches and inexpensive operations
counts[ /* data dependent */ ] += /* data dependent */;
for( /* loop 2 */ ) {
// YET MORE branches inexpensive operations
counts[ /* data dependent */ ] += /* data dependent */;
}
}
counts [ /* data dependent */ ] = /* data dependent */;
/* exclude for profiling
std::copy( &counts[0], &counts[40], std::ostream_iterator( std::cout, "," ) );
std::cout << "\n";
*/
}
private:
// there is private data here that is processed above
// the results get added into the array/vector as they are computed
};
#endif
И вот такой главный:
#include <iostream>
#include "c.h"
int main( int argc, char * argv ) {
Class c( //set the private data of c by passing data in );
int param1;
int param2;
while( std::cin >> param1 >> param2 ) {
c.doWork( int param1, int param2 );
}
}
Вот некоторые важные подробности о данных:
- 20 миллионов пар читаются на стандартном вводе (перенаправляются из файла)
- 20 миллионов звонков в c.doWork
- ВСЕГО 60 миллионов итераций через внешний цикл в c.doWork
- ВСЕГО 180 миллионов итераций через внутренний цикл в c.doWork
На все это требуется ровно 5 минут 48 секунд. Естественно, я могу распечатать массив внутри функции класса, и это то, что я делал, но я собираюсь опубликовать код, и некоторые варианты использования могут включать в себя желание сделать что-то, кроме печати вектора. В этом случае мне нужно изменить подпись функции, чтобы фактически передать данные пользователю. Вот где возникает проблема. Вещи, которые я пробовал:
Создание вектора в main и передача его по ссылке:
std::vector<unsigned long> counts( 40 ); while( std::cin >> param1 >> param2 ) { c.doWork( param1, param2, counts ); std::fill( counts.begin(), counts.end(), 0 ); }
Для этого требуется 7 минут 30 секунд. Удаление вызова std :: fill уменьшает это только на 15 секунд, так что это не учитывает расхождения.
Создание вектора в функции doWork и его возврат с использованием семантики перемещения. Поскольку это требует динамического распределения для каждого результата, я не ожидал, что это будет быстро. Как ни странно, не намного медленнее. 7 минут 40 секунд.
Возврат std :: array, который в настоящее время находится в doWork по значению. Естественно, это должно копировать данные при возврате, поскольку массив стека не поддерживает семантику перемещения. 7 минут 30 секунд
Передача std :: array по ссылке.
while( std::cin >> param1 >> param2 ) { std::array<unsigned long,40> counts = {{0}}; c.doWork( param1, param2, counts ) }
Я ожидал, что это будет примерно эквивалентно оригиналу. Данные помещаются в стек в функции main и передаются по ссылке в doWork, которая заполняет их. 7 минут 20 секунд. Этот действительно ставит меня в тупик.
Я не пробовал передавать указатели в doWork, потому что это должно быть эквивалентно передаче по ссылке.
Одно из решений состоит в том, чтобы иметь две версии функции: одну, которая печатает локально, а другая - возвращает. Проблемой является то, что мне пришлось бы дублировать ВСЕ код, потому что вся проблема здесь в том, что я не могу эффективно получить результаты из функции.
Так что я озадачен. Я понимаю, что любое из этих решений требует дополнительного разыменования для каждого доступа к массиву / вектору внутри doWork, но эти дополнительные разыменования очень тривиальны по сравнению с огромным количеством других быстрых операций и более проблемных ветвей, зависящих от данных.
Я приветствую любые идеи, чтобы объяснить это. Моя единственная мысль заключается в том, что код оптимизируется компилятором, так что некоторые необходимые в противном случае компоненты вычислений опускаются в исходном случае, потому что компилятор понимает, что в этом нет необходимости. Но это кажется противопоказанием по нескольким причинам:
- Внесение изменений в код внутри циклов меняет тайминги.
- Исходное время составляет 5 минут 50 секунд, тогда как простое чтение пар из файла занимает 12 секунд, поэтому делается много.
- Возможно, оптимизируются только операции, связанные со счетчиками, но это кажется странно избирательной оптимизацией, учитывая, что, если они оптимизируются, компилятор может понять, что поддержка вычислений в doWork также не нужна.
- Если операции, связанные со счетчиками, НЕ оптимизируются, почему они не оптимизируются в других случаях. Я фактически не использую их в основном.
Дело в том, что doWork компилируется и оптимизируется независимо от main, и поэтому, если функция имеет какое-либо обязательство возвращать данные в любой форме, она не может быть уверена в том, будет ли она использоваться или нет?
Является ли мой метод профилирования без печати, который должен был избежать затрат на печать, чтобы подчеркнуть относительные различия в различных методах, ошибочен?
Я благодарен за любой свет, который вы можете пролить.
counts
? Неужели это всего лишь 40 элементов (копирование которых займет очень мало времени)? Я вижу&counts[1156]
в вашем прокомментированном коде. - person Xymostech   schedule 28.04.2013array
внутри функции и если ваши циклы, скажем, от0
доarray.size()
, компилятор может это сделать. Когда вы передаетеarray &
в качестве параметра, компилятор не знает, какой у него размер, и не может развернуть циклы ... Это предположение, вам нужно проверить код, выданный компилятором в обоих случаях. - person lapk   schedule 29.04.2013array
является локальной переменной в функции, компилятор может оптимизировать код на основе этого - размер никогда не изменяется в функции. Еслиarray
передается в качестве параметра, компилятор не может использовать размер для оптимизации кода функции - он должен выдать общий код, который будет работать со всемиarray
независимо от их размера ... Вы снова сравнивали дизассемблирование? Чтобы сделать вещи очевидными, запретите любые оптимизации и сравните время для обеих версий без оптимизации. - person lapk   schedule 29.04.2013vector
s,resize
перед их передачей, конечно.)std::distance
может сказать вам размер конца диапазона (сохраните его вconst
). Конечно, это не касается разворачивания цикла из-за известных итераций (размера массива). Вы также можете попробовать написать шаблон, охватывающий два случая: 1) размер, известный во время компиляции, 2) размер, известный только во время выполнения. - person dyp   schedule 29.04.2013