Здравствуйте, Smal, Вы писали:
S>А еще лучше так (и короче, и переносимо).
Согласен, только вот платформа x86 изначально разваращает и в принципе провоцирует использование в С приведение типов, потому как у нее при чтении невыровненной памяти происходит два чтения смежных областей памяти (на 80x86 — 80x286 соответсвенно двух слов) и все пучком, время выполнения увеличивается только.
Здравствуйте, Alexander Pazdnikov, Вы писали:
E>>Насколько я понимаю, в C++ стандарте такого ограничения нет. Тем не менее, GCC 3.4.4 для C++ генерирует такой же проблемный код, как и для C
E>>McSeem2 прав -- такая паранойя. Если уж C/C++ позиционируются как языки для работы непосредственно с железом, то запрещение простого свопинга байт/слов выглядит полным маразмом.
AP>Вообще, такая проблема наблюдается на процессорах, адресация в которых к данным должна быть выравнена по границе 2, 4 и т.д. (8 не встречал) AP>Так вот, сразу же налетел на ARM (AT91RM9200) на выравнивание по границе кратной 2. AP>Потратил на это порядка несольких часов, просто ушли в холостую. AP>А произошло это из-за того,что надо было из буфера (char*)[1] получить uint32_t или наоборот в (char*)[1] положить uint32_t. AP>Тут бы по выравниванию памяти вообще механизм блокировок придумать какой-то было бы просто замечательно.
Выравнивание -- это не новая проблема. Но как раз в приведенном McSeem2 механизме свопинга проблем быть не должно. Поскольку uint32_t* будет выравнен на кратную 4-м границу, uint16_t* на кратную 2-м границу, а char* на любой адрес. Такие заморочки были на SPARC-ах когда-то очень давно. И ничего, все работало.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Выравнивание -- это не новая проблема. Но как раз в приведенном McSeem2 механизме свопинга проблем быть не должно. Поскольку uint32_t* будет выравнен на кратную 4-м границу, uint16_t* на кратную 2-м границу, а char* на любой адрес. Такие заморочки были на SPARC-ах когда-то очень давно. И ничего, все работало.
Мда... Сейчас перевожу проект под ARM с Си на С++, так что чувствую, если включить -O2, то в кусках могут появиться интересные глюки, невоспроизводимые при -g
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, Smal, Вы писали:
S>>А еще лучше так (и короче, и переносимо).
S>>
S>>void swap_words(uint32_t* arg)
S>>{
S>> uint16_t hi = (uint16_t) ((*arg & 0xFFFF0000u)>>16);
S>> uint16_t lo = (uint16_t) ((*arg & 0x0000FFFFu));
S>> *arg = hi | (lo << 16);
S>>}
S>>
E>А по скорости?
Лениво тестировать. Но очень хочется верить, что это не bottle-neck %).
Оптимизировать нужно алгоритмы, а не битовые операции. Если окажется, что
данная операция сжирает хоть какое-то значимое время, то значит нужно
оптимизировать алгоритм, её вызывающий.
Кто-нибудь!!!
Т.е. получается, что sp будет указывать по непонятному адресу? Т.е. реально вообще получить Segmentation Fault (при sp[0] = hi)?!!!
Правильно ли я понял?
Здравствуйте, Smal, Вы писали:
S>>>А еще лучше так (и короче, и переносимо).
S>>>
S>>>void swap_words(uint32_t* arg)
S>>>{
S>>> uint16_t hi = (uint16_t) ((*arg & 0xFFFF0000u)>>16);
S>>> uint16_t lo = (uint16_t) ((*arg & 0x0000FFFFu));
S>>> *arg = hi | (lo << 16);
S>>>}
S>>>
E>>А по скорости? S>Лениво тестировать. Но очень хочется верить, что это не bottle-neck %). S>Оптимизировать нужно алгоритмы, а не битовые операции. Если окажется, что S>данная операция сжирает хоть какое-то значимое время, то значит нужно S>оптимизировать алгоритм, её вызывающий.
Угу. Только вот когда придется мегабайты данных преобразовывать из host byte order в network byte order разница в скорости двух простых решений может оказаться слишком существенной. Так что результаты замеров были бы очень интересны.
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, Smal, Вы писали:
S>А еще лучше так (и короче, и переносимо).
Первый вариант тоже вполне переносим. И его тоже можно записать достаточно коротко:
void swap_words(uint32_t* arg)
{
union { uint32_t v; uint16_t sp[2]; } u = {*arg};
std::swap(u.sp[0], u.sp[1]);
*arg = u.v;
}
Здравствуйте, eao197, Вы писали:
E>Угу. Только вот когда придется мегабайты данных преобразовывать из host byte order в network byte order разница в скорости двух простых решений может оказаться слишком существенной. Так что результаты замеров были бы очень интересны.
#include <stdio.h>
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
void swap_words1(uint32_t* arg)
{
uint16_t* sp = (uint16_t*)arg;
uint16_t hi = sp[0];
uint16_t lo = sp[1];
sp[1] = hi;
sp[0] = lo;
}
void swap_words2(uint32_t* arg)
{
uint16_t hi = (uint16_t) ((*arg & 0xFFFF0000u)>>16);
uint16_t lo = (uint16_t) ((*arg & 0x0000FFFFu));
*arg = hi | (lo << 16);
}
void swap_words3(uint32_t* arg)
{
union { uint16_t sp[2]; uint32_t v; } u;
u.v = *arg;
uint16_t hi = u.sp[0];
uint16_t lo = u.sp[1];
u.sp[1] = hi;
u.sp[0] = lo;
*arg = u.v;
}
int main()
{
uint32_t v = 0xFFFF0000u;
int i = 0;
for ( i = 0; i != 10000000; ++i )
swap_words1(&v);
for ( i = 0; i != 10000000; ++i )
swap_words2(&v);
for ( i = 0; i != 10000000; ++i )
swap_words3(&v);
printf("%08X\n", v);
return 0;
}
gcc
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
54.17 0.26 0.26 10000000 26.00 26.00 swap_words3
25.00 0.38 0.12 10000000 12.00 12.00 swap_words1
10.42 0.43 0.05 10000000 5.00 5.00 swap_words2
10.42 0.48 0.05 main
gcc -O1
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
41.67 0.05 0.05 10000000 5.00 5.00 swap_words2
25.00 0.08 0.03 10000000 3.00 3.00 swap_words3
16.67 0.10 0.02 10000000 2.00 2.00 swap_words1
16.67 0.12 0.02 main
gcc -O2
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ns/call ns/call name
46.15 0.12 0.12 main
19.23 0.17 0.05 10000000 5.00 5.00 swap_words2
19.23 0.22 0.05 10000000 5.00 5.00 swap_words3
15.38 0.26 0.04 10000000 4.00 4.00 swap_words1
Здравствуйте, Alexander Pazdnikov, Вы писали:
AP>Здравствуйте, Smal, Вы писали:
AP>Кто-нибудь!!! AP>Т.е. получается, что sp будет указывать по непонятному адресу? Т.е. реально вообще получить Segmentation Fault (при sp[0] = hi)?!!! AP>Правильно ли я понял?
Можно получить все что угодно, ибо UB.
В реальности я трейсил адреса. arg и sp совпадают.
Правда при трейсах меняется результат, ибо это гейзенбаг.
Что там внутри происходит, я не смотрел. Надо дизассемблировать и смотреть — лень.
Да и какая разница. А т.к. UB, то можно получить и SF .
А можно увидеть ассемблерный листинг этого чуда? Хочется понять почему для 2-х байтового значения потребовалось более другое выравнивание, чем для 4-х байтового.
Здравствуйте, Alexander Pazdnikov, Вы писали:
AP>Т.е. получается, что sp будет указывать по непонятному адресу? Т.е. реально вообще получить Segmentation Fault (при sp[0] = hi)?!!! AP>Правильно ли я понял?
Нет. Не правильно.
sp будет указывать куда-то на стек. Но вот значение переменной v2 (кажется так её там звали?) может оказаться не на стеке, а в регистре, например, в этот момент.
Я думаю, что в приведённом коде происходит следующее. Функция подставляется, зависимость значения переменной v2 от манипуляций с sp компилятором не отслеживается благодаря обсуждаемому правилу, соответсвенно компилятор видит, что v2 инициализируется, потом от него берут адрес, не являющийся алиасом, потом печатают, и скорее всего просто печатает константу...
Можно код посмотреть, в принципе.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
А>А можно увидеть ассемблерный листинг этого чуда? Хочется понять почему для 2-х байтового значения потребовалось более другое выравнивание, чем для 4-х байтового.
я делал ассемблерные листинги обоих вариантов, скомпиленных GCC 4.1.x (linux)
интересно то что команды в дебажном и релизном варианте не отличаются вообще.
но в релизном некоторые переставлены местами (для выравнивания команд на границу слова?)
А я разве утверждал, что ваш вариант будет самым медленным?
Вот приведенные вами цифры показывают, что на битовых операциях не только переносимее, но и быстрее.
На VC++ 7.1 вариант с union вообще самым медленным оказался.
И кстати, VC++ 7.1 не компилирует вариант swap_words3 (а GCC компилирует), поскольку переменные в функции нужно объявлять до использования:
void swap_words3(uint32_t* arg)
{
union { unsigned short sp[2]; unsigned int v; } u;
unsigned short hi;
unsigned short lo;
u.v = *arg;
hi = u.sp[0];
lo = u.sp[1];
u.sp[1] = hi;
u.sp[0] = lo;
*arg = u.v;
}
SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Здравствуйте, eao197, Вы писали:
E>Здравствуйте, Smal, Вы писали:
S>>Комментарии?
E>А я разве утверждал, что ваш вариант будет самым медленным?
Просто замеры для таких "мелких" операций меня всегда напрягают.
Мне только один раз действительно пришлось ускорять некоторую элементарную
операцию — вычисление барицентрических координат. Но я делал это уже после того, как
исчерпал все оптимизации по алгоритму, прикрутил кэши и т.п.
E>Вот приведенные вами цифры показывают, что на битовых операциях не только переносимее, но и быстрее.
Только если без оптимизаций. Хотя, если выкинуть первый вариант как не работающий, то да. .
E>На VC++ 7.1 вариант с union вообще самым медленным оказался.
.
E>И кстати, VC++ 7.1 не компилирует вариант swap_words3 (а GCC компилирует), поскольку переменные в функции нужно объявлять до использования:
Это С++-ные привычки .
Здравствуйте, Андрей Коростелев, Вы писали:
АК>Правило введенное в С99 и запрещающее создание алиаса объекта с типом отличного от типа объекта-оригинала (ISO/IEC 9899 6.5/7) поддерживается в gcc 3.4.1 и выше на уровнях оптимизации начиная со 2-го.
Почему "введенное в С99"? Если Вы о тексте из 6.5/7, то он представляет собой просто другую редакцию ISO/IEC 9899:1990, 6.3:
An object shall have its stored value accessed only by an lvalue that has one of the following types: 36
— the declared type of the object,
— a qualified version of the declared type of the object,
— a type that is the signed or unsigned type corresponding to the declared type of the object,
— a type that is the signed or unsigned type corresponding to a qualified version of the declared type of the object,
— an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
— a character type.
36 The intent of this list is to specify those circumstances in which an object may or may not be aliased.
Здравствуйте, eao197, Вы писали:
E>Насколько я понимаю, в C++ стандарте такого ограничения нет. Тем не менее, GCC 3.4.4 для C++ генерирует такой же проблемный код, как и для C
В C++ все ровно то же самое.
5.17/8 If the value being stored in an object is accessed from another object that overlaps in any way the storage of the first object, then the overlap shall be exact and the two objects shall have the same type, otherwise the behavior is undefined.