Страницы

Поиск по вопросам

Показаны сообщения с ярлыком x86. Показать все сообщения
Показаны сообщения с ярлыком x86. Показать все сообщения

среда, 22 января 2020 г.

Аналог system в ассемблерном коде

#linux #c #ассемблер #x64 #x86


Какой аналог system в ассемблерном коде? Например, чтобы вывести строку в stdout
под linux x86 надо в eax записать номер системного вызова, в ebx записать код потока(для
stdout это 1), в ecx указатель на строку, в edx длину строки. И потом вызвать прерывание(int
0x80). Итого:

  mov $4,   %eax
  mov $1,   %ebx
  mov $msg, %ecx
  mov $len, %edx
  int $0x80


Как в ассемблере написать аналог вызова C-шной функции system? Без разницы  под x86
или x64. Например, я хочу написать аналог для вызова system("gedit").
    


Ответы

Ответ 1



Запуск файла на исполнение осуществляется через 11-ю функцию 80-го прерывания. Вот на английском SO был вопрос про это: https://stackoverflow.com/questions/9342410/sys-execve-system-call-from-assembly

четверг, 5 декабря 2019 г.

Как программно поставить точку останова на память?

#cpp #c #windows #x86


Возьмем цикл, который читает bool переменную

#include 

volatile bool stop = false;

void loop() {
  while (!stop) {
    putchar('.');
  }
}


Мы хотим поменять значение stop на 4-м чтении, чтобы цикл напечатал троеточие многоточие
и завершился.

Как на Windows перехватить чтения переменной stop, и поменять ее значение?
    


Ответы

Ответ 1



Для точек останова на память можно использовать Guard Page. Это специальный флаг свойств страницы памяти, при котором обращение к странице вызывает исключение STATUS_GUARD_PAGE_VIOLATION (0x80000001). При этом флаг Guard Page сбрасывается, и повторное обращение к странице памяти исключений не вызывает. Для чтений памяти, обработчик исключений может перехватить исключение STATUS_GUARD_PAGE_VIOLATION, изменить значение памяти и пометить исключение обработанным. Чтение перезапустится, и прочитается новое значение выставленное в обработчике. Чтобы точка останова не была одноразовой, флаг Guard Page надо выставить повторно. Для этого в обработчике STATUS_GUARD_PAGE_VIOLATION надо выставить флаг процессора TF (trap flag). Тогда перед выполнением следующей инструкции будет выброшено исключение STATUS_SINGLE_STEP. В его обработчике можно обновить флаг Guard Page, а также можно прочитать значение записанное предыдущей инструкцией, если она делала запись памяти. В коде это выглядит следующим образом: #include int aux_counter = 0; // счетчик для отсчета четырех чтений // установка PAGE_GUARD для страницы в которую входит адрес |addr| void set_guard_on_bool(volatile bool* addr) { MEMORY_BASIC_INFORMATION info; VirtualQuery((void*)addr, &info, sizeof(info)); DWORD unused; VirtualProtect((void*)addr, sizeof(*addr), info.Protect | PAGE_GUARD, &unused); } // обработчик исключений LONG __stdcall exception_handler(EXCEPTION_POINTERS* e) { switch (e->ExceptionRecord->ExceptionCode) { case STATUS_GUARD_PAGE_VIOLATION: { // параметры исключения - чтение/запись и адрес памяти bool is_read = e->ExceptionRecord->ExceptionInformation[0] == 0; void* location = (void*)e->ExceptionRecord->ExceptionInformation[1]; // проверяем что это чтение адреса |stop| // TODO: тут можно пропустить исключения для всех остальных страниц памяти if (location == &stop && is_read) { ++aux_counter; // завершаем цикл на 4-е чтение переменной if (aux_counter == 4) { stop = true; return EXCEPTION_CONTINUE_EXECUTION; } } // выставляем TF чтобы вызвать исключение STATUS_SINGLE_STEP e->ContextRecord->EFlags |= 0x100; return EXCEPTION_CONTINUE_EXECUTION; } case STATUS_SINGLE_STEP: { // обновляем флаг PAGE_GUARD set_guard_on_bool(&stop); return EXCEPTION_CONTINUE_EXECUTION; } default: { // пропускаем остальные исключения return EXCEPTION_CONTINUE_SEARCH; } } } int main() { // устанавливаем обработчик исключений AddVectoredExceptionHandler(1 /* first */, exception_handler); // и выставляем флаг PAGE_GUARD set_guard_on_bool(&stop); loop(); } Пример работы программы: > g++ -O3 main.cpp && a.exe ...

суббота, 30 ноября 2019 г.

Как использовать данные о размерах кэшей процессора для ускорения программы?

#cpp #ассемблер #x64 #x86 #x86_64


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


Есть ли смысл писать программу таким образом, что б абсолютно все используемые массивы,
переменные и т.п. в оперативной памяти занимали 
строго последовательные адреса? Т.е. например: начальный адрес 90870000, конечный
90880000. Есть смысл что б этом промежутке были исключительно данные конкретной программы?
Как влияет на скорость программы соотношение размера кэшей и используемого размера
оперативной памяти?
Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками?
Каждый блок меньше размера кэша.
В ассемблере есть инструкции по записи в оперативную память без использования кэша.
Какой в этом смысл?
Какую из двух инструкций и в каких случаях использовать для возврата 128 бит результата
обработки обратно в память?

     movdqu [ebx],xmm0
     movntps [ebx],xmm0//без использования кэша



Правильно я понимаю, что если эти 128 бит больше не нужны для обратотки, то лучше
movntps? Это быстрее?


Предположим есть чернобелое bmp изображение 1024*1024.
Перенесем все его пиксели в:

    unsigned __int8 *src_img
    src_img = new unsigned __int8[1024*1024];//каждый байт это значение одного пикселя
от 0 до 255



Допустим есть два варианта алгоритма:
1) копируем в xmm0 128 бит из src_img, что-то делаем в xmm регистрах и возвращаем
измененные 128бит обратно по тому же адресу в оперативную память. Самое важное эти
128 бит выбираются от начала src_img последовательно до конца.
2) Делаем тоже самое, но 128 бит выбираются не последовательно,а из разных мест src_img
1-й вариант будет быстрее или нет? Или формулируя по другому: первоначально программа
берет данные из: movdqu xmm0,[ebx]. Имеет различие для быстродействия насколько далеко
от первоначального адреса программа берет следующие 128 бит? 

Изучаю данные вопросы в контексте вот этой задачи:
https://stackoverflow.com/questions/50747393/prewitt-edge-detection-algorithm-using-x86-mmx-simd
    


Ответы

Ответ 1



Есть смысл что б этом промежутке были исключительно данные конкретной программы? Это зависит от программы. Если она оч. часто обращается к этим переменным, то да, конечно - размещение всех ее данных в блоке, который занимает минимальное к-во кеш-линий, ускорит эти операции. Обычно не говорят о размещении всех данных в одной кеш-линии. На практике это невозможно. Обычно говорят о "кратном рамере" блока данных. Как влияет на скорость программы соотношение размера кэшей и используемого размера оперативной памяти? Особой разницы с первым вопросом не вижу. Если у Вас программа оптимизирована под загрузку кеша, и учитывает его размер, конечно, она будет быстрее работать на железе с большим кешем. Надеюсь, Вы так же вкурсе, что кеши есть разных уровней (level1, level2, level3, кеш данных, кеш инструкций), их размеры могут отличаться, и скорость доступа к ним, естественно, тоже разная, т.е. поле для оптимизаций - обширное настолько, насколько Вам хватит терпения перебирать различное железо для экспериментов и фантазии на реализацию алгоритмов. Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками? Каждый блок меньше размера кэша. Нет. Есть смысл распараллелить эту обработку между ядрами таким образом, чтобы у каждого ядра эти порции данных, по возможности, не пересекались. Вот тут кратность размера обрабатываемого блока данных имеет большое значение. Порции следует разделить так, чтобы кеш-линии одного ядра не пересекались с кеш-линиями другого. В ассемблере есть инструкции по записи в оперативную память без использования кэша. Какой в этом смысл? Смысл в том, чтобы не делать лишнюю работу: если Вам нужно только переместить блок данных, и Вы не обращаетесь к нему для каких-то рассчетов, то и в кеш его нет необходимости загружать. Имеет различие для быстродействия насколько далеко от первоначального адреса программа берет следующие 128 бит? Нет, не должно. На 64битных кеш-линиях, по крайней мере, последовательное чтение 128битных значений в непараллельной программе выгоды не дает. В этом случае определяющим будет размер кеша и параллелизм между ядрами (кооперация кэшей). Ждите или ищите 256битных кеш линий, по крайней мере :)

Запуск низкоуровневых программ

#c++ #c #ассемблер #x86 #bios


Пытаюсь разобраться с низкоуровневым программированием. Поставил задачу — написать
"Hello World"-программу, записать её на флешку, перезагрузиться, запустить программу
с флешки (не запуская ОС). 


Можно ли это сделать на С или С++?
Как дать понять биосу, что её надо загрузить в память, выполнить?
Что из себя должна представлять программа? Исполняемый файл .com? .bin файл? Слышал,
это должно быть 16-bit приложение.
Если ли какие-то ограничения в названии программы?
Немного не по теме: Стоит ли пользоваться MASM? Какой Ассемблер сейчас наиболее популярен
для x86?


Заранне Спасибо!
    


Ответы

Ответ 1



Можно и на C/C++, но в очень урезанном виде - практически никаких библиотек. К вашим услугам только функции BIOS (int 13h) (Т.е. ваш "Hello, world" не должен выводиться ни с помощью puts или printf, ни тем более в cout). Никакой main в качестве входной точки (если только не измените соответствующим образом рантайм загрузчик). Т.е. только какие-то базовые вещи (типа арифметики :)), которые компилируются в .obj, и линкуются с начальным загрузчиком на ассемблере. Когда-то интереса ради урезал Turbo C++ до такого состояния - очень даже ничего получалось :) Я в свое время использовал TASM - отлично подходил для таких целей. Работать этот код начинает в реальном режиме, так что только 16 бит, причем (мы же о PC?) грузится он в память по адресу 0000:7С00 и начинает выполняться оттуда. Грузится он из MBR, так что если это что-то более-менее большое - то сразу должен сам считать и загрузить остальное в память. Считайте, что com-файл DOS, только никаких org 100h - что получено, то просто положено в очень конкретную :) память - и на диске, и в RAM, и управление передается первому байту. Соответственно, какое уж тут название программы... Название (файла) - это уже файловая система, а тут ею и не пахнет. Мой совет: виртуальная машина с виртуальным дисководом для дискет :), грузящаяся с такой виртуальной дискеты. Первый сектор - ваш. Это позволит вам делать, что хотите, ничем не рискуя, и очень легко записывать ваше творение в файл, не мучаясь с записью в конкретные сектора на диске/флешке (при этом можно легко натворить неприятностей).

Ответ 2



Да. Записать её в MBR флешки, а в BIOS выбрать загрузку с USB приоритетнее, чем с винчестера. Проще всего получить ассемблерный листинг и ввести код с помощью HexEdit прямо в MBR диска. Старайтесь в названии не использовать чужие торговые марки. Стоит. Отличный ассемблер.

Ответ 3



Если хотите поизучать низкоуровневое программирование - начните с микроконтроллеров там все под это дело заточено. Рекомендую с avr начать - есть много хороших статей по asm и с - очень удобные среды разработки и эмуляторы. Это если Вам x86 не принципиален.

Ответ 4



Да и практической пользы изучив низкоуровневое программирование МК все же поболее будет. Мало чем отличаться будет программирование x86 или МК если писать на C (С++ не очень распространен среди мк) с точки зрения обучения.Если конечно у Вас не стоит каких-то экзотических задач писать для 86 платформы.

понедельник, 25 ноября 2019 г.

Можно ли на языках C/C++ определить целочисленное переполнение?


Часто в контексте безопасного программирования упоминают проблему целочисленног
переполнения (integer overflow). А возможно ли отловить эту ситуацию в C/C++ коде? Вед
процессоры (по крайней мере x86) имеют среди EFLAGS флаг Overflow Flag, получается
что чисто технически такая возможность имеется, да и оверхэд на проверку флага в регистре не должен быть большим (думается так). Либо проблема в постановке вопроса и переполнение нужно не отлавливать, а не допускать никогда? Значит ли последнее, что целочисленные типы принципиально не подходят для любых вычислений?

P.S. Вопрос возник в связи с тем, что в существующем коде (наблюдаемом мной) широк
используется int для арифметических расчетов и ничто не защищает от переполнений, гипотетически они могут остаться незамеченными (логикой). И в большинстве случаев с большой вероятностью все в норме (реальные значения невелики), но иногда...
    


Ответы

Ответ 1



Официально в стандарте языка записано, если я не путаю, что переполнение знаковы целочисленных типов зависит от реализации (может генерироваться исключение, может игнорироваться), а беззнаковых игнорируется - значение приводится в представимый диапазон. Т.е. получается примерно так - сам C/C++ встроенных переносимых средств обнаружения переполнения не имеет. Но всегда можно использовать дополнительные методы, которые позволят по данным операнда выяснить, будет ли переполнение при выполнении операции. Масса таких вещей описана в книге Г. Уоррена Алгоритмические трюки для программистов. Ну и как иллюстрация, насколько легко пропустить это переполнение - у Страуструп в Программирование. Принципы и практика... есть одна программка, в которой вычислялс ряд для ex, что ли - и она у него в разнос шла. Он честно написал, что раньше, мол, я думал, что это связано с потерей точности в double, и вроде даже так и написал в первом издании книги, а потом ко второму изданию дошло, что на самом деле это переполнение целочисленных значений при вычислении факториала. Если уж даже Страуструп... :)

Ответ 2



Действие по умолчанию зависит от процессора, компилятора и настроек. На MIPS, например его "классические" компиляторы превращали сложение знаковых в команду add, которая генерируе исключение при переполнении, а беззнаковых - addu, которая не генерирует. Но gcc, clang уже такого не делали, всё через addu. На x86 - родные команды add, sub игнорируют переполнение (точнее, ставят флаг CF или OF, но не генерируют исключение). Компиляторы современного типа считают, что переполнения в операциях с целыми со знако не должно быть, иногда из-за этого возникают интересные эффекты - вот самый убойны пример из тех, что я видел. Стандарт тут действует по принципу "мы вам даём возможность писать максимально эффективно, потому что это C, а не учебный язык, а защита от кривостей - ваша проблема". Для C++ есть, например, библиотечка (шаблонный заголовочный файл) SafeInt3 от Microsoft под свободной лицензией; ею можно покрыть все основные операции, хотя она не использует специфику процессоров (даже x86) и оттого во многих случаях неэффективна - там, где достаточно одного OF или CF, она городит много сложных расчётов. Для C придётся всё писать вручную, заменяя обычные операции на свои эквиваленты функциями или макросами. UPDATE: про новые встроенные функции последних gcc и clang (__builtin_add_overflow __builtin_mul_overflow и вся группа)уже писали. Но и без них можно сделать достаточно неплохо. Например, в случае сложения двух int, проверка вида a > INT_MAX - b, если b > 0, иначе a < INT_MIN - b, достаточна, чтобы проверить разрешимость сложения.

Ответ 3



Хоть и не приветствуются ответы только из ссылок, но вот нашел хороший материал ( т.ч. там код проверок, вызовет ли переполнение (более обще -- безопасно ли) сложение, вычитание, умножение и деление целых): https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow с обсуждениями и т.п. Update Поскольку при поиске ответа всегда хочется сразу увидеть конкретный код, помогающи в решении задачи (а не гадать, например, как правильно написать условие проверки для вычитания, уже зная правильный ответ для сложения), приведу тут некоторые из материалов по этой ссылке, которые можно использовать как образцы для своих программ. Проверка перед сложением: #include void f(signed int si_a, signed int si_b) { signed int sum; if (((si_b > 0) && (si_a > (INT_MAX - si_b))) || ((si_b < 0) && (si_a < (INT_MIN - si_b)))) { /* Handle error */ } else { sum = si_a + si_b; } /* ... */ } Проверка перед вычитанием: #include void func(signed int si_a, signed int si_b) { signed int diff; if ((si_b > 0 && si_a < INT_MIN + si_b) || (si_b < 0 && si_a > INT_MAX + si_b)) { /* Handle error */ } else { diff = si_a - si_b; } /* ... */ } Проверка перед умножением: #include void func(signed int si_a, signed int si_b) { signed int result; if (si_a > 0) { /* si_a is positive */ if (si_b > 0) { /* si_a and si_b are positive */ if (si_a > (INT_MAX / si_b)) { /* Handle error */ } } else { /* si_a positive, si_b nonpositive */ if (si_b < (INT_MIN / si_a)) { /* Handle error */ } } /* si_a positive, si_b nonpositive */ } else { /* si_a is nonpositive */ if (si_b > 0) { /* si_a is nonpositive, si_b is positive */ if (si_a < (INT_MIN / si_b)) { /* Handle error */ } } else { /* si_a and si_b are nonpositive */ if ( (si_a != 0) && (si_b < (INT_MAX / si_a))) { /* Handle error */ } } /* End if si_a and si_b are nonpositive */ } /* End if si_a is nonpositive */ result = si_a * si_b; } Проверка перед делением: (или вычислением остатка) #include void func(signed long s_a, signed long s_b) { signed long result; if ((s_b == 0) || ((s_a == LONG_MIN) && (s_b == -1))) { /* Handle error */ } else { result = s_a / s_b; } /* ... */ } Ну, если кому не лень вытащить оттуда остальной (относящийся к вопросу ТС) код ( также описание всех существенных моментов) сюда и аккуратно его оформить, милости просим.

Ответ 4



Попробую резюмировать: легче избежать проблемы (продумав детально арифметику), чем потом ее (неприятно) обнаруживать и обрабатывать. Решения с перехватом либо зависят от компилятора, либо представляют собой отказ о непосредственно встроенных типов. Полагаю, подходящим решением (кроме работы с память и коллекциями, разрешающими индексацию) может стать использование чисел с плавающей запятой, т.к. они более приближены к естественной арифметике (в плане бесконечностей, деления на 0). В книге "24 смертных греха компьютерной безопасности" Ховарда, Лебланка, Вьеги встретил такое решение: учет размеров типов (см. другие ответы); простота кода; явное преобразование типов; использование SafeInt (by Дэвид Лебланк) при использовании gcc, компилировать с флагом -ftrapv (при переполнении знаковы целочисленных вызывается abort()); использование беззнаковых целых для работы с памятью и индексами массивов (слегка облегчает последствия);

Ответ 5



В Visual C++ можно использовать функции из Intsafe.h, например для умножения: #include #include #include #include #include int _tmain(int argc, _TCHAR* argv[]) { ULONGLONG a=100000000000, b=5000000000, c; HRESULT hr = ULongLongMult(a,b,&c); if(SUCCEEDED(hr)) printf("Result is %llu",c); else if(hr == INTSAFE_E_ARITHMETIC_OVERFLOW) printf("Overflow"); return 0; } Данные функции определены как inline, и их реализация зависит от архитектуры. Функция ULongLongMult: На 64-битных архитектурах использует intrinsic-функцию компилятора _umul128, поэтому должна быть довольно эффективной. На 32-битных архитектурах использует специальный алгоритм расчета с разбиением чисе на 2 32-битных части (результат вычисляется по формуле a.b * c.d = (a*c*2^64) + (a*d*2^32) + (b*c*2^32) + (b*d)), и переполнение обнаруживается проверкой определенных битов в промежуточных результатах.

четверг, 4 октября 2018 г.

Как использовать данные о размерах кэшей процессора для ускорения программы?

У каждого процессора есть кэш различных уровней и размеров.
Есть ли смысл писать программу таким образом, что б абсолютно все используемые массивы, переменные и т.п. в оперативной памяти занимали строго последовательные адреса? Т.е. например: начальный адрес 90870000, конечный 90880000. Есть смысл что б этом промежутке были исключительно данные конкретной программы? Как влияет на скорость программы соотношение размера кэшей и используемого размера оперативной памяти? Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками? Каждый блок меньше размера кэша. В ассемблере есть инструкции по записи в оперативную память без использования кэша. Какой в этом смысл? Какую из двух инструкций и в каких случаях использовать для возврата 128 бит результата обработки обратно в память?
movdqu [ebx],xmm0 movntps [ebx],xmm0//без использования кэша
Правильно я понимаю, что если эти 128 бит больше не нужны для обратотки, то лучше movntps? Это быстрее?
Предположим есть чернобелое bmp изображение 1024*1024. Перенесем все его пиксели в:
unsigned __int8 *src_img src_img = new unsigned __int8[1024*1024];//каждый байт это значение одного пикселя от 0 до 255
Допустим есть два варианта алгоритма: 1) копируем в xmm0 128 бит из src_img, что-то делаем в xmm регистрах и возвращаем измененные 128бит обратно по тому же адресу в оперативную память. Самое важное эти 128 бит выбираются от начала src_img последовательно до конца. 2) Делаем тоже самое, но 128 бит выбираются не последовательно,а из разных мест src_img 1-й вариант будет быстрее или нет? Или формулируя по другому: первоначально программа берет данные из: movdqu xmm0,[ebx]. Имеет различие для быстродействия насколько далеко от первоначального адреса программа берет следующие 128 бит?
Изучаю данные вопросы в контексте вот этой задачи: https://stackoverflow.com/questions/50747393/prewitt-edge-detection-algorithm-using-x86-mmx-simd


Ответ

Есть смысл что б этом промежутке были исключительно данные конкретной программы?
Это зависит от программы. Если она оч. часто обращается к этим переменным, то да, конечно - размещение всех ее данных в блоке, который занимает минимальное к-во кеш-линий, ускорит эти операции.
Обычно не говорят о размещении всех данных в одной кеш-линии. На практике это невозможно. Обычно говорят о "кратном рамере" блока данных.
Как влияет на скорость программы соотношение размера кэшей и используемого размера оперативной памяти?
Особой разницы с первым вопросом не вижу. Если у Вас программа оптимизирована под загрузку кеша, и учитывает его размер, конечно, она будет быстрее работать на железе с большим кешем. Надеюсь, Вы так же вкурсе, что кеши есть разных уровней (level1, level2, level3, кеш данных, кеш инструкций), их размеры могут отличаться, и скорость доступа к ним, естественно, тоже разная, т.е. поле для оптимизаций - обширное настолько, насколько Вам хватит терпения перебирать различное железо для экспериментов и фантазии на реализацию алгоритмов.
Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками? Каждый блок меньше размера кэша.
Нет. Есть смысл распараллелить эту обработку между ядрами таким образом, чтобы у каждого ядра эти порции данных, по возможности, не пересекались. Вот тут кратность размера обрабатываемого блока данных имеет большое значение. Порции следует разделить так, чтобы кеш-линии одного ядра не пересекались с кеш-линиями другого.
В ассемблере есть инструкции по записи в оперативную память без использования кэша. Какой в этом смысл?
Смысл в том, чтобы не делать лишнюю работу: если Вам нужно только переместить блок данных, и Вы не обращаетесь к нему для каких-то рассчетов, то и в кеш его нет необходимости загружать.
Имеет различие для быстродействия насколько далеко от первоначального адреса программа берет следующие 128 бит?
Нет, не должно. На 64битных кеш-линиях, по крайней мере, последовательное чтение 128битных значений в непараллельной программе выгоды не дает. В этом случае определяющим будет размер кеша и параллелизм между ядрами (кооперация кэшей). Ждите или ищите 256битных кеш линий, по крайней мере :)

Как использовать данные о размерах кэшей процессора для ускорения программы?

У каждого процессора есть кэш различных уровней и размеров.
Есть ли смысл писать программу таким образом, что б абсолютно все используемые массивы, переменные и т.п. в оперативной памяти занимали строго последовательные адреса? Т.е. например: начальный адрес 90870000, конечный 90880000. Есть смысл что б этом промежутке были исключительно данные конкретной программы? Как влияет на скорость программы соотношение размера кэшей и используемого размера оперативной памяти? Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками? Каждый блок меньше размера кэша. В ассемблере есть инструкции по записи в оперативную память без использования кэша. Какой в этом смысл? Какую из двух инструкций и в каких случаях использовать для возврата 128 бит результата обработки обратно в память?
movdqu [ebx],xmm0 movntps [ebx],xmm0//без использования кэша
Правильно я понимаю, что если эти 128 бит больше не нужны для обратотки, то лучше movntps? Это быстрее?
Предположим есть чернобелое bmp изображение 1024*1024. Перенесем все его пиксели в:
unsigned __int8 *src_img src_img = new unsigned __int8[1024*1024];//каждый байт это значение одного пикселя от 0 до 255
Допустим есть два варианта алгоритма: 1) копируем в xmm0 128 бит из src_img, что-то делаем в xmm регистрах и возвращаем измененные 128бит обратно по тому же адресу в оперативную память. Самое важное эти 128 бит выбираются от начала src_img последовательно до конца. 2) Делаем тоже самое, но 128 бит выбираются не последовательно,а из разных мест src_img 1-й вариант будет быстрее или нет? Или формулируя по другому: первоначально программа берет данные из: movdqu xmm0,[ebx]. Имеет различие для быстродействия насколько далеко от первоначального адреса программа берет следующие 128 бит?
Изучаю данные вопросы в контексте вот этой задачи: https://stackoverflow.com/questions/50747393/prewitt-edge-detection-algorithm-using-x86-mmx-simd


Ответ

Есть смысл что б этом промежутке были исключительно данные конкретной программы?
Это зависит от программы. Если она оч. часто обращается к этим переменным, то да, конечно - размещение всех ее данных в блоке, который занимает минимальное к-во кеш-линий, ускорит эти операции.
Обычно не говорят о размещении всех данных в одной кеш-линии. На практике это невозможно. Обычно говорят о "кратном рамере" блока данных.
Как влияет на скорость программы соотношение размера кэшей и используемого размера оперативной памяти?
Особой разницы с первым вопросом не вижу. Если у Вас программа оптимизирована под загрузку кеша, и учитывает его размер, конечно, она будет быстрее работать на железе с большим кешем. Надеюсь, Вы так же вкурсе, что кеши есть разных уровней (level1, level2, level3, кеш данных, кеш инструкций), их размеры могут отличаться, и скорость доступа к ним, естественно, тоже разная, т.е. поле для оптимизаций - обширное настолько, насколько Вам хватит терпения перебирать различное железо для экспериментов и фантазии на реализацию алгоритмов.
Есть ли смысл обработку данных во много раз больших размера кэшей проводить блоками? Каждый блок меньше размера кэша.
Нет. Есть смысл распараллелить эту обработку между ядрами таким образом, чтобы у каждого ядра эти порции данных, по возможности, не пересекались. Вот тут кратность размера обрабатываемого блока данных имеет большое значение. Порции следует разделить так, чтобы кеш-линии одного ядра не пересекались с кеш-линиями другого.
В ассемблере есть инструкции по записи в оперативную память без использования кэша. Какой в этом смысл?
Смысл в том, чтобы не делать лишнюю работу: если Вам нужно только переместить блок данных, и Вы не обращаетесь к нему для каких-то рассчетов, то и в кеш его нет необходимости загружать.
Имеет различие для быстродействия насколько далеко от первоначального адреса программа берет следующие 128 бит?
Нет, не должно. На 64битных кеш-линиях, по крайней мере, последовательное чтение 128битных значений в непараллельной программе выгоды не дает. В этом случае определяющим будет размер кеша и параллелизм между ядрами (кооперация кэшей). Ждите или ищите 256битных кеш линий, по крайней мере :)

вторник, 2 октября 2018 г.

Можно ли на языках C/C++ определить целочисленное переполнение?

Часто в контексте безопасного программирования упоминают проблему целочисленного переполнения (integer overflow). А возможно ли отловить эту ситуацию в C/C++ коде? Ведь процессоры (по крайней мере x86) имеют среди EFLAGS флаг Overflow Flag, получается, что чисто технически такая возможность имеется, да и оверхэд на проверку флага в регистре не должен быть большим (думается так). Либо проблема в постановке вопроса и переполнение нужно не отлавливать, а не допускать никогда? Значит ли последнее, что целочисленные типы принципиально не подходят для любых вычислений?
P.S. Вопрос возник в связи с тем, что в существующем коде (наблюдаемом мной) широко используется int для арифметических расчетов и ничто не защищает от переполнений, гипотетически они могут остаться незамеченными (логикой). И в большинстве случаев с большой вероятностью все в норме (реальные значения невелики), но иногда...


Ответ

Официально в стандарте языка записано, если я не путаю, что переполнение знаковых целочисленных типов зависит от реализации (может генерироваться исключение, может игнорироваться), а беззнаковых игнорируется - значение приводится в представимый диапазон.
Т.е. получается примерно так - сам C/C++ встроенных переносимых средств обнаружения переполнения не имеет.
Но всегда можно использовать дополнительные методы, которые позволят по данным операндам выяснить, будет ли переполнение при выполнении операции. Масса таких вещей описана в книге Г. Уоррена Алгоритмические трюки для программистов
Ну и как иллюстрация, насколько легко пропустить это переполнение - у Страуструпа в Программирование. Принципы и практика... есть одна программка, в которой вычислялся ряд для ex, что ли - и она у него в разнос шла. Он честно написал, что раньше, мол, я думал, что это связано с потерей точности в double, и вроде даже так и написал в первом издании книги, а потом ко второму изданию дошло, что на самом деле это переполнение целочисленных значений при вычислении факториала. Если уж даже Страуструп... :)