Warning: mkdir(): No space left on device in /var/www/tgoop/post.php on line 37

Warning: file_put_contents(aCache/aDaily/post/unsafecsharp/--): Failed to open stream: No such file or directory in /var/www/tgoop/post.php on line 50
Unity: Всё, что вы не знали о разработке@unsafecsharp P.228
UNSAFECSHARP Telegram 228
Cache Line и Cache Miss

На самом деле это две разные штуки, хоть и имеют схожие названия.
Я постараюсь объяснить просто и понятно, но так, чтобы не опускаться на самый низкий уровень.

Cache Line. Давайте представим, что у вас есть 2 GameObject: на одном есть скрипт Processor, а на втором скрипт - RAM.
В скрипте RAM давайте объявим массив объектов:


public class RAM : MonoBehaviour {
public object[] objects;
}


А у скрипта Processor одно поле

public class Processor : MonoBehaviour {
public object currentObject;
}


Мы хотим, чтобы процессор обрабатывал какой-то объект, мы можем сделать это двумя путями:
1. Как в примере выше: просто объявляем ссылку на объект;
2. Процессор будет только хранить только информацию о том как этот объект получить, но сам хранить ничего не будет.

Какой вариант будет работать быстрее? Очевидно, что первый.

Вот примерно так и работает Cache Line, где currentObject - это не объект, а просто определенного размера кэш. Он один раз загружается из RAM и используется до тех пор, пока не потребуется какой-то другой участок памяти. Поэтому если читать последовательно (например, из массива), то будет задействован кэш процессора, а не оперативка. Отсюда в названии "Line".
Размер кэш линии зависит от процессора, в основном 32, 64 или 128 байт.

Cache Miss. Это вытекает из первого. По сути это момент, когда мы не попадаем в cache line. Рассмотрим пример:


var arr = new int[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
}


При первом обращении к массиву мы получаем Cache Miss и загрузку Cache Line, т.к. нам нужно загрузить данные из памяти. Когда мы доходим до N-го элемента, мы снова делаем новую загрузку и снова получаем Cache Miss. Каждая загрузка занимает существенное время, поэтому минимизация количества Cache Miss дает буст в производтельности.
Поэтому код вида:


var arr2 = new int[arr.Length];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
arr2[i] // обращаемся ко второму массиву
}


Будет постоянно выдавать Cache Miss, т.к. мы загружаем Cache Line заново. На практике вы, конечно, вряд ли заметите разницу, т.к. все же этот процесс довольно быстрый, но при большом количестве обращений в хот частях может дать замедление.

Можно переписать код примерно так:

struct MyStruct {
int a1;
int a2;
}

var arr = new MyStruct[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i].a1 // обращаемся к массиву и получаем a1
arr[i].a2 // обращаемся к массиву и получаем a2
}


То есть мы просто положили 2 переменные рядом в памяти и теперь мы будем получать Cache Miss как и в первом примере, но в 2 раза чаще, т.к. данных на один элемент у нас теперь х2.

Многопоточность. С этим есть некоторые нюансы: каждый поток использует свою кэш линию (каждый честный поток). Наша задача сделать так, чтобы данные не пересекались.

Пример:


arr = new int[10]; // Допустим, мы делаем 10 потоков

Thread(int threadIndex) {
++arr[threadIndex]; // обращаемся к своему индексу для каждого потока
}


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

Что делать? Можно просто расширить массив arr с 10 до 10 * CacheLineSize / sizeof(int). Т.е. мы под каждый поток выделяем область памяти, которая никак не пересекается с другим потоком.
Таким образом, первый поток будет писать в arr[0], второй - в arr[1 * CacheLineSize / sizeof(int)] и т.д.
Т.е. при размере кэш линии в 128 байт, у нас размер массива для 10 потоков будет 320, а потоки будут использовать индексы: 0, 32, 64 и т.д. (с шагом 32, где 32 * sizeof(int) = 128 размер кэш линии).

#cache #memory #cachemiss #cacheline
👍394🤯3🔥2🫡2🥰1



tgoop.com/unsafecsharp/228
Create:
Last Update:

Cache Line и Cache Miss

На самом деле это две разные штуки, хоть и имеют схожие названия.
Я постараюсь объяснить просто и понятно, но так, чтобы не опускаться на самый низкий уровень.

Cache Line. Давайте представим, что у вас есть 2 GameObject: на одном есть скрипт Processor, а на втором скрипт - RAM.
В скрипте RAM давайте объявим массив объектов:


public class RAM : MonoBehaviour {
public object[] objects;
}


А у скрипта Processor одно поле

public class Processor : MonoBehaviour {
public object currentObject;
}


Мы хотим, чтобы процессор обрабатывал какой-то объект, мы можем сделать это двумя путями:
1. Как в примере выше: просто объявляем ссылку на объект;
2. Процессор будет только хранить только информацию о том как этот объект получить, но сам хранить ничего не будет.

Какой вариант будет работать быстрее? Очевидно, что первый.

Вот примерно так и работает Cache Line, где currentObject - это не объект, а просто определенного размера кэш. Он один раз загружается из RAM и используется до тех пор, пока не потребуется какой-то другой участок памяти. Поэтому если читать последовательно (например, из массива), то будет задействован кэш процессора, а не оперативка. Отсюда в названии "Line".
Размер кэш линии зависит от процессора, в основном 32, 64 или 128 байт.

Cache Miss. Это вытекает из первого. По сути это момент, когда мы не попадаем в cache line. Рассмотрим пример:


var arr = new int[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
}


При первом обращении к массиву мы получаем Cache Miss и загрузку Cache Line, т.к. нам нужно загрузить данные из памяти. Когда мы доходим до N-го элемента, мы снова делаем новую загрузку и снова получаем Cache Miss. Каждая загрузка занимает существенное время, поэтому минимизация количества Cache Miss дает буст в производтельности.
Поэтому код вида:


var arr2 = new int[arr.Length];
for (int i = 0; i < arr.Length; ++i) {
arr[i] // обращаемся к массиву
arr2[i] // обращаемся ко второму массиву
}


Будет постоянно выдавать Cache Miss, т.к. мы загружаем Cache Line заново. На практике вы, конечно, вряд ли заметите разницу, т.к. все же этот процесс довольно быстрый, но при большом количестве обращений в хот частях может дать замедление.

Можно переписать код примерно так:

struct MyStruct {
int a1;
int a2;
}

var arr = new MyStruct[1000];
for (int i = 0; i < arr.Length; ++i) {
arr[i].a1 // обращаемся к массиву и получаем a1
arr[i].a2 // обращаемся к массиву и получаем a2
}


То есть мы просто положили 2 переменные рядом в памяти и теперь мы будем получать Cache Miss как и в первом примере, но в 2 раза чаще, т.к. данных на один элемент у нас теперь х2.

Многопоточность. С этим есть некоторые нюансы: каждый поток использует свою кэш линию (каждый честный поток). Наша задача сделать так, чтобы данные не пересекались.

Пример:


arr = new int[10]; // Допустим, мы делаем 10 потоков

Thread(int threadIndex) {
++arr[threadIndex]; // обращаемся к своему индексу для каждого потока
}


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

Что делать? Можно просто расширить массив arr с 10 до 10 * CacheLineSize / sizeof(int). Т.е. мы под каждый поток выделяем область памяти, которая никак не пересекается с другим потоком.
Таким образом, первый поток будет писать в arr[0], второй - в arr[1 * CacheLineSize / sizeof(int)] и т.д.
Т.е. при размере кэш линии в 128 байт, у нас размер массива для 10 потоков будет 320, а потоки будут использовать индексы: 0, 32, 64 и т.д. (с шагом 32, где 32 * sizeof(int) = 128 размер кэш линии).

#cache #memory #cachemiss #cacheline

BY Unity: Всё, что вы не знали о разработке


Share with your friend now:
tgoop.com/unsafecsharp/228

View MORE
Open in Telegram


Telegram News

Date: |

Some Telegram Channels content management tips A new window will come up. Enter your channel name and bio. (See the character limits above.) Click “Create.” Hui said the messages, which included urging the disruption of airport operations, were attempts to incite followers to make use of poisonous, corrosive or flammable substances to vandalize police vehicles, and also called on others to make weapons to harm police. With the administration mulling over limiting access to doxxing groups, a prominent Telegram doxxing group apparently went on a "revenge spree." Telegram channels enable users to broadcast messages to multiple users simultaneously. Like on social media, users need to subscribe to your channel to get access to your content published by one or more administrators.
from us


Telegram Unity: Всё, что вы не знали о разработке
FROM American