diff --git a/README.md b/README.md index d5238f5..a8800ec 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # alien -Библиотека для типобезопасной работы с прямым доступом к памяти через -примитивы Foreign Function & Memory API из JDK 22. +Alien is a library for type safe direct memory access using structures introduced in +JDK 22 [Foreign Function & Memory API](https://openjdk.org/jeps/454). ```scala Region.fresh.shared { implicit region => @@ -15,22 +15,22 @@ Region.fresh.shared { implicit region => ``` -# Начало работы +# Getting started -Для использования библиотеки, добавьте следующую зависимость в файл `build.sbt` вашего проекта: +Add alien as a dependency to your `build.sbt`: ```scala libraryDependencies += "com.yandex.classifieds" %% "alien-memory" % "0.1.0" ``` -### Поддерживаемые версии +### Compatibility -Библиотека доступна для использования в следующих версиях: +Alien can be used with - Scala 2.13 -- Java 21 и Java 22 +- Java 21 or Java 22 -На момент выпуска Java 21, [Foreign Function & Memory API](https://openjdk.org/jeps/442) находилось в стадии превью, поэтому рекомендуется использовать предпочтительную версию Java 22. +At the time of the release of Java 21, the Foreign Function & Memory API was in the preview stage, so it is recommended to use Java 22 with a final version of FFM. -# Документация +# Docs -Документацию можно найти [здесь](https://yandexclassifieds.github.io/alien-memory/). +More detailed docs can be found [here](https://yandexclassifieds.github.io/alien-memory/). diff --git a/docs/index.yaml b/docs/index.yaml index 529122e..fe8c2d4 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -1,18 +1,18 @@ title: Alien Memory -description: Библиотека для типобезопасной работы с прямым доступом к памяти через примитивы Foreign Function & Memory API из JDK 22. +description: Alien is a library for type safe direct memory access using Foreign Function & Memory API from JDK 22. meta: title: Index noIndex: true links: - - title: Начало работы - description: Добавьте библиотеку в зависимости. + - title: Getting started + description: Dependencies and compatibility href: pages/getting_started.md - title: Memory - description: Аллокации памяти на хипе и вне его. + description: Heap and off-heap allocations href: pages/memory.md - title: Layouts - description: Описание разметки памяти. + description: Memory layouts href: pages/layouts.md - title: Regions - description: Менеджмент памяти. + description: Memory management href: pages/regions.md diff --git a/docs/pages/getting_started.md b/docs/pages/getting_started.md index f473883..146aba6 100644 --- a/docs/pages/getting_started.md +++ b/docs/pages/getting_started.md @@ -1,17 +1,15 @@ -# Начало работы +# Getting started -### Добавление в зависимости - -Для использования библиотеки, добавьте следующую зависимость в файл `build.sbt` вашего проекта: +Add alien as a dependency to project's `build.sbt`: ```scala libraryDependencies += "com.yandex.classifieds" %% "alien-memory" % "0.1.0" ``` -### Поддерживаемые версии +### Compatibility -Библиотека доступна для использования в следующих версиях: +Alien can be used with - Scala 2.13 -- Java 21 и Java 22 +- Java 21 or Java 22 -На момент выпуска Java 21, [Foreign Function & Memory API](https://openjdk.org/jeps/442) находилось в стадии превью, поэтому рекомендуется использовать предпочтительную версию Java 22. +Java 21 release contains preview version of [Foreign Function & Memory API](https://openjdk.org/jeps/442) thus using Java 22 with final version of FFM is recommended. diff --git a/docs/pages/layouts.md b/docs/pages/layouts.md index 1a9cf5b..97b2e4d 100644 --- a/docs/pages/layouts.md +++ b/docs/pages/layouts.md @@ -1,62 +1,61 @@ # Layout -Layout - описание схемы данных, хранящихся в памяти. В основе лежит `java.lang.foreign.MemoryLayout`. +Layout is a description of data structures stored in foreign memory. It is based on `java.lang.foreign.MemoryLayout`. -Схема собирается из кирпичиков: +Layout has the following building blocks: -- `Value` - элементарный слой для целых чисел, символов и т.д. +- `Value` - a basic layer for primitive types: numbers, characters, etc ```scala - val a = Values.Long // слой под Long значения + val a = Values.Long // layer for Long type values ``` -- `BoundedSequence` - контейнер для последовательности данных одного типа. - Упрощенно можно собирать последовательности с помощью оператора *, это повышает читабельность для матриц. +- `BoundedSequence` - container for sequences of same-type data. + Sequences can be built using `*`, which makes matrix data definitions more readable ```scala - val a = Values.Long * 2 * 5 // матрица с 5 строчками и 2 столбцами + val a = Values.Long * 2 * 5 // matrix with 5 rows * 2 columns ``` -- `Aligned` - контейнер группы данных, который задает выравнивание в байтах для нижележащих данных. +- `Aligned` - container for data sequence that specifies byte alignment for the underlying data. ```scala - val aligned = (Values.Long * 2 * 5).align[`2^8`] // обеспечивает выделение памяти по адресу, кратному 256. + val aligned = (Values.Long * 2 * 5).align[`2^8`] // allocated memory address is a multiple of 256. ``` -- `Padding` - "Пробел" в памяти, который не хранит данные, но добавляет смещение в байтах перед следующим элементом. +- `Padding` - A "gap" in memory that doesn't store any meaningful data, but adds a byte offset before the next element. ```scala - val onlyPadding = Padding(32) // 32 пустых байта + val onlyPadding = Padding(32) // 32 empty bytes ``` -- `Dynamic` - контейнер группы данных. Выделяются два типа: структуры и объединения. - - [Структуры](https://ru.wikipedia.org/wiki/Структура_(язык_Си)) записывают в память данные своих полей последовательно( - то же самое что и struct из C). +- `Dynamic` - container for a group of data. There are 2 types: structures and unions. - [Объединения](https://ru.wikipedia.org/wiki/Объединение_(структура_данных)) же могут содержать что-то одно из 2 (то же - самое что и union из C) + [Structures](https://en.wikipedia.org/wiki/Struct_(C_programming_language)) has data of their fields written sequentially + (same as struct in C). - В alien для конструирования dynamic layout используются операторы: + [Unions](https://en.wikipedia.org/wiki/Union_type), on the other hand, can contain one of two values (same as union from C) + + alien has the following constructs to build dynamic layout: ```scala - <>: - дополнение текущего объединения другим объединением + <>: // adds this union to existing union - >> - инициализация слоя: переданный в такой конструктор name := layout теперь считается структурой или обьеденением из одного элемента + >> // initialize layer: name := layout passed to this constructor is now a union with 1 element - >>: - дополнение текущей структуры другой структурой + >>: // add new structure to existing structure ``` - Части структур и объединений нужно именовать с помощью оператора `:=`. - Пример: + Parts of unions and structures may be named using `:=`. + + For example: ```scala - val a = "matrix" := Values.Long * 7 * 2 // матрица с именем matrix - val b = "array" := Values.Long * 5 // массив с именем array - val layout: ("matrix" := BoundedSequence[BoundedSequence[Value[Long]]]) >>: - (>>["array" := BoundedSequence[Value[Long]]]) = a >>: b // структура из матрицы и массива + val a = "matrix" := Values.Long * 7 * 2 // matrix named 'matrix' + val b = "array" := Values.Long * 5 // array named 'array' + val layout: ("matrix" := BoundedSequence[BoundedSequence[Value[Long]]]) >>: + (>>["array" := BoundedSequence[Value[Long]]]) = a >>: b // structure containing matrix and array ``` ### Note -При построении `Layout` нужно учитывать, что действует обратная (правая) ассоциативность. Например, при создании -матрицы + When constructing `Layout` please, note, that it's dimensions have _reverse order_ + compared to mathematics or programming languages ```scala -val memL = "bitmap" := (Values.Long * height) * width +val memL = "bitmap" := (Values.Long * columns) * rows // creates matrix rows*columns ``` -Сначала создается одна строка из height ячеек, затем эта строка масштабируется по ширине (width). \ No newline at end of file diff --git a/docs/pages/memory.md b/docs/pages/memory.md index bb7fa25..ebbdb63 100644 --- a/docs/pages/memory.md +++ b/docs/pages/memory.md @@ -1,22 +1,24 @@ # Memory -```Memory[L <: LayoutType, R <: Global]``` - обертка над ```java.lang.foreign.MemorySegment```. Позволяет выделять -off-heap память для схем данных описанных через Layout. Первый параметр `L` - это тип layout для которого аллоцирована -эта память. Второй параметр `R` - это тег региона внутри которого был аллоцирован этот сегмент памяти. +```Memory[L <: LayoutType, R <: Global]``` is a wrapper over ```java.lang.foreign.MemorySegment```. Allows to allocate +off-heap memory for data structures defined with [Layout](./layouts.md). -Для доступа к сегменту памяти следует использовать `MemoryPtr*` +`L` - layout type for which this memory is allocated. +`R` - region tag where this segment should be allocated -## Аллокации +To access memory segment use `MemoryPtr*` -Все аллокации осуществляются с помощью ##malloc## установленного в системе. -> По умолчанию все аллоцированные области заполняются нолями, чтобы этим пренебречь укажите ##JVM## флаг: +## Allocations + +All allocations use system's ##malloc## . +> By default, all allocated regions are initialized with 0s. To override this behaviour use ##JVM## parameter: ```-J-Djdk.internal.foreign.skipZeroMemory=true``` -Для аллокации сегментов есть следующие методы +There are following methods for different allocations ### ##allocate## -Использует методы аллокации региона лежащего в имплицитном контексте. +Allocations use implicit region's properties ```scala implicit val region = Region.fresh.newShared() @@ -27,7 +29,7 @@ region.close() ### ##allocateGC## -Память проаллоцируется в offheap, но сборкой ее будет заниматься указанный сборщик мусора ##JVM##. Все такие сегменты памяти будут иметь тайп-тег ```Global```. +Off-heap memory is allocated, but ##JVM## garbage collector will manage it. All such memory segments will have ```Global``` TypeTag. ```scala val layout = ("a" := Values.Int) >>: ("b" := Values.Int) @@ -37,7 +39,7 @@ val memory = Memory.allocateGC(layout) // память лежит в offheap, н ### ##allocateStatic## -Аллоцирует память, которая не удалится никогда и будет жить до окончания программы. Все такие сегменты памяти будут иметь тип-тег ```Global```. +Allocated memory will never be collected and will be allocated as long as program runs. All such memory segments will have ```Global``` TypeTag. ```scala val layout = ("a" := Values.Int) >>: ("b" := Values.Int) @@ -46,7 +48,7 @@ val memory = Memory.allocateStatic(layout) ### ##allocateManual## -Аллоцирует память с помощью переданной ```java.lang.foreign.Arena```. Все такие сегменты памяти будут иметь тип-тег ```Global```. +Allocates memory using the passed ```java.lang.foreign.Arena```. All such memory segments will have ```Global``` TypeTag. ```scala val layout = ("a" := Values.Int) >>: ("b" := Values.Int) @@ -56,7 +58,7 @@ val memory = Memory.allocateManual(layout, Arena.ofAuto()) ### ##allocateGlobal## -Аллоцирует память с помощью региона с тегом ```Global``` взятым из имплицитного контекста. Все такие сегменты памяти будут иметь тип-тег ```Global```. +Allocates memory using Region tagged ```Global``` from implicit context. All such memory segments will have ```Global``` TypeTag. ```scala val layout = ("a" := Values.Int) >>: ("b" := Values.Int) @@ -64,9 +66,10 @@ val memory = Memory.allocateGlobal(layout) ``` -## Байт-буферы +## Byte buffers -Каждый объект типа `Memory` можно преобразовывать в соответсвующий `ByteBuffer` и обратно. Обе структуры будут указывать на один и тот же сегмент памяти, вне зависимости от того где он лежит. +Objects with type `Memory` can be transformed to and back from `ByteBuffer`. Both structures will point to the same +memory segment regardless where it was located ```scala val heapMemory = Memory.ofArray(new Array[Byte](128)) diff --git a/docs/pages/pointers.md b/docs/pages/pointers.md index 749c922..beddf5a 100644 --- a/docs/pages/pointers.md +++ b/docs/pages/pointers.md @@ -1,60 +1,65 @@ # Path и MemoryPtr -Для того чтобы получить доступ к самой памяти, нужно получить относительный указатель на нее `MemoryPtr*`. Он используется для того чтобы эффективно обращаться в память по нужным смещениям. `Path` используется для навигации по сложной структуре данных, описанных через `Layout`. -Навигация начинается с корневого Layout. Углубляемся внутрь через оператор `/`, аналогично файловой системе. +To access allocated memory, you need to obtain a relative pointer to it `MemoryPtr*` +It is used to efficiently access memory at specific offsets. + +`Path` is used to navigate complex data structures defined via `Layout`. +Navigation starts with the root Layout. Next layers are accessed using `/`, same as in file systems. + +#### Examples -#### Пример: ```scala val structLayout = ("a" := Values.Long) >>: - ("sequence" := Values.Long * 123) >>: - ("b" := Values.Char) -val aPtr = structLayout / "a" / $ // MemoryPtr0, указатель на поле a -val sequencePtr = structLayout / "sequence" / % / $ // MemoryPtr1, указатель с размерностью 1, параметризован индексом массива sequence -val sequence2Ptr = structLayout / "sequence" / 12 / $ // MemoryPtr0, указатель на 14 элемент массива sequence + ("sequence" := Values.Long * 123) >>: + ("b" := Values.Char) +val aPtr = structLayout / "a" / $ // MemoryPtr0 referencing 'a' field +val sequencePtr = structLayout / "sequence" / % / $ // MemoryPtr1, size-1 pointer, parametrized with index in 'sequence' array +val sequence2Ptr = structLayout / "sequence" / 12 / $ // MemoryPtr0, referencing 12th element of 'sequence' array ``` -## Таблица термов для `Path` -| Терм | Применим на Layout | Куда углубляемся | -|:----------------:|:------------------:|:-----------------------------------------------------------------------------------:| -| `"_<имя слоя>_"` | Dynamic | В слой с этим именем | -| `i` | Sequence | В i-й элемент последовательности | -| `(n, m)` | Sequence | В подмножество элементов, а именно начиная с элемента n, далее каждый m-ный элемент | -| `%` | Sequence | В каждый элементов последовательности | -| `$` | Value | Никуда, конечный слой | +## Terms used with `Path` + +| Term | Layout type | Points to | +|:------------------:|:-----------:|:--------------------------------------------------------------------:| +| `"__"` | Dynamic | Layer with defined name | +| `i` | Sequence | sequence element number i | +| `(n, m)` | Sequence | Subsequence of elements starting with n and then every m'th elements | +| `%` | Sequence | Every element in sequence | +| `$` | Value | nowhere, final layer | -В зависимости от того сколько операторов `%` и `(n, m)` было использовано в `Path` будет возвращен -соответствующий `MemoryPtr*`(существуют `MemoryPtr0`, `MemoryPtr1`, ..., `MemoryPtr9`). +Depending on a number of `%` and `(n, m)` used in `Path` `MemoryPtr*` of the corresponding size will be returned +(there are `MemoryPtr0`, `MemoryPtr1`, ..., `MemoryPtr9`). -#### Больше примеров: +#### More examples ```scala val matrixLayout = Values.Long * 5 * 6 -val matrixPtr = maxtrixLayout / % / % / $ // слой - матрица, не усложненная структурами +val matrixPtr = maxtrixLayout / % / % / $ // matrix layer, without additional structures val namedLayout = "matrix" := (Values.Long * 5 * 6) val matrix = namedLayout / "matrix" / % / % / $ val oneRow = namedLayout / "matrix" / 0 / % / $ val oneColumn = namedLayout / "matrix" / % / 0 / $ -// заполнение матрицы нулями +// filling matrix with zeroes for (i <- 0 until 6) { for (j <- 0 until 5) { matrixPtr.set(memory, 0, i, j) } } -// заполнение одной строки нулями +// filling one line with zeroes for (i <- 0 until 5) { oneRow.set(memory, 0, i) } ``` -## Доступ к памяти +## Accessing memory -Чтение и запись данных осуществляется через объект `MemoryPtr*` и два метода. +Reading from and writing to memory is done via `MemoryPtr*` and two methods: * ```get(memory: Memory[L, R], i1: Long, ...)``` * ```set(memory: Memory[L, R], value: V, i1: Long, ...)``` -Количество индексов в методах соответствует размерности `MemoryPtr*`. Доступ к памяти возможен только для примитивных типов данных, таких как: +Number of indices in methods is the same as `MemoryPtr*` size. Only primitive types can be read from memory, such as: * ##Byte## * ##Short## diff --git a/docs/pages/regions.md b/docs/pages/regions.md index 00db7fa..fa059b3 100644 --- a/docs/pages/regions.md +++ b/docs/pages/regions.md @@ -1,13 +1,14 @@ # Region -Для обеспечения безопасного доступа к памяти каждая выделенная область памяти привязывается к implicit scope, с типом `Region[R <: Global]`. +To ensure safe memory access, every allocated memory region is bound to an implicit scope with `Region[R <: Global]` type. -`Region[R]` представляет собой обертку над `java.lang.foreign.Arena`. Регионы `confined`, `shared` и `slicing` соответствуют аренам из JDK, предоставляя различные уровни ограничения и доступа к выделенным областям памяти. +`Region[R]` is a wrapper over `java.lang.foreign.Arena`. The `confined`, `shared` and `slicing` regions match JDK arenas, +providing various level's of confinement and access to allocated memory. ```scala val layout = Values.Int * 100 val ptr = layout / % / $ -val leakedMemory = Region.fresh.confined { implicit region => +val leakedMemory = Region.fresh.confined { implicit region => val memory = Memory.allocate(layout) for (i <- 0 until 100) { @@ -16,38 +17,44 @@ val leakedMemory = Region.fresh.confined { implicit region => memory } -Region.fresh.confined { implicit region => - ptr.set(leakedMemory, 0, i) // не скомпилируется +Region.fresh.confined { implicit region => + ptr.set(leakedMemory, 0, i) // won't compile } ``` -## Теги +## Tags + +Each allocated memory region has a type tag `R`, matching a type parameter in `Region[R]`. All allocations made with +`Region[R]` will have the same type tag. This type `R` serves as a label for each memory region. Every tag is a descendant +of the `Global` tag which is applied to memory allocations made in an unsafe mode, not managed by GC and that are never +deallocated. See [documentation of Arenas for more detailed info](https://docs.oracle.com/en/java/javase/22/core/memory-segments-and-arenas.html) -Каждая выделенная область памяти имеет тип-тег `R`, который соответствует параметру в `Region[R]`. Все аллокации сделанные с помошью `Region[R]`, будут иметь тот же тип-тег. Этот тип `R` является меткой для каждого региона памяти. Каждая метка наследуется от метки `Global`, которая применяется к выделениям памяти, сделанным не в безопасном режиме, выделенным сборщиком мусора или не подлежащим удалению. ### Fresh -Вы можете использовать метод `Region.fresh`, который автоматически генерирует уникальную метку во время компиляции, что полезно, если вы создаете множество регионов и хотите избежать конфликтов имен. +`Region.fresh` generates a unique label at compile time. That's particularly useful if many regions are going to be allocated +to avoid name conflicts. -```scala -Region.fresh.confined { implicit region => - val memory = Memory.allocate(layout) // Memory[LayoutType, RandomComiletimeTag] +```scala +Region.fresh.confined { implicit region => + val memory = Memory.allocate(layout) // Memory[LayoutType, RandomCompiletimeTag] } ``` ### Named -Вы также можете явно указывать имена тегов. +Tag names can be specified explicitly. -```scala -Region.named["ScopeName"].confined { implicit region => +```scala +Region.named["ScopeName"].confined { implicit region => val memory = Memory.allocate(layout) // Memory[LayoutType, NamedMark["ScopeName"]] } ``` ### Global -Глобальный тип-тег от которого наседуютяс все остальные теги. Вы можете переопределить глобальный тип-тег с помощью метода `Region.newGlobal`, обернув какую-то область памяти. По умолчанию этот тип соответствует `Arena.global()` из `java.lang.foreign`. +Parent type tag for all the other tags. Global type tag can be overrided using `Region.newGlobal` method, providing +a specific memory segment. By default, same as `Arena.global()` from `java.lang.foreign`. ```scala val customArena = ... @@ -57,13 +64,15 @@ val memory = Memory.allocateGlobal(layout) ``` -## Арены +## Arenas -Внутри каждого региона находится объект `Arena` из пакета `java.lang.foreign`. Каждая арена определяет свои уровни ограничений и имеет различные стратегии выделения и освобождения памяти. +Every `Region` contains `java.lang.foreign.Arena`. There are several types of arenas, each with their own confinement levels +and strategies for allocation and de-allocation. For detailed info, please refer to [official Java FFM API docs](https://docs.oracle.com/en/java/javase/22/core/memory-segments-and-arenas.html) ### Confined -Все аллокации памяти, выполненные с помощью такого региона, будут доступны только из потока, который их произвел. Такие аллокации и освобождения памяти производятся быстрее по сравнению с ареной, доступной из разных потоков. +All allocations, made with such region will be available only from owner thread. +Such allocations and de-allocations are faster than shared arenas. ```scala @@ -72,7 +81,7 @@ Region.fresh.confined { implicit region => ptr.get(memory, 0) val anotherThread = new Thread { override def run(): Unit = - ptr.get(memory, 0) // выкинет ошибку + ptr.get(memory, 0) // throws error } anotherThread.start() anotherThread.join() @@ -82,8 +91,7 @@ Region.fresh.confined { implicit region => ### Shared -Все аллокации памяти, выполненные с помощью такого региона, будут доступны из любого потока. Такие аллокации и освобождения памяти производятся медление по сравнению с ареной, доступной из одного потока. - +Shared region allocations are available for every thread. Allocations and de-allocations are slower compared to confined regions. ```scala @@ -92,7 +100,7 @@ Region.fresh.shared { implicit region => ptr.get(memory, 0) val anotherThread = new Thread { override def run(): Unit = - ptr.get(memory, 0) // все будет работать + ptr.get(memory, 0) // works fine } anotherThread.start() anotherThread.join() @@ -101,24 +109,25 @@ Region.fresh.shared { implicit region => ### Slicing -`Slicing` будет выполнять аллокации последовательно на заранее выделенном участке памяти. Такой участок памяти может находиться как в `confined`, так и в `shared` арене. Однако сами аллокации в такой арене не являются потокобезопасными. +`Slicing` will do consecutive allocations within the pre-allocated memory region. Such region can be in either `confined` or +`shared` Arena. Such allocations are not thread safe. ```scala val layout1 = Values.Long * 16 val layout2 = Values.Char * 12 -Region.fresh.confinedSlicing(128, 8) { implicit region => - val memory1 = Memory.allocate(layout1) // в начале преаллоцированной памяти разместится первый сегмент - val memory2 = Memory.allocate(layout2) // этот сегмент разместится сразу после +Region.fresh.confinedSlicing(128, 8) { implicit region => + val memory1 = Memory.allocate(layout1) // the first segment will be at the beginning of pre-allocated memory + val memory2 = Memory.allocate(layout2) // this segment will be right after the first } ``` ### Custom -Так-же в регион можно обернуть любую собственную арену. +Region can be used with any custom arena. ```scala val newCustomArena = new Arena { ... } -Region.fresh.custom(newCustomArena) { implicit region => - val memory = Memory.allocate(layout) // в начале преаллоцированной памяти разместится первый сегмент +Region.fresh.custom(newCustomArena) { implicit region => + val memory = Memory.allocate(layout) } -``` \ No newline at end of file +```