Создание матрицы уничтожения в с ++ лучшая практика? - PullRequest
2 голосов
/ 02 марта 2012

Предположим, у меня есть код на c ++ с множеством небольших функций, в каждой из которых мне, как правило, понадобится матричное число с плавающей запятой M1 (n, p) с n, p, известным во время выполнения, чтобы содержать результаты промежуточных вычислений (нет необходимостичтобы инициализировать M1, просто объявить его, потому что каждая функция будет просто перезаписывать все строки M1).

Отчасти причина этого заключается в том, что каждая функция работает с исходной матрицей данных, которую она не может изменить,так много операций (сортировка, удаление значений, сферирование) должны быть выполнены в «другом месте».

Лучше ли создавать временные M1 (n, p) внутри каждой функции или, скорее, один раз и длявсе в main () и передать его каждой функции как своего рода контейнер, который каждая функция может использовать в качестве места для вырезки?

n и p часто умеренно велики [10 ^ 2-10 ^ 4] для nи [5-100] для стр.

(первоначально размещено в стеке обмена кодами, но перенесено сюда).

Наилучшее,

Ответы [ 4 ]

2 голосов
/ 02 марта 2012

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

(Кстати, я не думаю, что stackoverflow является правильным местом для этого).

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

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

Дизайн вашей матрицы должен быть оптимальным.Мы должны были бы взглянуть на этот дизайн.

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

2 голосов
/ 02 марта 2012
  1. выделение кучи действительно, довольно дорого.
  2. преждевременная оптимизация - это плохо, но если ваша библиотека довольно общая и матрицы огромные, поиск эффективного дизайна может быть преждевременным. В конце концов, вы не хотите изменять свой дизайн после того, как накопили много зависимостей от него.
  3. Существуют различные уровни, на которых вы можете решить эту проблему. Например, можно избежать затрат на выделение кучи, решая их на уровне распределителя памяти (например, пул памяти для каждого потока)
  4. хотя выделение кучи стоит дорого, вы создаете одну гигантскую матрицу только для выполнения довольно дорогих операций над матрицами (обычно линейной сложности или хуже). Условно говоря, выделение матрицы в бесплатном хранилище может быть не таким дорогим по сравнению с тем, что вы неизбежно будете делать с ним впоследствии, поэтому оно может быть довольно дешевым по сравнению с общей логикой функции, такой как сортировка.

Я рекомендую вам написать код естественным образом, принимая во внимание возможность №3 в будущем. То есть, не используйте ссылки на матричные буферы для промежуточных вычислений, чтобы ускорить создание временных. Сделать временные и вернуть их по значению. Корректность и хорошие, понятные интерфейсы на первом месте.

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


ВНИМАНИЕ: Следующее предназначено, только если вы действительно хотите выжать максимум из каждого цикла. Важно понять # 4, а также получить хороший профилировщик. Стоит также отметить, что вы, вероятно, добьетесь большего успеха, оптимизировав шаблоны доступа к памяти для этих матричных алгоритмов, чем пытаясь оптимизировать распределение кучи.


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

Другими словами:

Лучше ли объявлять M1 (n, p) внутри каждой функции или скорее раз и навсегда в main () и передать его каждой функции как своего рода контейнер, который каждая функция может использовать в качестве места для отходов.

Продолжайте и создайте M1 как временное в каждой функции. Старайтесь не требовать от клиента составления матрицы, которая не имеет для него / нее значения только для вычисления промежуточных результатов. Это могло бы раскрыть детали оптимизации, чего мы должны избегать при разработке интерфейсов (скрыть все детали, о которых клиенты не должны знать).

Вместо этого сосредоточьтесь на более общих понятиях, если вы абсолютно хотите, чтобы эта опция ускорила создание этих временных файлов, как необязательный распределитель. Это соответствует практическим проектам, таким как std::set:

std::set<int, std::less<int>, MyFastAllocator<int>> s; // <-- okay

Хотя большинство людей просто делают:

std::set<int> s;

В вашем случае это может быть просто: M1 my_matrix (n, p, alloc);

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

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


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

1 голос
/ 02 марта 2012

Преимущества в требовании внешнего буфера есть с точки зрения производительности, особенно когда вам необходимо объединить функции, которые его используют.

Однако, с точки зрения пользователя, это может скоро стать раздражающим.

Я часто обнаруживал, что в C ++ достаточно просто получить лучшее из обоих миров, просто предлагая оба способа:

int compute(Matrix const& argument, Matrix& buffer);

inline int compute(Matrix const& argument) {
  Matrix buffer(argument.width, argument.height);
  return compute(argument, buffer);
}

Эта очень простая упаковка означает, что код написан один раз, и представлены два слегка отличающихся интерфейса.

Более сложный API (принимая buffer) также немного менее безопасен, так как buffer должен учитывать некоторые ограничения по размеру в аргументе, поэтому вы можете захотеть дополнительно изолировать быстрый API (для пример за пространством имен), чтобы побудить пользователей сначала использовать более медленный, но более безопасный интерфейс, и пробовать быстрый только тогда, когда это необходимо.

1 голос
/ 02 марта 2012

Сначала попытайтесь определить матрицу внутри функции. Это определенно лучший выбор дизайна. Но если вы получаете потери производительности, которые вы не можете перенести, я думаю, что «пропускной буфер на ссылку» в порядке, если вы помните, что функции больше не являются поточно-ориентированными. Если в какой-то момент вы используете потоки, каждому потоку необходим собственный буфер.

...