Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > аппаратная проверка выравненных данных


Автор: null56 27.3.2014, 13:03
Всем привет

Процессор x86/x86_64

Задачи:
1) понять что такое проверка на выравненные данные
2) понять почему срабатывает проверка в конкретном примере
3) понять почему не срабатывает проверка в другом примере
4) понять зачем вообще эта проверка нужна? то есть был введен флаг в регистре СЛОВА (бит 18)
http://ru.wikipedia.org/wiki/%D0%A0%D0%B5%D0%B3%D0%B8%D1%81%D1%82%D1%80_%D1%84%D0%BB%D0%B0%D0%B3%D0%BE%D0%B2

И так
Вопрос 1: правильно ли я понимаю понятие "проверка на выравнивание"?
проверка на выравнивание - это ошибка в том случае, когда идет ДОСТУП к данным в оперативной памяти не по кратному машинному слову адресу (4 - для x86, 8 - для )
или
это связано с размером данных и адресом? скажем, если читаем 2 байта, то адрес должен быть кратен 2м, 4 - 4м?
Код

....
// ошибка, невыровненный доступ
char * ch = malloc(64);
int *i = (int *)(a + 2);
*i = 789;

....


И так пример: компилятор gcc 64 разрядный, поэтому буду использовать опцию -m32, чтобы сделать тесты 32разрядных приложений

Для незнающих gnu assembler: вставка 1) сохраняет в стек регистр слова, 2) изменяет в этом значении 18 бит, 3) загружает слово обратно в регистр. Код проверенных в дебагере, ошибок нет

32 бита: сборка с флагом -m32
Код

int main()
{
        asm("pushfl; "
        "orl $(1<<18), (%esp); "
        "popfl;");

        char d[] = "12345678";

        return 0;
}

Результат: "Ошибка шины"
Вопрос 2: если я правильно понимаю понятие "проверка на выравнивание", то тогда не понимаю, почему в данном примере идет какая - то проверка? ведь я даже к памяти не обратился?

64 бита (в ассемблерной вставке используется расширенный регистр указателя стека rsp, а так та же суть)
Код

int main()
{
        asm("pushf; "
        "orl $(1<<18), (%rsp); "
        "popf;");

        char a[] = "12345678";
//        char a[] = "1234567812345678";
       
        return 0;
}

Результат: ошибка добиться не получилось

Вопрос 3: как добиться аналогичной x86 ошибки на 64 битах?

ну и на конец
Вопрос 4: для чего нужна эта проверка?
в соседних темах были разговоры о более быстром доступе, размещение в кеше еще чего - то... Но, вопрос именно ЗАЧЕМ нужна проверка? где ее можно использовать?

Спасибо всем отклинувшимся за помощь

Автор: null56 27.3.2014, 13:32
вопрос 1, наверное можно снять, нашел правила выравнивания тут
http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html
иными словами, правильнее будет считать, что начальный адрес должен быть кратен размеру данных

Автор: bsa 31.3.2014, 18:22
Цитата(null56 @  27.3.2014,  14:03 Найти цитируемый пост)
Вопрос 4: для чего нужна эта проверка?
в соседних темах были разговоры о более быстром доступе, размещение в кеше еще чего - то... Но, вопрос именно ЗАЧЕМ нужна проверка? где ее можно использовать?
Некоторые процессоры просто требуют выровненного доступа. Т.е. если ты пытаешься прочитать 4 байта по адресу не кратному 4, то:
1. возникнет аппаратное исключение
2. будет прочитано хрен знает что (скорее всего, будет прочитано 4 байта с адреса address & ~(4 - 1))
Чтобы это не происходило компилятор выравнивает весь доступ. Если это невозможно, то читает по 1 байту. И вместо одной инструкции для чтения 32-х битного слова ты получаешь минимум 4 штуки.

Автор: null56 1.4.2014, 15:07
bsa, да про исключение я знаю, у нас на blackfin как раз падает всё
тут ключевое слово "некоторые процессоры".
так вот вопрос: зачем на процах(такие как x86), которые умеют читать по невыровненным адресам, этот флажок? для отладки? чтобы переносить на другие процы  что ли?

Автор: Alexeis 1.4.2014, 15:36
Цитата(null56 @  1.4.2014,  16:07 Найти цитируемый пост)
так вот вопрос: зачем на процах(такие как x86), которые умеют читать по невыровненным адресам, этот флажок? для отладки? чтобы переносить на другие процы  что ли? 

  Производительность разная. Данные выровненные на границу 4/8ми байт быстрее адресуются, чем выравненные на границу 1го байта. 

Автор: vinter 3.4.2014, 09:13
null56, посмотри первую часть http://scrutator.me/post/2014/01/30/objects_memory_layout_p1.aspx

Автор: null56 4.4.2014, 12:45
vinter, спасибо за статью, очень наглядно, хотя урывки я вроде где  - то уже читал, вроде бы в мейерсе, про тривиальные классы


Alexeis, 
vinter, 
а теперь прочитаем внимательнее вопрос... может я его не верно задал?
Цитата

зачем на процах(такие как x86), которые умеют читать по невыровненным адресам, этот флажок? для отладки? чтобы переносить на другие процы  что ли? 

вы мне сказали верно, "выравнивание - это ... и она для того - то..."

но! вопрос связан с назначением 18 бита в слове процессора? по умолчанию он выключен на х86, если включаю, то включается и проверка.... вопрос: зачем надо включать этот флаг??? зачем он вообще присутствует в процессоре данной архитектуры?

Автор: k0rvin 4.4.2014, 14:21
Цитата(Alexeis @  1.4.2014,  15:36 Найти цитируемый пост)
Производительность разная. Данные выровненные на границу 4/8ми байт быстрее адресуются, чем выравненные на границу 1го байта.  

Т.е. работа с массивом char менее эффективна, чем с массивом int?

Автор: vinter 4.4.2014, 15:28
Цитата(k0rvin @  4.4.2014,  15:21 Найти цитируемый пост)
Т.е. работа с массивом char менее эффективна, чем с массивом int?

нельзя этого утверждать, как нельзя утверждать и обратное. Всё нужно измерять.

Автор: k0rvin 5.4.2014, 05:56
Ладно, фиг с ней, с эффективностью. Есть такой код:
Код

#include <u.h>
#include <libc.h>

typedef int       Value;
typedef Value   **Matrix;
typedef uvlong    Size;

Matrix  Mnew(Size, Size);
void    Mfree(Matrix);
Size    Mrows(Matrix);
Size    Mcolumns(Matrix);

void    minit(Matrix);
void    mprint(Matrix);

void    usage(char*);

void
main(int argc, char *argv[])
{
    Size m, n;
    Matrix a;

    if (argc != 3) {
        usage(argv[0]);
        exits("badarg");
    }
    m = atoi(argv[1]);
    n = atoi(argv[2]);
    if (m < 2 || n < 2) {
        usage(argv[0]);
        exits("badarg");
    }
    a = Mnew(m, n);
    if (a == nil) {
        fprint(2, "can't allocate enough memory: %r");
        exits("Mnew");
    }

    minit(a);
    mprint(a);

    Mfree(a);
    exits(0);
}

void
usage(char *app)
{
    fprint(2, "usage: %s <rows count> <columns count>\n", app);
    fprint(2, "\nrows count    : integer greater than 2\n");
    fprint(2, "\ncolumns count : integer greater than 2\n");
}

Matrix
Mnew(Size m, Size n)
{
    Size *block, i;
    Matrix a;
    Value *r;

    block = malloc(
        2 * sizeof(Size) + 
        m * sizeof(Value *) + 
        m * n * sizeof(Value)
    );
    if (block == nil)
        return nil;
    block[0] = m;
    block[1] = n;
    a = (Matrix) (block + 2);
    for (i = 0, r = (Value *) (a + m); i < m; i++, r += n)
        a[i] = r;
    return a;
}

Size *
_mbbegin(Matrix a, int shift)
{
    return ((Size *) a) + shift;
}

void
Mfree(Matrix a)
{
    free(_mbbegin(a, -2));
}
 
Size
Mrows(Matrix a)
{
    return *(_mbbegin(a, -2));
}
 
Size
Mcolumns(Matrix a)
{
    return *(_mbbegin(a, -1));
}

void
minit(Matrix a)
{
    Size i, j, m, n;
    m = Mrows(a);
    n = Mcolumns(a);

    for (i = 0; i < m; i++)
        for (j = 0; j < n; j++)
            a[i][j] = i * n + j;
}

void
mprint(Matrix a)
{
    Size i, j, m, n;
    m = Mrows(a);
    n = Mcolumns(a);

    for (i = 0; i < m; i++) {
        for (j = 0; j < n; j++)
            print(" %4d ", a[i][j]);
        print("\n");
    }
}


Один знакомый сказал, что такой способ представления массива «опасен при кроссплатформенности — у разных аппаратных платформ разные требования к выравниванию. Необходимо принять меры в виде пустого куска между указателями и данными», но безопасного варианта он пока не предоставил. Может вы что подскажете?

Автор: vinter 5.4.2014, 08:50
k0rvin, да, твой способ хранения явно не лучший, в плане выравнивания. Во первых тебе нужно http://stackoverflow.com/questions/3839922/aligned-malloc-in-gcc по Size. Потом тебе уже надо ручками подсчитать, где после Size должны располагаться Value* и это будет зависеть от размера указателя на платформе. Псле чего тебе нужно посчитать, опять руками, где можно начать располагать Value. На мой взгляд оно того не стоит smile

Автор: Alexeis 5.4.2014, 11:16
Цитата(vinter @  4.4.2014,  16:28 Найти цитируемый пост)
нельзя этого утверждать, как нельзя утверждать и обратное. Всё нужно измерять.

  Достаточно посмотреть ассемблерный код. Но на самом деле речь об ином. Когда компилятор вставляет код записи в не выровненные поля то добавляются лишние инструкции. Массив байтов считается выравненным. Даже если поставить режим выравнивания на 8 или 16 байтов, все равно в массиве байты будут идти один за другим. Но тем не менее код записи в char длиннее чем в int размера в регистр.
  
Цитата(null56 @  4.4.2014,  13:45 Найти цитируемый пост)
но! вопрос связан с назначением 18 бита в слове процессора? по умолчанию он выключен на х86, если включаю, то включается и проверка.... вопрос: зачем надо включать этот флаг??? зачем он вообще присутствует в процессоре данной архитектуры? 

  Могу предположить, что флаг связан с риск архитектурой 
Цитата(http://ru.wikipedia.org/wiki/RISC)

Наиболее широко используемые в настольных компьютерах процессоры архитектуры x86 ранее являлись CISC-процессорами, однако новые процессоры, начиная с Intel 486DX, являются CISC-процессорами с RISC-ядром[источник не указан 1169 дней]. Они непосредственно перед исполнением преобразуют CISC-инструкции x86-процессоров в более простой набор внутренних инструкций RISC.
 По крайней мере ARM процессоры, которые чисто RISC генерят исключение при попытке адресовать по не выровненному адресу. Кроме того, этот флаг можно использовать для раннего обнаружения ошибок работы с памятью, ведь даже компиляторы для x86 не используют не выравненные адреса. 

Автор: vinter 5.4.2014, 12:40
Цитата(Alexeis @  5.4.2014,  12:16 Найти цитируемый пост)
Достаточно посмотреть ассемблерный код

нет не достаточно. Ты измерял выровненный и не выровненный код? Я вот измерял и мне пришлось изрядно потрудиться, чтобы написать код при котором будет хотя бы 5% разница на моём core i7.

Цитата(Alexeis @  5.4.2014,  12:16 Найти цитируемый пост)
  Могу предположить, что флаг связан с риск архитектурой 

x86 не является RISC, а то, что там внутри наружу выдавать никто не будет, на мой взгляд

Автор: k0rvin 6.4.2014, 09:20
Цитата(vinter @  5.4.2014,  08:50 Найти цитируемый пост)
Во-первых, тебе нужно выделять память выровненную по Size

Зачем?

Цитата(vinter @  5.4.2014,  08:50 Найти цитируемый пост)
Потом тебе уже надо ручками подсчитать, где после Size должны располагаться Value*

А «2*sizeof(Size)» по-твоему зачем? Впрочем тут все относительно просто, как мне предложили
Код

#include <stddef.h>

struct S
{
    Size s[2];
    Matrix m;
    ...
}

offsetof(struct S, m);

Но размер массива m не известен в compile-time, поэтому я не могу узнать, сколько он на самом деле займет памяти с учетом выравнивания.


Цитата(vinter @  5.4.2014,  08:50 Найти цитируемый пост)
Псле чего тебе нужно посчитать, опять руками, где можно начать располагать Value. На мой взгляд оно того не стоит

«m*sizeof(Value *)» чем не устраивает?

Т.е. собвственно проблема в том, чтобы узнать, какое выравнивание использует текущая среда (компилятор+ОС+железо).

Автор: vinter 6.4.2014, 09:59
Цитата(k0rvin @  6.4.2014,  10:20 Найти цитируемый пост)
Зачем?

затем, что если ты задумался о выравнивании, то нужно чтобы первый объект твоих данных находился по выровненному адресу. В твоём коде этого нет.

Цитата(k0rvin @  6.4.2014,  10:20 Найти цитируемый пост)
А «2*sizeof(Size)» по-твоему зачем?

понятия не имею, но к выравниванию это не имеет никакого отношения.  К примеру, Size Равный 5 байт. 2*5 = 10. mallco выделяет память по адресу 3. Значит первый Size находится по адресу 3, второй по адресу 8. Указатель по адресу 13. В результате мы имеем чёрти-что. Понятно?

Цитата(k0rvin @  6.4.2014,  10:20 Найти цитируемый пост)
«m*sizeof(Value *)» чем не устраивает?

тоже самое.


Цитата(k0rvin @  6.4.2014,  10:20 Найти цитируемый пост)
Впрочем тут все относительно просто, как мне предложили

Цитата(k0rvin @  6.4.2014,  10:20 Найти цитируемый пост)
о размер массива m не известен в compile-time,

так просто или непонятно как сделать?

Цитата

 malloc(
        2 * sizeof(Size) + 
        m * sizeof(Value *) + 
        m * n * sizeof(Value)

я понимаю твою запись как: выделить память под 2 Size, m указателей на Value и m x n объектов типа Value. Если всё так, то я не вижу просто го способа решения этой проблемы - нужно много считать и не ошибиться при этом

Автор: k0rvin 6.4.2014, 10:58
Цитата(vinter @  6.4.2014,  09:59 Найти цитируемый пост)
затем, что если ты задумался о выравнивании, то нужно чтобы первый объект твоих данных находился по выровненному адресу. В твоём коде этого нет.

Т.е. malloc может выдать невыровненный адрес?


Цитата(vinter @  6.4.2014,  09:59 Найти цитируемый пост)
я понимаю твою запись как: выделить память под 2 Size, m указателей на Value и m x n объектов типа Value. Если всё так, то я не вижу простого способа решения этой проблемы - нужно много считать и не ошибиться при этом

По сути в этом и есть вопрос: как кроссплатформенно получить это самое значение выравнивания для расчетов.

Автор: vinter 6.4.2014, 11:32
Цитата(k0rvin @  6.4.2014,  11:58 Найти цитируемый пост)
Т.е. malloc может выдать невыровненный адрес?

не знаю, но если есть вопросы по-поводу aligned malloc, то, как-минимум ты не можешь контролировать по какой границе он выравнивает, если выравнивает. А тебе не просто нужно знать выровнен или нет адрес но и по какой границе, т.к. выравнивание твоего Size может не совпадать с тем, что даёт malloc(если он и выравнивает, то по границе кратной размеру указателя, я полагаю).

Цитата(k0rvin @  6.4.2014,  11:58 Найти цитируемый пост)
По сути в этом и есть вопрос: как кроссплатформенно получить это самое значение выравнивания для расчетов.

В C++ есть оператор alignof

Автор: k0rvin 6.4.2014, 13:11
Цитата(vinter @  6.4.2014,  11:32 Найти цитируемый пост)
В C++ есть оператор alignof 

Интересует чистый Си.

Автор: bsa 7.4.2014, 22:43
Цитата
       The malloc() and calloc() functions return a pointer to  the  allocated
       memory  that  is  suitably aligned for any kind of variable.  On error,
       these functions return NULL.  NULL may also be returned by a successful
       call  to  malloc() with a size of zero, or by a successful call to cal‐
       loc() with nmemb or size equal to zero.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)