دسترسی به دادههای ساختاریافته فقط با عملیات پایهای میتواند کد پیچیده و دشواری برای نگهداری تولید کند. راهحل مناسبتر، استفاده از Memory Layout ها است. این لایوتها بهینهتر هستند و به نوعهای دادهی بومی پیچیدهتر مثل ساختارهای C دسترسی دارند.
برای مثال، یک ساختار C که یک کسر (Fraction) را توصیف میکند در نظر بگیرید:
struct Fraction {
int numerator;
int denominator;
};
در جاوا میتوانید این ساختار را با MemoryLayout نمایش دهید:
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
متد MemoryLayout.structLayout(MemoryLayout...) یک نمونه از StructLayout برمیگرداند.
لایوت ساختار شامل دو Value Layout از نوع JAVA_INT است: یکی برای صورت (numerator) و دیگری برای مخرج (denominator).
مقدار ازپیشتعریفشدهی ValueLayout.JAVA_INT اطلاعات مربوط به تعداد بایتهای مورد نیاز یک مقدار int در جاوا را شامل میشود.
بیایید جمع دو کسر را با استفاده از MemoryLayout fractionLayout سفارشی محاسبه کنیم. ابتدا یک Arena برای مدیریت حافظهی Off-Heap و تخصیص حافظه برای دو کسر و یک کسر نتیجه ایجاد کنیم:
try (Arena arena = Arena.ofConfined()) {
MemorySegment fraction1 = arena.allocate(fractionLayout);
MemorySegment fraction2 = arena.allocate(fractionLayout);
MemorySegment resultFraction = arena.allocate(fractionLayout);
}
در مرحلهی بعد به دو VarHandle با دسترسی به آفستهای آدرس حافظه نیاز دارید:
try (Arena arena = Arena.ofConfined()) {
MemorySegment fraction1 = arena.allocate(fractionLayout);
MemorySegment fraction2 = arena.allocate(fractionLayout);
MemorySegment resultFraction = arena.allocate(fractionLayout);
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
}
VarHandle یک ارجاع (Reference) به یک متغیر یا خانوادهی پارامتریکی از متغیرها است که بهصورت پویا و با نوع قوی (Strongly Typed) کار میکند. این شامل فیلدهای استاتیک، فیلدهای غیراستاتیک، عناصر آرایه یا اجزای یک ساختار دادهی Off-Heap میشود.
فراخوانی PathElement.groupElement("numerator") یک لایوت حافظه با نام numerator بازیابی میکند.
مرحلهی بعدی، تنظیم مقادیر کسرها با فراخوانی VarHandle.set(java.lang.Object...) است:
try (Arena arena = Arena.ofConfined()) {
MemorySegment fraction1 = arena.allocate(fractionLayout);
MemorySegment fraction2 = arena.allocate(fractionLayout);
MemorySegment resultFraction = arena.allocate(fractionLayout);
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
numeratorHandle.set(fraction1, 0, 1);
denominatorHandle.set(fraction1, 0, 3);
numeratorHandle.set(fraction2, 0, 1);
denominatorHandle.set(fraction2, 0, 2);
}
در مثال numeratorHandle.set(fraction1, 0, 1)، متد set سه آرگومان دارد:
fraction1 Memory Segmentای است که مقدار در آن تنظیم میشود.0 آفست پایه است. با تزریق محاسبهی آفست اضافی در VarHandle میتوانید عملیات دسترسی پیچیدهتری تعریف کنید.1 مقدار واقعی برای تنظیم است.به همین ترتیب، میتوانید با VarHandle.get(java.lang.Object...) به مقادیر دسترسی پیدا کنید و در نهایت نتیجه را محاسبه کنید:
import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
import static java.lang.foreign.MemoryLayout.*;
final MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment fraction1 = arena.allocate(fractionLayout);
MemorySegment fraction2 = arena.allocate(fractionLayout);
MemorySegment resultFraction = arena.allocate(fractionLayout);
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
numeratorHandle.set(fraction1, 0, 1);
denominatorHandle.set(fraction1, 0, 3);
numeratorHandle.set(fraction2, 0, 1);
denominatorHandle.set(fraction2, 0, 2);
int n1 = (int) numeratorHandle.get(fraction1, 0);
int d1 = (int) denominatorHandle.get(fraction1, 0);
int n2 = (int) numeratorHandle.get(fraction2, 0);
int d2 = (int) denominatorHandle.get(fraction2, 0);
// Add fractions: resultNumerator = (n1 * d2) + (n2 * d1);
int resultNumerator = (n1 * d2) + (n2 * d1);
int resultDenominator = d1 * d2;
// Store the result in resultFraction
numeratorHandle.set(resultFraction, 0, resultNumerator);
denominatorHandle.set(resultFraction, 0, resultDenominator);
IO.println("Result Fraction: " +
numeratorHandle.get(resultFraction, 0L) + "/" +
denominatorHandle.get(resultFraction, 0L));
}
میتوانید نتیجهی کارتان را با قرار دادن قطعه کد بالا در یک نشست jshell بررسی کنید:
fractionLayout ==> [i4(numerator)i4(denominator)]
Result Fraction: 5/6
---
برای گسترش مثال قبلی و اجرای جمع هر دو کسر در یک آرایه با نوع دادهی سفارشی، دو گزینه وجود دارد:
SegmentAllocator به درخواستهای تخصیص با بازگرداندن نواحی پیوستهی متوالی حافظه (یا Slice) از یک Memory Segment موجود پاسخ میدهد.MemorySegment.asSlice.بیایید با ذخیرهی بیست کسر در یک SequenceLayout شروع کنیم:
try (Arena arena = Arena.ofConfined()) {
// Define the layout for a fraction
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
// Create a sequence layout for storing 20 fractions
SequenceLayout fractionArrayLayout = MemoryLayout.sequenceLayout(20, fractionLayout);
}
حال میتوانید حافظه برای بیست کسر با استفاده از SegmentAllocator.slicingAllocator(MemorySegment) تخصیص دهید:
try (Arena arena = Arena.ofConfined()) {
// Define the layout for a fraction (two integers: numerator and denominator)
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
// Create a sequence layout for storing 20 fractions
SequenceLayout fractionArrayLayout = MemoryLayout.sequenceLayout(20, fractionLayout);
// Allocate memory for 20 fractions
MemorySegment segment = arena.allocate(fractionArrayLayout);
SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment);
}
با SegmentAllocator میتوانید Sliceهای متوالی از یک Segment دریافت کنید و در هر Slice یک آرایه از اعداد صحیح تخصیص دهید.
در مثال ما، در هر Slice یک کسر تخصیص داده میشود.
try (Arena arena = Arena.ofConfined()) {
// Define the layout for a fraction (two integers: numerator and denominator)
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
// Create a sequence layout for storing 20 fractions
SequenceLayout fractionArrayLayout = MemoryLayout.sequenceLayout(20, fractionLayout);
// Allocate memory for 20 fractions
MemorySegment segment = arena.allocate(fractionArrayLayout);
SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment);
// Create an array of MemorySegments for each fraction
MemorySegment[] fractions = new MemorySegment[20];
// VarHandles to access the fields
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
// Initialize fractions with values (e.g., (1/2), (3/4), etc.)
for (int i = 0; i < 20; i++) {
fractions[i] = allocator.allocate(fractionLayout);
numeratorHandle.set(fractions[i],0, i + 1);
denominatorHandle.set(fractions[i],0, (i + 2));
}
}
در نهایت، منطق مثال را با جمعبندی هر دو کسر کامل کنید:
import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
import static java.lang.foreign.MemoryLayout.*;
try (Arena arena = Arena.ofConfined()) {
// Define the layout for a fraction (two integers: numerator and denominator)
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
// Create a sequence layout for storing 20 fractions
SequenceLayout fractionArrayLayout = MemoryLayout.sequenceLayout(20, fractionLayout);
// Allocate memory for 20 fractions
MemorySegment segment = arena.allocate(fractionArrayLayout);
SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment);
// Create an array of MemorySegments for each fraction
MemorySegment[] fractions = new MemorySegment[20];
// VarHandles to access the fields
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
// Initialize fractions with values (e.g., (1/2), (3/4), etc.)
for (int i = 0; i < 20; i++) {
fractions[i] = allocator.allocate(fractionLayout);
numeratorHandle.set(fractions[i],0, i + 1);
denominatorHandle.set(fractions[i],0, (i + 2));
}
MemorySegment resultFraction = arena.allocate(fractionLayout);
for (int i = 0; i < 20; i+=2) {
int n1 = (int) numeratorHandle.get(fractions[i], 0);
int d1 = (int) denominatorHandle.get(fractions[i], 0);
int n2 = (int) numeratorHandle.get(fractions[i+1], 0);
int d2 = (int) denominatorHandle.get(fractions[i+1], 0);
int resultNumerator = (n1 * d2) + (n2 * d1);
int resultDenominator = d1 * d2;
// Store the result in resultFraction
numeratorHandle.set(resultFraction, 0L, resultNumerator);
denominatorHandle.set(resultFraction, 0L, resultDenominator);
// Retrieve and print the result
IO.println("Result Fraction: " +
numeratorHandle.get(resultFraction, 0L) + "/" +
denominatorHandle.get(resultFraction, 0L));
}
}
کد بالا را در یک نشست jshell قرار دهید و نتایج را بررسی کنید:
Result Fraction: 7/6
Result Fraction: 31/20
Result Fraction: 71/42
Result Fraction: 127/72
Result Fraction: 199/110
Result Fraction: 287/156
Result Fraction: 391/210
Result Fraction: 511/272
Result Fraction: 647/342
Result Fraction: 799/420
یک Slicing Allocator یک Slice برمیگرداند و هنگام کار با نواحی حافظهی پیوستهی متوالی مفید است. آدرس شروع Slice بلافاصله بعد از انتهای Slice قبلیای است که Slicing Allocator بازگردانده.
اگر میخواهید از هر نقطهای درون یک Memory Segment و با هر اندازهای یک Slice دریافت کنید، میتوانید MemorySegment.asSlice(long,long) را فراخوانی کنید.
هشدار:
MemorySegment.asSlice(long,long)تا زمانی یک Slice ازMemorySegmentبرمیگرداند که اندازهی Slice در محدودهی فضایی Memory Segment اصلی باقی بماند.
بیایید از همان MemoryLayout قبلی برای کسرها استفاده کنیم و Sliceهایی از یک Memory Segment دریافت کنیم:
try (Arena arena = Arena.ofShared()) {
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
long fractionSize = fractionLayout.byteSize();
int amount = 20;
// Allocate memory for 20 fractions in a contiguous memory block
MemorySegment fractionsSegment = arena.allocate(fractionLayout.byteSize() * amount);
// VarHandles to access the fields
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
// Initialize fractions with sample values (e.g., (1/2), (2/3), etc.)
for (int i = 0; i < amount; i++) {
MemorySegment fractionSlice = fractionsSegment.asSlice(i * fractionSize, fractionSize);
numeratorHandle.set(fractionSlice, 0, i + 1);
denominatorHandle.set(fractionSlice, 0, (i + 2));
}
}
چون چند Thread میتوانند بهصورت موازی به هر کدام از Sliceها دسترسی پیدا کنند، Memory Segment باید برای آنها قابل دسترسی باشد. این کار با مرتبط کردن Memory Segment با یک Arena مشترک (Shared) امکانپذیر است.
قطعه کد زیر را کپی و پیست کنید تا مثال کامل را در یک نشست jshell امتحان کنید:
import java.lang.foreign.*;
import java.lang.invoke.VarHandle;
import static java.lang.foreign.MemoryLayout.*;
try (Arena arena = Arena.ofShared()) {
MemoryLayout fractionLayout = MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName("numerator"),
ValueLayout.JAVA_INT.withName("denominator")
);
long fractionSize = fractionLayout.byteSize();
int amount = 20;
// Allocate memory for 20 fractions in a contiguous memory block
MemorySegment fractionsSegment = arena.allocate(fractionLayout.byteSize() * amount);
// VarHandles to access the fields
VarHandle numeratorHandle = fractionLayout.varHandle(PathElement.groupElement("numerator"));
VarHandle denominatorHandle = fractionLayout.varHandle(PathElement.groupElement("denominator"));
// Initialize fractions with sample values (e.g., (1/2), (2/3), etc.)
for (int i = 0; i < amount; i++) {
MemorySegment fractionSlice = fractionsSegment.asSlice(i * fractionSize, fractionSize);
numeratorHandle.set(fractionSlice, 0, i + 1);
denominatorHandle.set(fractionSlice, 0, (i + 2));
}
for (int i = 0; i < amount; i++) {
MemorySegment fractionSlice = fractionsSegment.asSlice(i * fractionSize, fractionSize);
int numerator = (int) numeratorHandle.get(fractionSlice, 0);
int denominator = (int) denominatorHandle.get(fractionSlice, 0);
IO.println("Fraction " + i + ": " + numerator + "/" + denominator);
}
}
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://dev.java/learn/