2015-04-21 2 views
4

У меня есть большой блок данных, где некоторые операции были бы самыми быстрыми, если бы блок рассматривался как массив из 64-битных целых без знака, а другие были бы самыми быстрыми, если бы они рассматривались как массив из 32 бит целые числа без знака. «Самый быстрый», я имею в виду самый быстрый в среднем для машин, которые будут запускать код. Моя цель - быть почти оптимальной во всех средах, на которых работает код, и я думаю, что это возможно, если я использую указатель void, отведя его к одному из двух типов для разыменования. Это приводит меня к моим вопросам:Стоимость доступа к объединению vs с использованием фундаментальных типов

1) Если я использую указатель на void, будет ли его перетаскивание на один из двух типов для разыменования будет медленнее, чем непосредственно с помощью указателя нужного типа?

2) Правильно ли я в своем понимании стандарта, что это не будет нарушать правила сглаживания и что оно не приведет к каким-либо неопределенным или неуказанным поведением? 32 и 64-битные типы, которые я использую, существуют и не имеют дополнения (это статическое утверждение).

3) Правильно ли я понимаю правила сглаживания, чтобы в основном выполнять две цели: обеспечить безопасность и гарантировать компилятор для оптимизации? Если это так, если все ситуации, когда код, который я обсуждаю, будут выполняться, таковы, что не происходит другого разыменования, могу ли я потерять значительную оптимизацию компилятора?

Я отметил это с помощью 'c11', потому что мне нужно доказать из стандарта c11, что поведение четко определено. Любые ссылки на стандарт будут оценены.

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

EDIT: ответ и комментарии отметили, что с этим подходом существует проблема сглаживания, которую я проверил непосредственно в стандарте c11. Массив союзов потребует двух вычислений адресов и реферирования в 32-битном случае, поэтому я предпочел бы объединение массивов. Затем возникают вопросы:

1) Есть ли проблема с производительностью при использовании члена объединения в качестве массива, а не указателя на память? I.e., есть ли стоимость доступа члена профсоюза? Обратите внимание, что объявление двух указателей на массивы нарушает правила сглаживания, поэтому доступ должен производиться непосредственно через объединение.

2) Является ли содержимое массива гарантированным инвариантом при доступе через один массив, а затем через другой?

+0

И ваша проверка времени выполнения (if()) не влияет на производительность? Если вы хотите, чтобы это действительно невероятно оптимально, сделайте 2 сборки и проверьте время установки, которое из 2 для установки. – BitTickler

+0

Извините, я был не очень ясен в этом вопросе. Некоторые операции лучше с 64-битными типами, а другие - с 32-разрядными типами, поэтому я бы использовал оба члена объединения во время выполнения. – jack

+1

IIRC, тип-punning через объединение явно разрешен в C99. Что касается части производительности, да, может быть штраф за производительность, если вы записываете в память с использованием одного размера слова и сразу читаете его с использованием другого размера слова или другого выравнивания. Когда это происходит, это зависит от процессора. Если вы хотите остаться в стороне от этого штрафа, я бы рекомендовал сохранить не менее 100 циклов между записью и чтением.(100 циклов, вероятно, слишком много, но я никогда не сравнивал его, чтобы получить более точный номер.) – Mysticial

ответ

1
  1. Я не хотел бы использовать указатель на пустоту. Объединение двух массивов или массива объединения будет лучше.

  2. Используйте правильное выравнивание по всему типу. C11 обеспечивает alignas() как ключевые слова. GCC имеет атрибуты для выравнивания, которые являются нестандартными (и работают также в стандартах по 11). У других компиляторов вообще ничего не может быть. В зависимости от вашей архитектуры не должно быть влияния на производительность. Но это не может быть гарантировано (я не вижу ее проблемы, однако). Вы можете даже выровнять тип до более крупного типа, чем 64 бит, чтобы отлично заполнить строку кэша. Это может ускорить предварительную выборку и обратную запись.

  3. Алиасинг относится к тому факту, что объект ссылается на несколько указателей в одно и то же время. Это означает, что один и тот же адрес памяти может быть адресован с использованием двух разных «источников». Проблема в том, что компилятор может не знать об этом и, таким образом, может удерживать значение переменной в регистре CPU во время некоторых вычислений, не записывая ее обратно в память мгновенно. Если одна и та же переменная затем ссылается на другой «источник» (то есть на указатель), компилятор может считывать неверные данные из памяти. Imo - это сглаживание, относящееся только к функции, если внутри расположены два указателя. Итак, если вы не собираетесь передавать два указателя на один и тот же объект (или его часть), не должно быть никаких проблем. В противном случае вам следует устраивать барьеры (компиляторы). Редактировать: Стандарт C, по-видимому, немного более строгий, так как для выполнения определенных критериев требуется только lvalues, доступ к объекту (C11 6.5/7 (n1570) - thks Matt McNabb).

  4. О, и не используйте int/long/etc. Вы действительно должны использовать типы stdint.h, если вам действительно нужны правильные типы.

+2

Хорошо, у меня нет проблем с ниспровержением, но мне очень хотелось бы знать, где я был неправ? – Olaf

+0

Я нашел ваш ответ полезным, я не спустил его вниз. – jack

+0

@jack: Спасибо! Я просто ненавижу, что не получаю отзывов, почему я ошибаюсь в заявлении. Как я должен учиться на этом? – Olaf

1

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

  • алиасов
  • выравнивание
  • обивки

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

Проблемы с выравниванием часто упускают из виду в настоящее время, поскольку многие процессоры в настоящее время довольно терпимы к проблемам с выравниванием, но это ничего не переносит и может также иметь последствия для производительности. Таким образом, вы должны убедиться, что ваш массив выровнен таким образом, который подходит для всех типов, к которым вы обращаетесь к нему. Это можно сделать с помощью _Alignas в C11, у старых компиляторов есть расширения, которые также позволяют это. C11 добавляет некоторые ограничения для aligment, например, что это всегда значение 2, что должно позволить вам писать переносимый код в отношении этой проблемы.

Заполнение целочисленного типа в наши дни является чем-то редким (только исключение составляет _Bool), но убедитесь, что вы должны использовать типы, которые, как известно, не имеют проблем с этим. В вашем случае это [u]int32_t и [u]int64_t, которые, как известно, имеют точно запрограммированное количество бит и имеют представление двух дополнений для подписанных типов. Если платформа не поддерживает их, ваша программа просто не будет компилироваться.

Смежные вопросы