Страницы

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

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

среда, 26 февраля 2020 г.

Реестр Windows x64 и x86

#windows #com #x64 #cpp


В чем различие между реестром в Windows x64 и Windows x86? Я регистрирую 32-битный
компонент в Windows 7 x64. После регистрации его не получается вызвать, при этом регистрация
проходит успешно.     


Ответы

Ответ 1



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

суббота, 1 февраля 2020 г.

Что влияет на количество памяти занимаемой разными типами данных

#cpp #x64 #integer


Как я понимаю, обычно тип integer весит 4 байта, в том числе и unsigned integer. 
Но есть случаи где в зависимости от разрядности процессора integer может иметь как
2 байта так и 8. 
Не понимаю, как разрядность влияет на размер целочисленного типа и что это меняет
для разработчика кроме того, что в него можно вместить шире диапазон чисел(или меньший)?

UPD

Что значить переводить программу из x32 в x64? Грубо говоря, это использования типов
x64? Если я правильно понимаю, причина по которой программа написанная под x64 не будет
работать на x32, это оверхед по память в типах данных? 
Почему тогда мы можем допустим в Visual Studio выбрать под какую разрядность делать
build? Как это будет влиять на размер самой программы и размер, который будут забирать
обычные типы данных? 
    


Ответы

Ответ 1



Есть такая таблица соответствия размеров фундаментальных типов в зависимости от используемой модели данных: Модель данных в общем случае не привязана непосредственно к железу (разрядности процессора), и зависит от используемой операционной системы (если она присутствует). При этом должно быть очевидно, что на 32bit процессоре не удастся запустить 64bit ОС, но обратное вполне себе имеет место быть. Размер фундаментальных типов в конкретной реализации компилятора (т.е. под конкретную платформу) подбирается так, чтобы обеспечивать оптимальную производительность. Помимо увеличения диапазона значений фундаментальных типов, бОльшая разрядность позволяет адресовать больше памяти. Например, при 32bit можно адресовать не более 4Gb, а при 64bit уже 16Eb. Когда говорят о переводе программы с x32 на x64 имеют в виду, что должна обеспечиваться в первую очередь работоспособность программы в 64bit окружении. Т.е. что из-за изменения размеров типов ничего не сломается. Такого рода проблемы часто возникают, когда при первичной разработке закладываются на фиксированный размер фундаментальных типов, например, что указатель всегда будет 4 байта. А когда при сборке в 64bit режиме он "вдруг" становится в 2 раза больше программа просто начинает крашиться. Поэтому, чтобы программа была переносима между x32 ↔ x64 при необходимости обеспечить нужную разрядность вычислений стоит полагаться на типы из .

Ответ 2



Чтобы это понять, лучше написать пару программ на ассемблере, например простейший калькулятор. Основная причина разных размеров типа int, обычно кроется в количестве байт машинного слова архитектуры процессора (т.е. размер регистров), а также непосредственно в компиляторе. Обычно представление следующее: 8 бит int = 16 бит 16 бит int = 16 бит 32 бит int = 32 бита 64 бит int = 32 бита Необходимость 64 разрядных операционных систем и как следствие использования архитектур процессора возникла из-за роста памяти. Дело в том, что 32 разрядная ОС позволяет адресовать только 4 гигабайта оперативной памяти, т.к. 2^32 = 4294967296 байт. Регистры процессора архитектуры x86 AL, BL, CL, DL - 8 бит AX, BX, CX, DX - 16 бит EAX, EBX, ECX, EDX - 32 бит

среда, 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

суббота, 4 января 2020 г.

Как размещаются стековые переменные в C

#c #gdb #x64


Здравствуйте. Я копаюсь с помощью отладчика в коде, и пытаюсь понять по какому принципу
GCC (C-compiler) размещает переменные (локальные) в стеке. Сначала я думал что он проталкивает
их в том же порядке:



то есть встретил int auth_flag и толкает в стек, переменная соответственно получает
больший адрес (т.к. стек растёт в направлении младших адресов). Потом password_buffer
уже получает адрес меньше. Но когда я поменял их местами -- ничего не изменилось:



auth_flag по прежнему получает адрес больше и (т.е. стоит ниже в стеке чем password_buffer).
Я бы подумал что так и должно быть, но этот пример взял с книги, и там, когда автор
меняет местами переменные, -- они меняются местами и в стеке. Книга за 2010 год, x32
архитектура, возможно GCC моложе. 

Поясните почему так происходит, почему компилятор не даёт самому выбирать порядок
размещения переменных и чем он "руководствуется" когда размещает переменные. 

GCC version: 4.9.2
    


Ответы

Ответ 1



Посмотрите на это с другой стороны. Если компилятор поменяет местами эти две переменные в стеке, это повлияет как-то на Ваш код? ответ - скорее всего Вы даже не узнаете об этом. А раз так, значит компилятор может расставить в стеке переменные так, как ему кажется правильным. И эти правила порой такие сложные, что только разработчики компилятора и процессора могут их аргументировать. Но есть случаи, когда компилятор может переставить ещё хитрее. Например, в так называемом RVO. Если в функции создается объект и он из нее возвращается, то компилятор может это увидеть и создать объект ещё в стеке вызывающей функции. Таким образом, экономится вызов конструктора копирования и деструктора. Но вернемся к вопросу, почему компилятор все-таки переставляет переменные? Он может ставить переменные так, что бы они были выровнены по границе в 16 байт - таким образом получится ускорение.

среда, 25 декабря 2019 г.

Права доступа к страницам в linux x86_64

#linux #memory #x64 #x86_64 #виртуальная_память


Изучаю как устроена атака на переполнение буфера. Написал вот такую программу

typedef void(*Function)();
int main() {
    const char* shell = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90";
    Function f = (Function)shell;
    f();
    return 0;
}


shell - пара десятков nop-ов.(0x90 - опкод nop-a).
Пошагово прошелся по ней в gdb и она падает от segfault при вызове f, а точнее при
выполнении инструкции

callq    *%rdx


При этом в rdx записан адрес 0x4005d8( Именно по этому адресу располагаются nop-ы).
И все это можно было бы объяснить тем, что адрес 0x4005d8 относится к странице у которой
не выставлено право на исполнение, но:

1) Вот вывод утилиты pmap для этого процесса

26040:   ./main
0000000000400000      4K r-x-- main
0000000000600000      4K r---- main
0000000000601000      4K rw--- main
0000000001c65000    132K rw---   [ anon ]
00007f40de3ce000   1792K r-x-- libc-2.23.so
00007f40de58e000   2048K ----- libc-2.23.so
00007f40de78e000     16K r---- libc-2.23.so
00007f40de792000      8K rw--- libc-2.23.so
00007f40de794000     16K rw---   [ anon ]
00007f40de798000    152K r-x-- ld-2.23.so
00007f40de995000     12K rw---   [ anon ]
00007f40de9bd000      4K r---- ld-2.23.so
00007f40de9be000      4K rw--- ld-2.23.so
00007f40de9bf000      4K rw---   [ anon ]
00007ffd8e874000    132K rw---   [ stack ]
00007ffd8e924000     12K r----   [ anon ]
00007ffd8e927000      8K r-x--   [ anon ]
ffffffffff600000      4K r-x--   [ anon ]
 total             4356K


И если я правильно понимаю что тут написано, то у диапазона адресов 0х400000-0х60000
есть право на исполнение.

2) Что подтверждает предыдущий пункт: адрес функции main был 0х400507 что довольно
близко к последовательности nop-ов.

Однако если в rdx положить адрес main то никакого segfault-а не происходит. Проясните,
пожалуйста, что не так.
    


Ответы

Ответ 1



Учитывая, что сегмент константных данных (а именно туда попадает const char* shell) отображается без права на исполнение, скорее всего, вы столкнулись с ASLR. ASLR (Address Space Layout Randomisation, разбиение адресного пространства случайным образом) — это механизм, который как раз и направлен против шелл-атак, полагающихся на фиксированный адрес тех или иных участков памяти. Он разбрасывает программу, библиотеки и стек случайным образом по всей виртуальной памяти, доступной программе, причём из запуска в запуск результат подобного распределения отличается. При первом запуске (под gdb) адрес 0x4005d8 попадал в сегмент неизменяемых данных программы (на это указывают права доступа сегмента, да и const-переменные находятся только там). При втором же запуске (для pmap) по тому адресу стал располагаться сегмент кода. Если вы хотите получить согласованный результат, запускайте pmap параллельно с gdb, в другом окне или вкладке консоли. (Немного о праве на исполнение блока памяти) Начиная с версии 2.3.23, Linux имеет встроенную поддержку DEP. DEP (Data Execution Prevention, предотвращение выполнения данных) — это механизм, направленный на запрет исполнения кода там, где должны лежать данные (к примеру, в стеке или сегменте данных). Он работает за счёт того, что к уже существующим флагам прав доступа к странице (чтение, запись, доступ только из режима ядра и т. д.) был добавлен ещё один (запрет на исполнение), который операционная система устанавливает для всех страниц, не соответствующих сегментам кода исполняемых файлов (а main() как раз и лежит в подобном сегменте). Единственное, где может сработать ваш шелл — это в областях памяти, созданных при помощи mmap() с одновременно выставленными флагами PROT_EXEC и PROT_WRITE.

пятница, 13 декабря 2019 г.

Невозможно выделить память более 2 Гб для класса TMemoryStream даже в 64-битном режиме

#delphi #потоки_данных #x64


В Delphi, в том числе в последних версиях (могу проверить только до 10.1 Berlin,
хотя негативные отзывы есть и о 10.2 Tokyo), класс TMemoryStream не может выделить
под данные более 2 Гб даже при компиляции в 64-битном режиме. Попытки записать в поток
большие объёмы приводят к ошибке "Out of memory while expanding memory stream".  

Можно ли решить эту проблему без создания собственного класса, реализующего поток
в памяти?
    


Ответы

Ответ 1



Давайте взглянем в определение класса TMemoryStream в файле System.Classes.pas: TMemoryStream = class(TCustomMemoryStream) private FCapacity: Longint; procedure SetCapacity(NewCapacity: Longint); protected function Realloc(var NewCapacity: Longint): Pointer; virtual; property Capacity: Longint read FCapacity write SetCapacity; public destructor Destroy; override; procedure Clear; procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure SetSize(const NewSize: Int64); override; procedure SetSize(NewSize: Longint); override; function Write(const Buffer; Count: Longint): Longint; override; function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override; end; Сразу отмечаем, что для адресации памяти используются переменные типа longint, что, естественно, объясняет невозможность использования более 2 Гб. Более того, если взглянуть на методы Read (их два) непосредственного предка TMemoryStream - TCustomMemoryStream: function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; , то заметим, что результат может быть посчитан неправильно. Поэтому решением было: заменить тип переменных внутри TMemoryStream на NativeInt и Int64, в том числе локальных переменных, а методы TCustomMemoryStream.Read переписать, например, так: function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff; Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end; Это позволяет работать с объёмами памяти более 2 Гб (тестовый пример спокойно обработал 5-гигабайтный файл, полностью скопировав его в поток. Для тех, кто захочет внести изменения у себя в System.Classes, напомню, что предварительно необходимо сохранить резервную копию этого файла - на всякий случай :) Update 1 Для тех, кто по какой-то причине не хочет идти по предложенным выше вариантам, могу предложить класс TSegmentedMemoryStream - он доступен для скачивания и работает (проверено!).

Ответ 2



В текущей версии Delphi (10.2.2), класс объявлен следующим образом: TMemoryStream = class(TCustomMemoryStream) private FCapacity: NativeInt; protected procedure SetCapacity(NewCapacity: NativeInt); virtual; function Realloc(var NewCapacity: Longint): Pointer; virtual; property Capacity: NativeInt read FCapacity write SetCapacity; public destructor Destroy; override; procedure Clear; procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure SetSize(const NewSize: Int64); override; procedure SetSize(NewSize: Longint); override; function Write(const Buffer; Count: Longint): Longint; override; function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override; end; Видно, что они кое-как попытались исправить проблему, но она ещё не до конца исправлена (например, функция Realloc всё ещё завязана на LongInt). В квалити-центре есть несколько тикетов касательно TMemoryStream: (баг, отработан) TCustomMemoryStream and TMemoryStream don't work with very large streams (баг, не отработан) TMemoryStream Realloc not support Int64 For greater than 2G (новая фича, не отработан) TMemoryStream does not support large (> 2 Gb) memory allocations Так что есть надежда, что они наконец сделают в TMemoryStream поддержку 64-х бит.

суббота, 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битных кеш линий, по крайней мере :)

среда, 15 мая 2019 г.

Реестр Windows x64 и x86

В чем различие между реестром в Windows x64 и Windows x86? Я регистрирую 32-битный компонент в Windows 7 x64. После регистрации его не получается вызвать, при этом регистрация проходит успешно.


Ответ

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

пятница, 19 октября 2018 г.

Невозможно выделить память более 2 Гб для класса TMemoryStream даже в 64-битном режиме

В Delphi, в том числе в последних версиях (могу проверить только до 10.1 Berlin, хотя негативные отзывы есть и о 10.2 Tokyo), класс TMemoryStream не может выделить под данные более 2 Гб даже при компиляции в 64-битном режиме. Попытки записать в поток большие объёмы приводят к ошибке "Out of memory while expanding memory stream".
Можно ли решить эту проблему без создания собственного класса, реализующего поток в памяти?


Ответ

Давайте взглянем в определение класса TMemoryStream в файле System.Classes.pas:
TMemoryStream = class(TCustomMemoryStream) private FCapacity: Longint; procedure SetCapacity(NewCapacity: Longint); protected function Realloc(var NewCapacity: Longint): Pointer; virtual; property Capacity: Longint read FCapacity write SetCapacity; public destructor Destroy; override; procedure Clear; procedure LoadFromStream(Stream: TStream); procedure LoadFromFile(const FileName: string); procedure SetSize(const NewSize: Int64); override; procedure SetSize(NewSize: Longint); override; function Write(const Buffer; Count: Longint): Longint; override; function Write(const Buffer: TBytes; Offset, Count: Longint): Longint; override; end;
Сразу отмечаем, что для адресации памяти используются переменные типа longint, что, естественно, объясняет невозможность использования более 2 Гб. Более того, если взглянуть на методы Read (их два) непосредственного предка TMemoryStream - TCustomMemoryStream
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; begin if (FPosition >= 0) and (Count >= 0) then begin Result := FSize - FPosition; if Result > 0 then begin if Result > Count then Result := Count;
Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
, то заметим, что результат может быть посчитан неправильно.
Поэтому решением было: заменить тип переменных внутри TMemoryStream на NativeInt и Int64, в том числе локальных переменных, а методы TCustomMemoryStream.Read переписать, например, так:
function TCustomMemoryStream.Read(var Buffer; Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff; Move((PByte(FMemory) + FPosition)^, Buffer, Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
function TCustomMemoryStream.Read(Buffer: TBytes; Offset, Count: Longint): Longint; var diff: Int64; begin if (FPosition >= 0) and (Count >= 0) then begin diff := FSize - FPosition; if diff > 0 then begin if diff > Count then Result := Count else Result := diff;
Move((PByte(FMemory) + FPosition)^, Buffer[Offset], Result); Inc(FPosition, Result); Exit; end; end; Result := 0; end;
Это позволяет работать с объёмами памяти более 2 Гб (тестовый пример спокойно обработал 5-гигабайтный файл, полностью скопировав его в поток.
Для тех, кто захочет внести изменения у себя в System.Classes, напомню, что предварительно необходимо сохранить резервную копию этого файла - на всякий случай :)
Update 1
Для тех, кто по какой-то причине не хочет идти по предложенным выше вариантам, могу предложить класс TSegmentedMemoryStream - он доступен для скачивания и работает (проверено!).

четверг, 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битных кеш линий, по крайней мере :)