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: |

As of Thursday, the SUCK Channel had 34,146 subscribers, with only one message dated August 28, 2020. It was an announcement stating that police had removed all posts on the channel because its content “contravenes the laws of Hong Kong.” Avoid compound hashtags that consist of several words. If you have a hashtag like #marketingnewsinusa, split it into smaller hashtags: “#marketing, #news, #usa. ‘Ban’ on Telegram Just at this time, Bitcoin and the broader crypto market have dropped to new 2022 lows. The Bitcoin price has tanked 10 percent dropping to $20,000. On the other hand, the altcoin space is witnessing even more brutal correction. Bitcoin has dropped nearly 60 percent year-to-date and more than 70 percent since its all-time high in November 2021. Deputy District Judge Peter Hui sentenced computer technician Ng Man-ho on Thursday, a month after the 27-year-old, who ran a Telegram group called SUCK Channel, was found guilty of seven charges of conspiring to incite others to commit illegal acts during the 2019 extradition bill protests and subsequent months.
from us


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