Вернемся на мгновение назад. Если проблема, которую вы пытаетесь решить, состоит в том, чтобы позволить базовой памяти эффективно обрабатываться встроенными функциями SIMD или развернутыми циклами, или и тем, и другим, вам не обязательно выделять память сверх используемого объема только для «округления». off" размер выделения кратен ширине вектора.
Существуют различные подходы, используемые для обработки этой ситуации, и вы упомянули пару, например, специальный код ввода и вывода для обработки начальной и конечной частей. На самом деле здесь есть две разные проблемы: обработка того факта, что данные не кратны ширине вектора, и обработка (возможно) невыровненных начальных адресов. Ваш метод перераспределения решает первую проблему, но, вероятно, есть лучший способ...
На практике большая часть SIMD-кода может просто считывать конец обрабатываемой области. Кто-то может возразить, что технически это UB, но при использовании встроенных функций SIMD вы уже выходите за рамки стандартного C++. На самом деле этот метод уже широко используется в стандартной библиотеке, поэтому он неявно поддерживается компилятором и сопровождающими библиотеки. Это также стандартный метод обработки SIMD-кодов в целом, так что вы можете быть уверены, что он не сломается внезапно.
Ключом к тому, чтобы заставить его работать, является наблюдение, что если вы можете правильно прочитать хотя бы один байт в каком-то месте N
, то любое естественно выровненное чтение любого размера1 победит. t вызвать ошибку. Конечно, вам все еще нужно игнорировать или иным образом обрабатывать данные, которые вы читаете за пределами официально выделенной области, но вам все равно придется делать это с вашим подходом «выделить больше», верно? В зависимости от алгоритма вы можете замаскировать недопустимые данные или исключить недопустимые данные после выполнения части SIMD (т. е. если вы ищете байт, если вы найдете байт после выделенной области, это то же самое, что «не найденный").
Чтобы это сработало, вам нужно читать выровненным образом, но, я думаю, это то, что вы, вероятно, уже хотите сделать. Вы можете либо договориться о том, чтобы ваша выделенная память была выровнена в первую очередь, либо выполнить чтение с перекрытием в начале (т. Е. Сначала одно невыровненное чтение, затем все выровнено с первым выровненным чтением, перекрывающим невыровненную часть), или использовать тот же трюк в качестве хвоста для чтения перед массивом (с теми же рассуждениями, почему это безопасно). Кроме того, существуют различные приемы для запроса выровненной памяти без необходимости написания собственного распределителя.
В целом, я рекомендую стараться избегать написания собственного распределителя. Если код не достаточно жестко ограничен, вы можете столкнуться с различными ловушками, включая другой код, делающий неверные предположения о том, как была выделена ваша память, и различные другие ловушки, которые Леон упоминает в своем ответе. Кроме того, использование пользовательского распределителя отключает множество оптимизаций, используемых стандартными алгоритмами контейнера, если только вы не используете его повсеместно, поскольку многие из них применимы только к контейнерам, использующим один и тот же распределитель.
Кроме того, когда я фактически реализовывал пользовательские распределители2 , я обнаружил, что это хорошая идея в теории, но слишком неясная, чтобы поддерживать ее одинаковым образом во всех компиляторах. Теперь компиляторы со временем стали намного более совместимыми (в основном я смотрю на вас, Visual Studio), а поддержка шаблонов также улучшилась, так что, возможно, это не проблема, но я чувствую, что она все еще попадает в категорию «сделать». это только в том случае, если вы должны».
Имейте в виду также, что пользовательские распределители не очень хорошо компонуются - вы получаете только один! Если кто-то еще в вашем проекте захочет использовать настраиваемый распределитель для вашего контейнера по какой-либо другой причине, он не сможет этого сделать (хотя вы можете согласовать и создать комбинированный распределитель).
Этот вопрос, который я задал ранее, также мотивированный SIMD, охватывает много вопросов о безопасности чтения после конца ( и, неявно, перед началом), и, вероятно, это хорошее место для начала, если вы рассматриваете это.
1 Технически, ограничением является любое выровненное чтение до размера страницы, которого при размере 4 КБ или больше достаточно для любой из текущих векторно-ориентированных ISA общего назначения.
2 В данном случае я делал это не для SIMD, а в основном для того, чтобы избежать malloc()
и разрешить частичное и непрерывное быстрое выделение в стеке для контейнеров с множеством мелких узлов.
person
BeeOnRope
schedule
30.10.2016
n
. Я просто хочу развернуть какой-то цикл, не следя за тем, чтобы не промахнуться (или проверить, есть ли что-то, что нужно обработать неразвернутым способом). - person Pixelchemist   schedule 15.10.2016reserve
(до развертывания операции, например,reserve(n+x)
) вместо того, чтобы возиться с распределителем? И если вы не сохраняете ничего полезного послеsize
, почемуi<(n+x)
вместо простогоi<n
? - person FranMowinckel   schedule 15.10.2016i<n
в этом случае точно эквивалентен. Этот код не настоящий код, а просто иллюстрация. - person Pixelchemist   schedule 15.10.2016