2016-04-30 6 views
1

x32 ABI указывает, среди прочего, 32-разрядные указатели для кода, сгенерированного для архитектуры x86_64. Он сочетает в себе преимущества архитектуры x86_64 (включая 64-разрядные регистры процессора) с уменьшенными накладными расходами 32-разрядных указателей.Как должны быть определены типы [u] int_fastN_t для x86_64 с или без x32 ABI?

Заголовок <stdint.h> определяет определений типов int_fast8_t, int_fast16_t, int_fast32_t и int_fast64_t (и соответствующих беззнаковых типов и др), каждая из которых является:

целочисленный тип, который, как правило, быстро оперировать среди всех целые типы, которые имеют по меньшей мере заданную ширину

со сноской:

Назначенный тип не гарантируется как самый быстрый для всех целей; , если в реализации нет четких оснований для выбора одного типа по сравнению с другим, он просто выберет целочисленный тип, удовлетворяющий требованиям подписи и ширины .

(Цитируется N1570 C11 draft.)

Вопрос заключается в том, каким образом следует [u]int_fast16_t и [u]int_fast32_t типы определены для архитектуры x86_64, с или без x32 ABI? Есть ли документ x32, который указывает эти типы? Должны ли они быть совместимы с 32-разрядными определениями x86 (оба 32 бита) или, поскольку x32 имеет доступ к 64-разрядным регистрам CPU, должны ли они быть того же размера с или без x32 ABI? (Обратите внимание, что x86_64 имеет 64-битовые регистры, независимо от того, является ли x32 ABI находится в использовании или нет.)

Вот тестовая программа (которая зависит от GCC конкретных __x86_64__ макро):

#include <stdio.h> 
#include <stdint.h> 
#include <limits.h> 

int main(void) { 
#if defined __x86_64__ && SIZE_MAX == 0xFFFFFFFF 
    puts("This is x86_64 with the x32 ABI"); 
#elif defined __x86_64__ && SIZE_MAX > 0xFFFFFFFF 
    puts("This is x86_64 without the x32 ABI"); 
#else 
    puts("This is not x86_64"); 
#endif 
    printf("uint_fast8_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast8_t)); 
    printf("uint_fast16_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast16_t)); 
    printf("uint_fast32_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast32_t)); 
    printf("uint_fast64_t is %2zu bits\n", CHAR_BIT * sizeof (uint_fast64_t)); 
} 

Когда я скомпилировать его с gcc -m64, выход:

This is x86_64 without the x32 ABI 
uint_fast8_t is 8 bits 
uint_fast16_t is 64 bits 
uint_fast32_t is 64 bits 
uint_fast64_t is 64 bits 

Когда я скомпилировать его с gcc -mx32, выход:

This is x86_64 with the x32 ABI 
uint_fast8_t is 8 bits 
uint_fast16_t is 32 bits 
uint_fast32_t is 32 bits 
uint_fast64_t is 64 bits 

(который, помимо первой строки, соответствует выходному сигналу gcc -m32, который генерирует 32-разрядный код x86).

Является ли это ошибкой в ​​glibc (который определяет заголовок <stdint.h>), или он соответствует некоторым требованиям x32 ABI? Нет ссылок на типы [u]int_fastN_t в x32 ABI document или x86_64 ABI document, но может быть что-то другое, что его определяет.

Можно утверждать, что типы fast16 и fast32 должны быть 64 бит с или с x32, поскольку доступны 64-разрядные регистры; было бы более разумно, что нынешнее поведение?

(Я существенно отредактировал исходный вопрос, который задал вопрос только о x32 ABI. Теперь задается вопрос о x86_64 с или без x32.)

+0

Зачем это ошибка в glibc? –

+0

@ RossRidge: Если ваша точка в том, что '' предоставляется glibc, а не gcc, вы правы; Я обновил вопрос. Если вы говорите, что это не ошибка, меня бы заинтересовало ваше обоснование. Поскольку система имеет 64-разрядные регистры, 'int64_t' должен быть быстрее, чем' int32_t', поэтому 'int_fast32_t' должно быть 64 бита, как и в x86_64. –

+0

Мне интересно сначала услышать ваше разумное. Почему бы 64-разрядные регистры делать 'int64_t' быстрее, чем' int32_t' при работе со значениями, которым требуется только 32 бита? –

ответ

-3

Жесткий. Давайте просто возьмем int_fast8_t. Если разработчик использует большой массив для хранения большого количества 8-разрядных целых чисел, то int8_t будет быстрее из-за кэширования. Я бы сказал, что использование больших массивов int_fast8_t, вероятно, является плохим.

Вам нужно будет взять большую базу кода и систематически заменить int8_t и подписанные символы и простой символ, если он подписан с помощью int_fast8_t. Затем сравните код с использованием разных typedef для int_fast8_t и определите, что самое быстрое.

Обратите внимание, что неопределенное поведение изменится. Например, присвоение 255 даст результат -1, если тип int8_t и 255 в противном случае.

+0

Я не вижу, как это отвечает на вопрос. –

+0

'int_fast8_t' и' uint_fast8_t' - 8 бит под gcc; Я спрашивал о '[u] int16_t' и' [u] int32_t'. –

+0

Мне также интересно, что вы имели в виду под названием «Tough». –

0

Я составил следующий код, чтобы проверить сгенерированный код для простой суммы с различными целыми типами:

#include <stdint.h> 

typedef int16_t INT; 
//typedef int32_t INT; 
//typedef int64_t INT; 

INT foo() 
{ 
    volatile INT a = 1, b = 2; 
    return a + b; 
} 

А потом разобрал код, сгенерированный с каждым из целочисленных типов. Команда компиляции - gcc -Ofast -mx32 -c test.c. Обратите внимание, что в полном 64-битном режиме сгенерированный код будет почти таким же, потому что в моем коде нет указателей (только %rsp вместо %esp).

С int16_t он излучает:

00000000 <foo>: 
    0: b8 01 00 00 00   mov $0x1,%eax 
    5: ba 02 00 00 00   mov $0x2,%edx 
    a: 67 66 89 44 24 fc  mov %ax,-0x4(%esp) 
    10: 67 66 89 54 24 fe  mov %dx,-0x2(%esp) 
    16: 67 0f b7 54 24 fc  movzwl -0x4(%esp),%edx 
    1c: 67 0f b7 44 24 fe  movzwl -0x2(%esp),%eax 
    22: 01 d0     add %edx,%eax 
    24: c3      retq 

С int32_t:

00000000 <foo>: 
    0: 67 c7 44 24 f8 01 00 00 00 movl $0x1,-0x8(%esp) 
    9: 67 c7 44 24 fc 02 00 00 00 movl $0x2,-0x4(%esp) 
    12: 67 8b 54 24 f8    mov -0x8(%esp),%edx 
    17: 67 8b 44 24 fc    mov -0x4(%esp),%eax 
    1c: 01 d0      add %edx,%eax 
    1e: c3       retq 

И int64_t:

00000000 <foo>: 
    0: 67 48 c7 44 24 f0 01 00 00 00 movq $0x1,-0x10(%esp) 
    a: 67 48 c7 44 24 f8 02 00 00 00 movq $0x2,-0x8(%esp) 
    14: 67 48 8b 54 24 f0    mov -0x10(%esp),%rdx 
    1a: 67 48 8b 44 24 f8    mov -0x8(%esp),%rax 
    20: 48 01 d0      add %rdx,%rax 
    23: c3        retq 

Теперь, я не утверждаю, что знаю точно, почему компилятор генерируется именно этот код (возможно,Ключевое слово, объединенное с целым типом не регистрационного размера, не лучший выбор?). Но из этого сгенерированного кода мы можем сделать следующие выводы:

  1. Самый медленный тип - int16_t. Для перемещения значений требуется дополнительные инструкции.
  2. Самый быстрый тип: int32_t. Хотя 32-разрядная и 64-разрядная версии имеют одинаковое количество инструкций, 32-разрядный код короче в байтах, поэтому он будет более дружественным к кэшу, поэтому быстрее.

Так естественный выбор для быстрых типов будет:

  1. Для int_fast16_t, выберите int32_t.
  2. Для int_fast32_t, выберите int32_t.
  3. Для int_fast64_t, выберите int64_t (что еще).
+0

Вопрос относится только к x32 ABI, но теперь я думаю, что вопрос о правильных размерах типов '[u] int_fastN_t' для x86_64 * с и без * x32 сделает для лучшего вопроса. Поскольку вы опубликовали ответ на вопрос, поскольку он стоит сейчас, было бы несправедливо изменить его. Не могли бы вы возразить, если бы я сделал такое изменение? –

+0

@KeithThompson: Я не понимаю, что вы имеете в виду с _x86_64 с и без x32_. x86_64 и x32 - разные архитектуры, хотя оба они работают в 64-разрядных процессорах. Но в любом случае, я ожидал бы, что целые типы будут одинаковыми, так как только размер указателя отличается. – rodrigo

+0

@KeithThompson: Ну, я только что проверил, и я ошибся. Эти три быстрых целых числа - все 64-битные длины в x86_64, отличные от x32. Может быть, разница связана с выравниванием памяти по умолчанию? Но я боюсь, что мой ответ, как есть, не относится к вопросу x86_64. – rodrigo

0

Вообще говоря, вы ожидаете, что 32-разрядные целочисленные типы будут быть немного быстрее, чем 64-разрядные целые типы на процессорах x86-64. Отчасти потому, что они используют меньше памяти, но также потому, что для 64-битных инструкций требуется дополнительный префиксный байт над их 32-разрядными аналогами. 32-разрядная инструкция разделения значительно быстрее, чем 64-разрядная, но в остальном время выполнения команд одинаково.

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

Несоответствие между размерами int_fast32_t типов при использовании ABI x32 и x86-64, вероятно, наилучшим образом оправдано тем фактом, что указатели имеют ширину 64 бит. Всякий раз, когда 32-битовое целое добавляется к указателю, его необходимо расширить, что делает его гораздо более вероятным при использовании x86-64 ABI.

Еще один фактор, который следует учитывать, заключается в том, что вся точка x32 ABI должна быть лучше, если использовать меньшие типы. Любое приложение, которое выигрывает от указателей и связанных с ними типов, является меньшим, также должно быть полезно также уменьшить размер int_fast32_t.

+0

Вопрос относится только к x32 ABI, но теперь я думаю, что вопрос о правильных размерах типов '[u] int_fastN_t' для x86_64 * с и без * x32 сделает для лучшего вопроса. Поскольку вы опубликовали ответ на вопрос, поскольку он стоит сейчас, было бы несправедливо изменить его. Не могли бы вы возразить, если бы я сделал такое изменение? –

+0

@KeithThompson Конечно, звучит не похоже, что это изменит мой ответ. –

+0

Спасибо, вопрос обновлен. –