Допустим, у меня есть указатель на 32-х битное целое, нужно получить в нём второй байт, какой код будет быстрее:
int* pInt = ....;
BYTE second = ((byte*)pInt)[1];
или
BYTE second = (*pInt & 0x00ff0000) >> 16;
// И что быстрее, если мне нужно разложить по байтам всё целое число?
BYTE first = ((byte*)pInt)[0];
BYTE second = ((byte*)pInt)[1];
BYTE third = ((byte*)pInt)[2];
BYTE fourth = ((byte*)pInt)[3];
или
BYTE first = (*pInt & 0xff000000) >> 24;
BYTE second = (*pInt & 0x00ff0000) >> 16;
BYTE third = (*pInt & 0x0000ff00) >> 8;
BYTE fourth = (*pInt & 0x000000ff);
Вроде ведь mov mov'у рознь, т.е. в зависимости от аргументов будут выполняться разные команды. Тут я вижу только что в первом случае код на ассемблере короче, что не гарантирует более быстрого выполнения.
Здравствуйте, _nn_, Вы писали:
__>Может сделать так : __>
__>union int_char
__>{
__> int i;
__> char c[4];
__>};
__>int_char* ic = pInt;
__>// Доступ
__>ic.c[0]
__>ic.c[1]
__>ic.c[2]
__>ic.c[3]
__>
Но сначала-то у меня указатель на int, т.е., придётся или копировать содержимое в эту структуру, или приводить указатели, что в общем я и делал, только с байтами. В итоге выйгрыш будет только в удобстве, но не в скорости.
Здравствуйте, WinterMute, Вы писали:
WM>Здравствуйте, _nn_, Вы писали:
__>>Может сделать так : __>>
__>>union int_char
__>>{
__>> int i;
__>> char c[4];
__>>};
__>>int_char* ic = pInt;
__>>// Доступ
__>>ic.c[0]
__>>ic.c[1]
__>>ic.c[2]
__>>ic.c[3]
__>>
WM>Но сначала-то у меня указатель на int, т.е., придётся или копировать содержимое в эту структуру, или приводить указатели, что в общем я и делал, только с байтами. В итоге выйгрыш будет только в удобстве, но не в скорости.
Здравствуйте, WinterMute, Вы писали:
WM>Допустим, у меня есть указатель на 32-х битное целое, нужно получить в нём второй байт, какой код будет быстрее:
WM>
WM>int* pInt = ....;
WM>BYTE second = ((byte*)pInt)[1];
WM>или
WM>BYTE second = (*pInt & 0x00ff0000) >> 16;
WM>// И что быстрее, если мне нужно разложить по байтам всё целое число?
WM>BYTE first = ((byte*)pInt)[0];
WM>BYTE second = ((byte*)pInt)[1];
WM>BYTE third = ((byte*)pInt)[2];
WM>BYTE fourth = ((byte*)pInt)[3];
WM>или
WM>BYTE first = (*pInt & 0xff000000) >> 24;
WM>BYTE second = (*pInt & 0x00ff0000) >> 16;
WM>BYTE third = (*pInt & 0x0000ff00) >> 8;
WM>BYTE fourth = (*pInt & 0x000000ff);
WM>
Второй вариант правильный. Первый будет работать только на некоторых платформах. Насчет скорости сказать ничего нельзя. Хотя при большом регистровом пространстве второй вариант может оказаться быстрее.
господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
JS>господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
Ш>Второй вариант правильный. Первый будет работать только на некоторых платформах.
Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
Кроме того не гарантируется, что размер типа int будет равен 4-м байтам.
Здравствуйте, JakeS, Вы писали:
JS>господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
Как правило интеловский, например, компилятор генерирует более качественный код. Так как учитывает особенности работы процессора.
Здравствуйте, Sir Wiz, Вы писали:
SW>Здравствуйте, JakeS, Вы писали:
JS>>господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
SW>Как правило интеловский, например, компилятор генерирует более качественный код. Так как учитывает особенности работы процессора.
В данном случае никакие особенности процессора ни при чем. речь идет всего лишь о доступе к участкам памяти. Очевидно что быстрее всего единственная операция mov регистр, адрес и в дальнейшем работа только с регистрами. Причем совершенно без разницы какой процессор.
mov eax, addr
mov ebx
а затем получение и сразу же использование байтиков, типа:
mov eax, ebx
shr eax, 16
and eax, FFh
но ни один компиллятор такого не сделает, ибо все зависит от конкретного случая, а компиллятору это до фонаря. Поэтому повторяю, если это действительно критично — inline asm. Иначе не стоит парится о таких мелочах а оптимизировать чтонибудь другое.
Ш>>Второй вариант правильный. Первый будет работать только на некоторых платформах.
А почему, кстати?
SW>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
В приведенном выше коде — surprise! — никогда не будет правого сдвига отрицательных чисел.
SW>Кроме того не гарантируется, что размер типа int будет равен 4-м байтам.
Это да. Но тогда и задача разложить int на черыре байта выглядит как-то...
WM>>>BYTE first = (*pInt & 0xff000000) >> 24; WM>>>BYTE second = (*pInt & 0x00ff0000) >> 16; WM>>>BYTE third = (*pInt & 0x0000ff00) >> 8; WM>>>BYTE fourth = (*pInt & 0x000000ff); WM>>>[/ccode]
Ш>>Второй вариант правильный. Первый будет работать только на некоторых платформах.
SW>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
int я привёл для примера, у меня, конечно, unsigned int
SW>Кроме того не гарантируется, что размер типа int будет равен 4-м байтам.
Меня интересовало как будет быстрее, все попутные проблемы в общем не суть.
Здравствуйте, elcste, Вы писали:
SW>>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
E>В приведенном выше коде — surprise! — никогда не будет правого сдвига отрицательных чисел.
(*pInt & 0xff000000) >> 24;
Не будет, говорите? Это для x86. Borland и VC сделают тут две разные вещи.
А ещё есть вариации с low/big endian... Я не нашел в стандарте ни слова о требованиях к побитному расположению в памяти интегральных типов.
Здравствуйте, JakeS, Вы писали:
JS>>>господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
SW>>Как правило интеловский, например, компилятор генерирует более качественный код. Так как учитывает особенности работы процессора.
JS>В данном случае никакие особенности процессора ни при чем. речь идет всего лишь о доступе к участкам памяти. Очевидно что быстрее всего единственная операция mov регистр, адрес и в дальнейшем работа только с регистрами. Причем совершенно без разницы какой процессор.
Не претендую на профессианлизм в этом вопросе, но как же конвейерная оптимизация?
Опыт показал что способ с преобразованием к указателю на байт вроде как быстрее (примерно на 20-30%%). Хотя подозреваю что результат может измениться при использовании разных компиляторов и оптимизаций под отдельные процессоры. (Я компилировал на штатном VC с включением оптимизации по скорости).
Здравствуйте, Sir Wiz, Вы писали:
SW>>>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
E>>В приведенном выше коде — surprise! — никогда не будет правого сдвига отрицательных чисел.
SW>(*pInt & 0xff000000) >> 24;
SW>Не будет, говорите? Это для x86.
Никогда — это значит никогда. В том числе для x86, конечно.
SW>Borland и VC сделают тут две разные вещи.
Что ж, если какой-то компилятор работает неправильно (что, правда, еще надо показать), стоит отправить bug report.
SW>А ещё есть вариации с low/big endian... Я не нашел в стандарте ни слова о требованиях к побитному расположению в памяти интегральных типов.
Стандарт, по большей части, внутреннее представление типов не регламентирует. А при чем тут это?
Здравствуйте, Sir Wiz, Вы писали:
Ш>>>>Второй вариант правильный. Первый будет работать только на некоторых платформах. E>>А почему, кстати?
SW>Например, отсутствие физической возможности обратиться к адресу не кратному 2^n, где n > 0.
Гм-гм... Вы, как и я, предполагаете, что типы BYTE и byte в примерах WinterMute — это синонимы unsigned char?
Здравствуйте, elcste, Вы писали:
SW>>>>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора.
E>>>В приведенном выше коде — surprise! — никогда не будет правого сдвига отрицательных чисел. E>
SW>>(*pInt & 0xff000000) >> 24;
SW>>Не будет, говорите? Это для x86.
E>Никогда — это значит никогда. В том числе для x86, конечно.
Извольте, вот пояснение с картинками
int * pInt = new int;
*pInt = -1;
*pInt = *pInt & 0xFF000000; // *pInt == -16777216
*pInt >> 24; // Правый сдвиг отрицательного числа.
SW>>Borland и VC сделают тут две разные вещи. E>Что ж, если какой-то компилятор работает неправильно (что, правда, еще надо показать), стоит отправить bug report.
Правильно работает. В соответствии со стандартом делает что хочет. В частности, VC сохраняет знак, Borland — нет.
SW>>А ещё есть вариации с low/big endian... Я не нашел в стандарте ни слова о требованиях к побитному расположению в памяти интегральных типов.
E>Стандарт, по большей части, внутреннее представление типов не регламентирует. А при чем тут это?
К тому, что побитные операции в принципе не переносимы. Поправьте, если это не так.
Отгадка: Подвыражение *pInt имеет тип int, тогда как подвыражение *pInt & 0xff000000 в 32-разрядных компиляторах под x86 имеет тип unsigned int.
Литература: Читать про integer literals, integral promotions и usual arithmetic conversions.
SW>К тому, что побитные операции в принципе не переносимы. Поправьте, если это не так.
Побитовые операции над беззнаковыми типами в принципе переносимы. Побитовые операции над знаковыми типами имеют ряд опасных особенностей (которые, кстати, лучше описаны в стандарте C99, нежели C++).
Здравствуйте, Sir Wiz, Вы писали:
E>>Гм-гм... Вы, как и я, предполагаете, что типы BYTE и byte в примерах WinterMute — это синонимы unsigned char?
SW> Да, точно подмечено. Не знаю, честно говоря. Но, видимо, да.
То есть вопрос о выравнивании исчерпан. Правильно я понимаю?
Здравствуйте, elcste, Вы писали:
E>Отгадка: Подвыражение *pInt имеет тип int, тогда как подвыражение *pInt & 0xff000000 в 32-разрядных компиляторах под x86 имеет тип unsigned int.
Здравствуйте, Sir Wiz, Вы писали:
SW>Здравствуйте, JakeS, Вы писали:
JS>>>>господа, елси уж для вашей программы существенна разница в скорости выполнения команды mov — Пишите нужный участок в чистом ассемблере, однозначно будет быстрее.
SW>>>Как правило интеловский, например, компилятор генерирует более качественный код. Так как учитывает особенности работы процессора.
JS>>В данном случае никакие особенности процессора ни при чем. речь идет всего лишь о доступе к участкам памяти. Очевидно что быстрее всего единственная операция mov регистр, адрес и в дальнейшем работа только с регистрами. Причем совершенно без разницы какой процессор.
SW>Не претендую на профессианлизм в этом вопросе, но как же конвейерная оптимизация?
Конвейерная оптимизация — это то, поведение чего никто не гарантирует и, кроме создателей, предсказать не может. Короче как морская свинка и не свинка, и не морская, но работает быстрее.
Ш>>Второй вариант правильный. Первый будет работать только на некоторых платформах.
SW>Нет, второй вариант тоже неправильный. Правый сдвиг отрицательных чисел определяется реализацией компилятора. SW>Кроме того не гарантируется, что размер типа int будет равен 4-м байтам.
Как сказал автор, int 32 разрядный. Сколько он при этом занимает на самом деле байт -- не имеет значения.
Сдвигов отрицательных чисел в этом примере не будет. Потому что константе 0xFF000000, например, на машине, где int 32 разрядный, будет назначен тип unsigned int.
Здравствуйте, JakeS, Вы писали:
SW>>Не претендую на профессианлизм в этом вопросе, но как же конвейерная оптимизация?
JS>Конвейерная оптимизация — это то, поведение чего никто не гарантирует и, кроме создателей, предсказать не может. Короче как морская свинка и не свинка, и не морская, но работает быстрее.
AFAIK, поведение конвейеров описано в интеловской документации.
И в компиляторе Intel реализована оптимизация именно под их процессоры, учитывающая работу конвейеров, HT, и времени выполнения команд.
Пожалуй единственное, известное мне место, где приходится писать на ассемблере — MMX/SSE/SSE2/SSE3. Например при умножении матриц компилятор не всегда может сам векторизовать.