Функция, написанная не является ни оптимальной, ни переносной. Это, как было сказано, авторы C89 содержат некоторые плохо написанные правила, которые, если интерпретировать, делают C89 гораздо менее мощным языком, чем более ранние диалекты C, которые существовали на многих платформах. Заявленная цель этого правила, чтобы избежать, требующих C компиляторы, которые данный код как:
float *fp;
int i;
int foo(void)
{
i++;
*fp=1.0f;
return i;
}
от того, чтобы пессимистически предположить, что запись в *fp
может повлиять на i
, несмотря на полное отсутствие чего-либо, что можно было бы предположить, что что-то типа int
может быть затронуто. Код, который использовал тип punning для различных целей, включая оптимизацию каналов, был широко распространен, когда был написан C89, но в большинстве случаев такой код включал бы четкие указания компилятору, что будет происходить сглаживание. Как правило, если объект будет изменен указатель иностранного типа между двумя «нормальный» доступ, один или оба будут происходить между двумя нормальными доступов следующее:
адрес объекта будут приняты ,
Указатель будет преобразован из типа объекта в другой тип.
Помимо очевидного случая, когда указатель используется для доступа к объекту из его точного типа, стандарт в основном определяет случаи, когда это не было бы очевидно, что компилятор должен предполагать наложение спектров было возможно (например, между «int» и указатель типа «unsigned *», или между чем-либо и указателем типа «char *»). Учитывая обоснованность, я думаю, что авторы намеревались сосредоточиться на том, чтобы авторы сценариев обрабатывали случаи, когда не было бы основанием ожидать наложения псевдонимов, но не думали, что они должны быть рассказали, как идентифицировать случаи, когда это было очевидно, вероятно.
отрывов оптимизации будут безопасными на компиляторы, которые признают, что Адресов и литейные операторов предполагают, что перекрестный тип сглаживание, вероятно, при условии, что любое использование указателя в результате броска выполняется до следующего доступа с использованием указателя без заливки - требование, которое обычно соответствует большинству . К сожалению, нет стандарта для «sane-compiler C», а gcc использует тот факт, что авторы стандарта не требовали, чтобы компиляторы обрабатывали случаи очевидного сглаживания как оправдание , чтобы игнорировать их.
Тем не менее, преимущество производительности от отрывов оптимизации может перевесить затраты производительности -fno-strict-aliasing
, особенно если код использует restrict
классификаторов, когда это необходимо. Есть несколько ситуаций, в основном с глобальными переменными, где restrict
не является достаточным для включения полезных оптимизаций; они могут обрабатываться режимом псевдонимов, который ограничивает анализ объектами статической или автоматической продолжительности (например, объекты в примере обоснования ), но gcc не предлагает такого режима.
Кстати, я не уверен, что тайминги инструкции, как на современных процессорах x86, но на некоторых ARM варианты компиляторы будут иметь шанс получения оптимального кода долго струнами из чего-то вроде:
uint32_t x01000000 = 0x01000000;
uint64_t *search(uint64_t *p)
{
uint64_t n;
uint32_t a,b;
uint32_t v = x01000000; // See note
do
{
n=*p++;
a=(uint32_t)n;
b=n>>32;
if (a >= v || (a << 8) >= v || (a << 16) >= v || (a << 24) >= v ||
b >= v || (b << 8) >= v || (b << 16) >= v || (b << 24) >= v) return p;
} while(1);
}
Выяснение из которого сравнение было связано с выходом из цикла, потребовалось бы дополнительное время, но сохранение таких соображений вне цикла могло бы позволить самому циклу быть более эффективным.
Многие процессоры ARM имеют инструкцию, которая может сравнивать сдвинутое значение с регистром; компиляторам иногда нужно немного помочь понять, что 0x01000000 следует хранить в регистре (существует команда сравнения с константой, но не включает «свободный» сдвиг сравниваемого регистра), но с помощью они могут найти сравнение со сдвигом. Я еще не нашел способ убедить компилятор для генерации оптимального кода для ARM7-TDMI, которое было бы эквивалентно:
search:
mov r1,#0x010000000
lp:
ldrmia r0,{r2,r3}
cmp r1,r2
cmplt r1,r2,asl #8
cmplt r1,r2,asl #16
cmplt r1,r2,asl #24
cmplt r1,r3
cmplt r1,r3,asl #8
cmplt r1,r3,asl #16
cmplt r1,r3,asl #24
blt lp
bx lr
Это займет 15 циклов за восемь байт; он может быть адаптирован для выполнения 25 циклов на шестнадцать байт. Цикл, который обрабатывает восемь байтов индивидуально, займет 42 цикла; разворачивается до шестнадцати байтов, это будет 82 цикла. Лучшая петля, которую я видел, компиляторы генерируют для кода на основе uint64_t, будет 22 цикла для восьми байтов - почти наполовину до тех пор, пока оптимальный код, но все же примерно в два раза быстрее, чем версия с байтами.
Это нарушает правило. Но попытались ли вы сравнить его? Я уверен, что компилятор будет лучше работать, оптимизируя стандартный 'strlen'. –
Он также нарушает выравнивание. –
[OT] Если вы используете 'std :: string' вместо' char * ', то функция' size' - это O (1), которую трудно превзойти :) – NathanOliver