[C++] есть ли смысл в выравнивании на Intel ?

Maurog

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

struct A
{
std::string a;
char b;
std::vector<std::string> c;
int d;
double e;
}

и я ее "криво" размещаю в памяти (нарушая правило выравнивания)
с какими реальными проблемами я могу столкнуться? это интересно как с точки зрения корректности работы программы, так и с точки зрения перформанса. если такие случаи есть, то хотелось бы их воспроизвести
опытным путем было выяснено, что чтение int работает с одинаковой скоростью независимо от его выравнивания, однако проседание скорости чтения было отмечено при размазывании int по двум кеш-линиям. В связи с этим накладываю еще одно ограничение : пусть моя структура лежит в одной кеш-линии и плохо выровнена. Какие проблемы тем не менее возможны?

salamander

Меня не покидает ощущение, что ты хочешь чего-то странного. Обычно программисту на C++ нет дела до выравниваний и этим занимается компилятор. В редких случаях бывают полезны packed структуры, которые тоже одним атрибутом задаются, а дальше уже компилятор разбирается с корректностью.
Нагуглилось:
http://lemire.me/blog/archives/2012/05/31/data-alignment-for...
читать включая комментарии, в них едва ли не половина всего интересного.

vall

В связи с этим накладываю еще одно ограничение : пусть моя структура лежит в одной кеш-линии и плохо выровнена. Какие проблемы тем не менее возможны?
на новых x86 похоже никаких, но есть ещё странный atom который может чудить.
проблемы:
* дырки в структурах. неэффективное использование памяти и кэшей
* разный layout между i386 и x86_64
если выключить выравнивание
* не все архитектуры позволяют не выравненный доступ, компилятор начнёт городить плохой код или получишь SIGBUS
* производительность всё-же упадёт, хотя бы из-за удвоения cache misses в некоторых случаях
* load/store станут совсем неатомарными

Maurog

* производительность всё-же упадёт, хотя бы из-за удвоения cache misses в некоторых случаях
в каком случае упадет? можно примерчик описать? напоминаю, что моя структура все же в кеш влезает по условию

Maurog

Нагуглилось:
в общем, я тоже не вижу проблем. хотя автор блога мог бы потестить offset 61, 62, 63, чтобы увидеть проседание скорости из-за расползания инта по двум кеш-линиям :grin:

Serab

И еще random access неплохо бы потестить, а не последовательный доступ.

Codcod

Посмотрел код.
Афтар небельмесе в тестировании таких подводных камней.
Если переделать код на рандомное вытаскивание данных из памяти, - апа ! получаем 2х прирост производительности.

vall

в каком случае упадет? можно примерчик описать? напоминаю, что моя структура все же в кеш влезает по условию
Если влазит то cache miss будет конечно один.
Мне кажется измерять скорость одной инструкции бесполезно, это какой-то сферический конь.
Если брать какой-то кусок то инструкции явно разобьются на большее число операций и как-то иначе распределятся конвейером.
Если чего-то не хватит или появятся конфликты и параллелизм упадёт то и производительность тоже упадёт.
Нужен некий цикл который на каждом такте нагружает почти все блоки и в нём уже сломать выравнивание.

Maurog

* load/store станут совсем неатомарными
на самом деле это важное замечание :D т.к. влияет на корректность многопоточного приложения

salamander

Не, где-то он в другом месте налажал. Я немножко подпилил код, который он упоминает как "контрпример".
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <time.h>

#define TEST_ALIGNED 0
#define REPEAT_COUNT 10000
uint16_t indexes[100000];
uint32_t data1[32768];
uint32_t data2[32768];
uint32_t data3[32768];
uint32_t data4[32768];

uint32_t accumulate_non_aligned(uint8_t *data1, uint8_t *data2, uint8_t *data3,
uint8_t *data4, uint16_t *indexes, uint32_t size, int offset) {
int i, j;
uint32_t result = 0;
for (j=0; j<REPEAT_COUNT; j++) {
for (i=0; i<size; i++) {
result += *volatile uint32_t *data1+indexes[i]*4+offset;
result += *volatile uint32_t *data2+indexes[i]*4+offset;
result += *volatile uint32_t *data3+indexes[i]*4+offset;
result += *volatile uint32_t *data4+indexes[i]*4+offset;
}
}
return result;
}

int main(int argc, char **argv) {
int i;
uint32_t result = 0;
clock_t start, end;
srand(1234);
for (i=0;i<100000;i++) {
indexes[i] = rand % 32766;
// indexes[i] = i % 32766;
}
for (i=0;i<32768;i++) {
data1[i] = rand;
data2[i] = rand;
data3[i] = rand;
data4[i] = rand;
}
for (i = 0; i < 8; i++) {
start = clock;
result = accumulate_non_aligneduint8_t *)data1, (uint8_t *)data2,
(uint8_t *)data3, (uint8_t *)data4, indexes, 100000, i);
end = clock;
printf("%d: result = %d, time = %0.5f\n", i, result, doubleend-start)/CLOCKS_PER_SEC;
}
return 0;
}

У меня невыровненный доступ получается медленнее всегда, как при последовательном, так и при произвольном доступе.
На Core 2 E8400 невыровненный на 35% медленнее при произвольном и на 20% при последовательном.
На Core i7 3770K на 10% и 5% соответственно.
Ни твоих 100%, ни авторовских 0% у меня воспроизвести не получилось.

vall

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

bleyman

В связи с этим накладываю еще одно ограничение : пусть моя структура лежит в одной кеш-линии и плохо выровнена.
Как ты собираешься гарантировать первое без избавления от второго? Или так, подрочить кругообразно хочется?

vall

да легко, сделать размер самой структуры кратным кэшлайну.
но непонятно нафига всё это — пересортировав поля можно всё выравнять.

Maurog

пересортировав поля можно всё выравнять
один задал непонятный вопрос, второй дал непонятный ответ :grin:
не представляю как сортировка полей может повлиять на выравнивание :confused: она может повлиять на размер структуры, на паддинги между полями, но никак не на требование к выравниванию
пример структуры, которая влазит в кеш-линию, но которая может быть размещена с нарушением требований выравнивания:

struct A
{
int x;
};

// требование к выравниванию скорее всего "адрес кратен 4"
void * address = // такой адрес, что % cache_line_size == 1;
new (address) A;
A* a = (A*) address; // плохо выровненный объект, помещающийся в кеш-линию

vall

откуда ты возьмёшь эти кривые адреса? у тебя есть специальный кривой аллокатор?
не проще ли его выпрямить чтоб он выравнивал адреса хотя бы на 8 байт вместо
того чтоб упарываться с этим выраванием потом.
В ядре повсюду пару младших бит поинтеров используют под разные интересные нужды
то фражок туда такой воткнут то вообще бит-спинлок. А если где-то вылазит нечётный
поинтер (кроме некторых char *) то это сразу сигнализирует о баге или каррапшне.

Maurog

у тебя есть специальный кривой аллокатор?
не проще ли его выпрямить
есть. собственно вопрос изначальный в том и состоит - надо ли его выпрямлять :grin:

Maurog

чтоб он выравнивал адреса хотя бы на 8 байт
планку надо поднять до 16 : http://liveworkspace.org/code/3ASJEy%242

vall

если не знаешь на каких машинах в будущем этот код будет работать то лучше выправить.

Maurog

если не знаешь на каких машинах
я знаю, это в первом посте указано :grin:

vall

я знаю, это в первом посте указано
тогда забей
Оставить комментарий
Имя или ник:
Комментарий: