LINUX.ORG.RU

безопасно ли читать в avx регистр концы массивов

 , , ,


1

3

Допустим есть такой случай

uint8_t arr[33] __attribute__((aligned(64)));
....

__m256i v = _mm256_load_si256((__m256i*) arr);
...
v = _mm256_load_si256((__m256i*) arr+32);

Безопасен ли такой код? Насколько я понимаю память на x86 выделяется страницами с минимальным размером в 4кб. А то в свою очердь означает, что читая 32 байта (256 бит) по выравненому указателю (по 64 байта - 512бит) мы не можем залезть в память другого приложения (или ядра) и получить неприятные последствия от этого.

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

★★★★★

Причём тут система и другой процесс? Память либо принадлежит этому процессу и тогда прочитается из соседних переменных, либо процесс сигфолтнется по причине выхода из диапазона, принадлежащего этому процессу. В таких случаях надо либо размер выбирать кратным, либо увеличивать align до 256 у следующей переменной. О размерах страниц можно говорить только если у этой переменной вы бы поставили align 4k.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)

В дополнение к оратору выше. Возможно, санитайзер на такое поругается. Собери с -fsanitize=address и посмотри.

d ★★★ ()
Ответ на: комментарий от vodz

О размерах страниц можно говорить только если у этой переменной вы бы поставили align 4k.

Постойте, но ведь если мы находимся внутри куска памяти размером 4096 байт и читает с номера 0 <= x < 4096 и такому чтоон кратен 32 и читаем мы 32 байта, то никак невозможно выйти за верхнюю границу.

Dudraug ★★★★★ ()

А то в свою очердь означает, что читая 32 байта (256 бит) по выравненому указателю (по 64 байта - 512бит) мы не можем залезть в память другого приложения (или ядра) и получить неприятные последствия от этого.

Похоже на правду. Кажется, что-то такое делали в glibc в strlen. Не вникал код, но там что-то типа упреждающего чтения до 64 байт за раз. Примерно как у тебя.

Само собой, всякие инструменты типа Valgrind или AddressSanitizer ругаться будут. Про особенности функций стандартной библиотеки они в курсе, а про особенности твоих функций — нет.

i-rinat ★★★★★ ()
Ответ на: комментарий от Dudraug

А, ну так то да, 33 элемент будет лежать в «дырке» до 64 байт. Вот только одна тонкость, размер последней страницы процесса указывается точно, то есть <= 4k, чтобы от этой границы и расширятся (s)brk().

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Кажется, что-то такое делали в glibc в strlen.

Не так делали, всегда делается выравнивание вначале, а хвост дочитывают отдельно.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Вот только одна тонкость, размер последней страницы процесса указывается точно, то есть <= 4k, чтобы от этой границы и расширятся (s)brk().

Хм, а можно с этого места поподробнее? Линк например.

Dudraug ★★★★★ ()
Ответ на: комментарий от vodz

хвост дочитывают отдельно

Как в strlen определить, где хвост начинается?

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Как в strlen определить, где хвост начинается?

Магия с $0xfefefefefefefeff дает только флаг, что 0 есть в 64-тном слове, далее уже происходит поиск в хвосте побайтно.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Не так делали, всегда делается выравнивание вначале, а хвост дочитывают отдельно.

Мне стало интересно, я поглядел немного в код. Там всё страшно, поэтому я перестал глядеть в код. Решил потестить.

Берём программку:

#include <string.h>
static char buf[1024];
int main(int argc, char *argv[]) {
 if (argc < 2)
  return 0;
 strcpy(buf, argv[1]);
 return strlen(buf);
}

Собираем, запускаем:

Reading symbols from ./a.out...done.
(gdb) b main
Breakpoint 1 at 0x6b9: file strlen_test.c, line 4.
(gdb) r
Starting program: /tmp/1/a.out 01234567890123456789

Breakpoint 1, main (argc=2, argv=0x7fffffffe228) at strlen_test.c:4
4	 if (argc < 2)
(gdb) p buf
$1 = '\000' <repeats 1023 times>
(gdb) p &buf
$2 = (char (*)[1024]) 0x555555556060 <buf>
(gdb) p strlen(argv[1])
$3 = 20
(gdb) rwatch *(unsigned char *)(0x555555556060 + 30)
Hardware read watchpoint 2: *(unsigned char *)(0x555555556060 + 30)
(gdb) c
Continuing.

Hardware read watchpoint 2: *(unsigned char *)(0x555555556060 + 30)

Value = 0 '\000'
strlen () at ../sysdeps/x86_64/strlen.S:118
118	../sysdeps/x86_64/strlen.S: Нет такого файла или каталога.
(gdb) 

Вот, пожалуйста. Вроде бы strlen не должен залазить дальше двадцать первого байта, ведь там строка длиной 20. Ан нет, лезет и в +30. Массив в 1024 байта гарантирует, что туда ничего из служебного кода залазить не будет. Только явный strlen.

i-rinat ★★★★★ ()
Ответ на: комментарий от vodz

Магия с $0xfefefefefefefeff дает только флаг, что 0 есть в 64-тном слове

Эээ... Кажется, ты смотришь в код для PowerPC64.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Эээ... Кажется, ты смотришь в код для PowerPC64.

Нет. В x64.

Вроде бы strlen не должен залазить дальше двадцать первого байта, ведь там строка длиной 20. Ан нет, лезет и в +30.

Ну по границе слова то точно можно лезть.

vodz ★★★★★ ()
Ответ на: комментарий от Dudraug

Хм, а можно с этого места поподробнее? Линк например.

Я взял из памяти, это было давно, сейчас вот провёл эксперимент с sbrk на x64, так он вообще случайно скачет, типа защита, но таки да, по границе 4k.

Но ваш подход же странный. Типа если в реализации так упростили, то давайте срочно это заюзаем и будем ко всем приставать по теме, а что, бывает как-то по правильному? Ну так и на 32-битных процессорах по правильному бывает для процесса 4G, а не 3G со скрипом в x386 в отлчии от.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Ну по границе слова то точно можно лезть.

Срабатывает до buf+63 включительно. Все 64 байта читаются безусловно.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Срабатывает до buf+63 включительно. Все 64 байта читаются безусловн

Что, правда у вас процессор с AVX512 ? Интересно, сколько оно вычисляет какой алгоритм задействовать, а потом в этих 64 байтах ищет ноль в 20-том...

vodz ★★★★★ ()
Ответ на: комментарий от vodz

А чего AVX512? Там же первая строка:

/* SSE2 version of strlen/wcslen.

xaizek ★★★★★ ()
Ответ на: комментарий от d

Не возможно а точно. Потому в таких случаях вставляют прагмы чтобы не ругался.
Что касается безопасности чтения - страницы обычно намного больше чем размер читаемого элемента, а элементы читаются выровненно.

mittorn ★★★★★ ()
Ответ на: комментарий от xaizek

А чего AVX512? Там же первая строка

Где там? И давно в sse2 регистры по 512 бит?

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Что, правда у вас процессор с AVX512?

Нет, там четыре операции по 128 бит за раз. Как я понял, сначала проверяются первые 16 байт инструкциями, которые могут грузить невыровненные данные, потом ещё 48 уже выровненных. Ещё наслоение может быть, так что для невыровненного начала будет не 64 байта, а меньше. У меня длина была 20, буфер выровнен, поэтому получилось чтение 64 байт. Надо бы ещё попробовать строки длиной больше 64.

i-rinat ★★★★★ ()
Ответ на: комментарий от vodz

И давно в sse2 регистры по 512 бит?

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

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Про особенности функций стандартной библиотеки они в курсе

Два года назад valgrind совершенно точно был не в курсе такой особенности strrchr.

kawaii_neko ★★★ ()
Ответ на: комментарий от i-rinat

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

Так если перемежать обычные инструкции с sse, то получается выигрыш. А тут не только перемежать можно, а нужно увидеть, что пора закончить, а то и вообще граница памяти по башке даст. Особенно пикантно, что с 20 байтами всё это просто отпад. Уж чего-чего, а strlen если в первых десятках байт не нашло и полезло в мегабайты, то явно там уже нечего спешить, всё равно оно скорее всего сканирует мусор :)

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Тут я совсем перестал понимать, что ты хочешь сказать.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Я хотел сказать, что в прострации от этого новомодного кода. Они в самом деле считают, что strlen должно быть оптимизировано для сверхдлинных строк?

vodz ★★★★★ ()
Ответ на: комментарий от vodz

strlen из стандартной библиотеки быстрее наивной реализации в 2,3 раза на моей машине. Ну как быстрее. Я разнёс микробенчмарк, наивный strlen и буфер по разным файлам, чтобы компилятор не заменял strlen своим встроенным и не выбрасывал вычисления полностью. Так что там в цикле вызывается char *get_buf() и strlen. Это не совсем правильный бенчмарк, но основную идею ухватить позволяет.

Оказывается быстрее читать больше байт (64 против 20) и делать проверки скопом, чем читать по одному байту и каждый раз проверять, не нашли ли мы окончание строки.

Они в самом деле считают, что strlen должно быть оптимизировано для сверхдлинных строк?

Это ж опенсорс. Если у тебя есть реализация получше, закинь в рассылку. Проверить производительность не забудь только. А то в этих оптимизациях «мне кажется» не работают.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Проверить производительность не забудь только.

Во-первых, это вы уже утрируете. Полностью побайтно я не предлагал.

Во-вторых, речь была не о разнице производительности, а о паттерне использования.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Полностью побайтно я не предлагал.

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

Во-вторых, речь была не о разнице производительности, а о паттерне использования.

Там вся суть — в производительности. Никто не хочет медленную стандартную библиотеку. А то, что код не особо привычный, а иногда и просто дикий, так это удивительный мир оптимизаций.

i-rinat ★★★★★ ()
Последнее исправление: i-rinat (всего исправлений: 1)
Ответ на: комментарий от i-rinat

Там вся суть — в производительности.

Вы не с той мыслью со мной спорите. Я прекрасно осознаю, что на сверхдлинных строках этот алгоритм победит.

Никто не хочет медленную стандартную библиотеку.

А в результате она пухнет и в обычных профилях использования становится медленнее. Вот ваш простой бенчмарк взять. Где под него взять библиотеку без тредов и printf без поддержки %ls? Брать очень старую версию uclibc с кучей глюков?

vodz ★★★★★ ()
Ответ на: комментарий от vodz

А в результате она пухнет и в обычных профилях использования становится медленнее

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

Без этого твои слова про «медленнее» — всего лишь безосновательные заявления. Я понимаю, что кажется, что ты можешь судить о скорости того или иного кода. Но реальность жестока: с предсказаниями сейчас очень трудно не ошибиться. Единственный способ узнать — измерять.

i-rinat ★★★★★ ()

если у тебя чтение выровнено - то да. есть риск прочитать мусор, но page fault ты не поймаешь. только еще один момент - надо замаскировать исключения типа NaN - операции с мусором могут привести к внезапным исключениям, которые приведут к SIGILL, SIGFPE, или еще чему-то неприятному.

ckotinko ☆☆☆ ()
Ответ на: комментарий от vodz

а зачем fefefefe?

pxor mm1, mm1

pcmpeqb mm1,mm0

maskmov eax,mm1

add eax,1

jne нулевых байт нет

cntlz eax -> номер нулевого байта

ckotinko ☆☆☆ ()

Безопасен ли такой код

нет, на ЛОРе все опасно

anonymous ()
Ответ на: комментарий от i-rinat

К этому утверждению нужно прикладывать пачку бенчмарков.

Кому нужно? Мне, чтобы увидеть, что мир становится тормозным bloatware никакие бенчмарки не нужны, я и так вижу, что для 20 байт это чудовищно избыточно. С приведенным кодом воевать бесполезно, ибо решения strlen для строк ~32 байт в libc всё равно не вставят.

vodz ★★★★★ ()
Ответ на: комментарий от ckotinko

а зачем fefefefe?

Такая реализация в древней uclibc для x86-64. Зато уж точно не надо проверять, поддерживает для процессор sse.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

я и так вижу, что для 20 байт это чудовищно избыточно.

Твоё «вижу» — ничто перед цифрами результатов бенчмарков.

мир становится тормозным bloatware

Мир становится тормозным bloatware в том числе и потому, что некоторые люди принимают решения потому что им кажется, что так будет быстрее, не заморачиваясь на собственно проверки.

ибо решения strlen для строк ~32 байт в libc всё равно не вставят.

Если твоя аргументация будет на уровне «я и так вижу», то конечно не вставят. И это хорошо.

i-rinat ★★★★★ ()
Ответ на: комментарий от vodz

Такая реализация в древней uclibc для x86-64

Не-е, ну отлично вообще. Речь шла про glibc, а он пишет про код из uclibc.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Твоё «вижу» — ничто перед цифрами результатов бенчмарков.
Если твоя аргументация будет на уровне «я и так вижу»

У вас то и вообще никакого утверждения нет. А моё «вижу» от вас подтверждения не нуждается. Аргументация будет - не положено в libc иметь strlen_for_typical_short_strings(). Ибо если так сделать то все сразу что-то подобного захотят.

Не-е, ну отлично вообще. Речь шла про glibc, а он пишет про код из uclibc.

Так они же тоже откуда-то спёрли эту магию, скорее всего из более древней glibc.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

У вас то и вообще никакого утверждения нет.

А оно должно быть?

Тем не менее, я в теме пока что единственный, кто проверил производительность реализаций strlen.

Аргументация будет - не положено в libc иметь strlen_for_typical_short_strings(). Ибо если так сделать то все сразу что-то подобного захотят.

Это всё? Обсуждений конкретных вариантов реализации тут не будет? Тогда мне тут больше делать нечего. Удачи с теориями заговора.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

А оно должно быть?
Обсуждений конкретных вариантов

Нормальненько, да. Предложить (ведь никто за рукав не тянул) строку в 20 байт и не сделать утверждения, что код на 64-байтном чтении обгонит пословное 64-битное чтение и потом ещё обвинить в отсутствии вариантов собеседника? Да вы же просто боитесь и апелируете к авторитету в виде текущей реализации strlen.

Тогда мне тут больше делать нечего.

Да давно бы так, достали уже тупить.

vodz ★★★★★ ()
Ответ на: комментарий от i-rinat

Немного офтоп про strlen. Чтобы считать длину строк в X86 процессорах с SSE4.2 есть инструкция PCMPISTRI.

Вот пример strlen с использованием PCMPISTRI на MASM: https://pastebin.com/50ZateaY

fsb4000 ★★ ()
Ответ на: комментарий от fsb4000

Немного офтоп про strlen. Чтобы считать длину строк в X86 процессорах с SSE4.2 есть инструкция PCMPISTRI.

И sse4.2 использовался для strlen до https://github.com/bminor/glibc/commit/37bb363f03d75e5e6f2ca45f2c686a3a0167797e. В архиве рассылки можно найти обсуждения, правда ссылка на готовые результаты бенчмарков уже не работает.

i-rinat ★★★★★ ()

Да, очевидно, что первый элемент массива будет находиться по относительному (от начала страницы) адресу, кратному 64, поскольку 4096 кратно 64, то последний граничный элемент в любом случае не будет размазан на две страницы.

buddhist ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.