Non-uniform Memory Access (بهصورت مخفف NUMA) یک طراحی حافظه است که در این طرح هر processor (پردازنده) حافظهی محلی مخصوص به خود را دارد و سرعت دسترسی پردازنده به حافظه محلی خود بالاتر از حافظههای غیرمحلی است.
NUMA (نوما) نقطه مقابل معماری SMP است که در آن تمام پردازندهها از یک حافظه مشترک استفاده میکنند، مناسب پروسسهایی وابسته به یک کاربر یا تسک است، در نتیجه اجرای پروسس در یک نود و با یک RAM اختصاصی سرعت اجرا را بیشتر میکند. برای اجرای بهینه برنامهها باید هر پروسس تا حد ممکن از RAM محلی پردازندهای که در آن مقیم است استفاده کند.
در سیستمعامل گنو/لینوکس از ابزار numactl برای کنترل سیاستهای نومای پروسسها استفاده میشود. با استفاده از این دستور مشخص میکنیم که یک برنامه در چه پردازندهای اجرا شود و از کدام حافظه استفاده کند.
در ادامه با این دستور آشنا میشویم و در انتها برنامه نویسی بهینه با استفاده از کتابخانه numalib را فرا میگیریم.
دستور numactl:
برای بررسی وضعیت سختافزار NUMA میتوان از دستور numactl بهصورت زیر استفاده کرد.
numactl --hardware
خروجی دستور مشخصات پردازنده خواهد بود و میتواند به دو صورت باشد:
۱. در خروجی دستور تنها ۱ نود وجود داشتهباشد، به این معنی است که معماری پردازنده ما نمیتواند از نوما پشتیبانی کند.
$numactl --hardware available: 1 nodes (0) node 0 cpus: 0 1 2 3 node 0 size: 3735 MB node 0 free: 172 MB node distances: node 0 0: 10
۲. در خروجی بیش از یک نود وجود داشتهباشد، به این معنی است که پردازنده از numa پشتبیانی میکند.
numactl --hardware available: 2 nodes (0-1) node 0 cpus: 0 1 2 3 8 9 10 11 node 0 size: 16374 MB node 0 free: 10935 MB node 1 cpus: 4 5 6 7 12 13 14 15 node 1 size: 16384 MB node 1 free: 15298 MB node distances: node 0 1 0: 10 20 1: 20 10
در خروجی بالا دو NODE داریم (که با شماره ۰ و ۱ شناخته میشوند) در node شماره ۰ پردازندههای ۰ و ۱ و ۲ و ۳ و ۸ و ۹ و ۱۰ و ۱۱ قرار دارد، RAM اختصاص یافته به نود صفر ۱۶۳۷۴ مگابایت است که از این رم ۱۰۹۳۵ مگا بایت آن آزاد است. اطلاعات نود ۱ نیز به همین شکل تفسیر میشود.
در جدول node distances سرعت دسترسی هر نود به حافظهها مشخص شدهاست. در مثال فوق دسترسی هر نود به حافظه اختصاصی خود با سرعت ۱۰ و حافظههای دیگر ۲۰ میباشد.
برای مشاهده وضعیت NUMA میتوان میتوان از دستور numactl بهصورت زیر استفاده کرد.
$numactl --show
خروجی دستور:
policy: default preferred node: current physcpubind: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cpubind: 0 1 nodebind: 0 1 membind: 0 1
بر اساس خروجی دستورات بالا میتوان نتیجه گرفت که ۱۶ پردازنده وجود دارد که دو NODE را تشکیل میدهند، هر node هشت پردازنده و ۱۶ گیگابایت حافظه محلی دارد.
نکته: وضعیت numa را با استفاده از اطلاعات موجود در مسیر /sys نیز میتوان بهصورت دست آورد.
#ls /sys/devices/system/node/
خروجی دستور:
node0/ node1/
#ls /sys/devices/system/node/node0
خروجی دستور:
compact memory1 memory112 memory126 memory18 memory39 memory52 memory66 memory8 memory93 cpu0 memory10 memory113 memory127 memory19 memory4 memory53 memory67 memory80 memory94 cpu1 memory100 memory114 memory128 memory2 memory40 memory54 memory68 memory81 memory95 cpu10 memory101 memory115 memory129 memory20 memory41 memory55 memory69 memory82 memory96 cpu11 memory102 memory116 memory13 memory21 memory42 memory56 memory7 memory83 memory97 cpu2 memory103 memory117 memory130 memory22 memory43 memory57 memory70 memory84 memory98 cpu3 memory104 memory118 memory131 memory23 memory44 memory58 memory71 memory85 memory99 cpu8 memory105 memory119 memory132 memory3 memory45 memory59 memory72 memory86 numastat cpu9 memory106 memory12 memory133 memory32 memory46 memory6 memory73 memory87 scan_unevictable_pages cpulist memory107 memory120 memory134 memory33 memory47 memory60 memory74 memory88 vmstat cpumap memory108 memory121 memory135 memory34 memory48 memory61 memory75 memory89 distance memory109 memory122 memory14 memory35 memory49 memory62 memory76 memory9 hugepages memory11 memory123 memory15 memory36 memory5 memory63 memory77 memory90 meminfo memory110 memory124 memory16 memory37 memory50 memory64 memory78 memory91 memory0 memory111 memory125 memory17 memory38 memory51 memory65 memory79 memory92
مشاهده اطلاعات حافظه به کمک دستور numastat:
#numastat
خروجی دستور:
node0 node1 numa_hit 4134896 1809856 numa_miss 0 0 numa_foreign 0 0 interleave_hit 12114 12083 local_node 4134765 1795945 other_node 131 13911
سادهترین شکل اجرای دستور numactl بهصورت زیر است.
numactl -l mongod
در این حالت برنامه mongod تنها از حافظه محلی پردازندهی مقیم استفاده میکند، پارامتر localalloc و یا l برنامه را وادار میکند که تنها از حافظه محلی نودی که در آن در حال اجراست استفاده کند. ممکن است حافظه مورد نیاز برنامه از حافظه محلی نود بیشتر باشد، در اینصورت نتیجه اجرای برنامه با خطا مواجه میشود. میتوان از پارامتر preferred استفاده کرد تا در صورت کمبود RAM، برنامه از حافظهی نودهای دیگر استفاده کند.
numactl --preferred=5 PROGRAM
با استفاده از پارامتر membind میتوان برنامه را در حافظه یک نود اجرا کرد، همچین با پارامتر cpunodebind برنامه در پردازندههای نود تعریف شده اجرا میشود.
numactl --membind=2 --cpunodebind=2 mongo
با دستور بالا یک پروسس را میتوان در یک نود و حافظههای آن اجرا کرد.
نکته: با دستور زیر میتوانیم اجرای صحیح پروسسها بر اساس سیاست numa را بررسی کرد.
for i in `ps -ef | awk '{print $2}'`; do taskset -c -p $i; done
هماهنطور که در مثالهای بالا هم دیدیم سیاستهای numa به چهارصورت میتوانند اختصاص یابند:
۱. Local: در این حالت برنامه تنها از حافظه محلی نودی که در آن احرا میشود استفاده میکند. این حالت پیش فرض است.
numactl --localalloc mongod
۲. Preferred: برنامه از حافظه محلی نودی که در آن اجرا میشود استفاده میکند در صورتی که برنامه به حافظه بیشتری نیاز داشته باشد از حافظه محلی سایر نودها استفاده میکند.
numactl --preferred=1
۳. Interleave: حاافظه با الگوریتم round robin به نودها اختصاص مییابد.
numactl --interleave=all mongos
۴. اجرای برنامه در یک سیپییو خاص و استفاده از حافظه محلی یک نود خاص:
numactl --cpubind=1 --membind=0,1 mongod
کتابخانه libnuma:
برنامه نویسان با استفاده از این کتابخانه میتواند برنامههای خود را در نودهای متفاوت اجرا کنند و برنامه را محدود به استفاده از حافظه نودهای خاص کنند. برای استفاده از این قابلیت باید کتابخانه libnuma را نصب کنید.
یک نمونه کد به زبان سی که از این کتابخانه استفاده میکند را در زیر میبینید.
#include <numa.h> #include <stdio.h> int main(int argc, char **argv) { if (numa_available() < 0) { printf("numa_* functions unavailable\n"); return 1; } printf("numa_available: %d \n\r", numa_available()); printf("numa_num_possible_nodes: %d \n\r", numa_num_possible_nodes()); printf("numa_max_possible_node: %d \n\r", numa_max_possible_node()); printf("numa_num_configured_nodes: %d \n\r", numa_num_configured_nodes()); printf("numa_num_configured_cpus: %d \n\r", numa_num_configured_cpus()); }
برنامه را با پارامتر -lnuma کامپایل و سپس اجرا میکنیم.
$gcc -o NUMA.BIN numac.c -lnuma $./NUMA.BIN