Bagikan melalui


Arsitektur NUMA

Model tradisional untuk arsitektur multiprosedur adalah multiproscessor simetris (SMP). Dalam model ini, setiap prosesor memiliki akses yang sama ke memori dan I/O. Karena lebih banyak prosesor ditambahkan, bus prosesor menjadi batasan untuk performa sistem.

Perancang sistem menggunakan akses memori non-seragam (NUMA) untuk meningkatkan kecepatan prosesor tanpa meningkatkan beban pada bus prosesor. Arsitekturnya tidak seragam karena setiap prosesor dekat dengan beberapa bagian memori dan lebih jauh dari bagian memori lainnya. Prosesor dengan cepat mendapatkan akses ke memori yang dekat dengannya, sementara dapat memakan waktu lebih lama untuk mendapatkan akses ke memori yang lebih jauh.

Dalam sistem NUMA, CPU diatur dalam sistem yang lebih kecil yang disebut simpul. Setiap simpul memiliki prosesor dan memorinya sendiri, dan terhubung ke sistem yang lebih besar melalui bus interkoneksi yang koheren cache.

Sistem mencoba meningkatkan performa dengan menjadwalkan utas pada prosesor yang berada dalam simpul yang sama dengan memori yang digunakan. Ini mencoba untuk memenuhi permintaan alokasi memori dari dalam simpul, tetapi akan mengalokasikan memori dari simpul lain jika perlu. Ini juga menyediakan API untuk membuat topologi sistem tersedia untuk aplikasi. Anda dapat meningkatkan performa aplikasi Anda dengan menggunakan fungsi NUMA untuk mengoptimalkan penjadwalan dan penggunaan memori.

Pertama-tama, Anda harus menentukan tata letak simpul dalam sistem. Untuk mengambil simpul bernomor tertinggi dalam sistem, gunakan fungsi GetNumaHighestNodeNumber . Perhatikan bahwa angka ini tidak dijamin sama dengan jumlah total simpul dalam sistem. Selain itu, simpul dengan angka berurutan tidak dijamin berdekatan. Untuk mengambil daftar prosesor pada sistem, gunakan fungsi GetProcessAffinityMask . Anda dapat menentukan simpul untuk setiap prosesor dalam daftar dengan menggunakan fungsi GetNumaProcessorNode . Atau, untuk mengambil daftar semua prosesor dalam simpul, gunakan fungsi GetNumaNodeProcessorMask .

Setelah menentukan prosesor mana yang termasuk dalam simpul mana, Anda dapat mengoptimalkan performa aplikasi Anda. Untuk memastikan bahwa semua utas untuk proses Anda berjalan pada simpul yang sama, gunakan fungsi SetProcessAffinityMask dengan masker afinitas proses yang menentukan prosesor dalam simpul yang sama. Ini meningkatkan efisiensi aplikasi yang utasnya perlu mengakses memori yang sama. Atau, untuk membatasi jumlah utas pada setiap simpul, gunakan fungsi SetThreadAffinityMask .

Aplikasi intensif memori perlu mengoptimalkan penggunaan memori mereka. Untuk mengambil jumlah memori gratis yang tersedia untuk node, gunakan fungsi GetNumaAvailableMemoryNode . Fungsi VirtualAllocExNuma memungkinkan aplikasi menentukan simpul pilihan untuk alokasi memori. VirtualAllocExNuma tidak mengalokasikan halaman fisik apa pun, sehingga akan berhasil apakah halaman tersedia pada simpul tersebut atau di tempat lain dalam sistem atau tidak. Halaman fisik dialokasikan sesuai permintaan. Jika simpul pilihan kehabisan halaman, manajer memori akan menggunakan halaman dari simpul lain. Jika memori di-page out, proses yang sama digunakan ketika dibawa kembali.

Dukungan NUMA pada Sistem Dengan Lebih dari 64 Prosesor Logis

Pada sistem dengan lebih dari 64 prosesor logis, simpul ditetapkan ke grup prosesor sesuai dengan kapasitas simpul. Kapasitas node adalah jumlah prosesor yang ada ketika sistem dimulai bersama dengan prosesor logis tambahan yang dapat ditambahkan saat sistem berjalan.

Windows Server 2008, Windows Vista, Windows Server 2003 dan Windows XP: Grup prosesor tidak didukung.

Setiap simpul harus sepenuhnya terkandung dalam grup. Jika kapasitas simpul relatif kecil, sistem menetapkan lebih dari satu simpul ke grup yang sama, memilih simpul yang secara fisik dekat satu sama lain untuk performa yang lebih baik. Jika kapasitas simpul melebihi jumlah maksimum prosesor dalam grup, sistem membagi simpul menjadi beberapa simpul yang lebih kecil, masing-masing cukup kecil agar pas dalam grup.

Simpul NUMA yang ideal untuk proses baru dapat diminta menggunakan atribut PROC_THREAD_ATTRIBUTE_PREFERRED_NODE diperluas saat proses dibuat. Seperti prosesor ideal utas, simpul ideal adalah petunjuk untuk penjadwal, yang menetapkan proses baru ke grup yang berisi simpul yang diminta jika memungkinkan.

Fungsi NUMA yang diperluas GetNumaAvailableMemoryNodeEx, GetNumaNodeProcessorMaskEx, GetNumaProcessorNodeEx, dan GetNumaProximityNodeEx berbeda dari rekan-rekan mereka yang tidak terekspresi karena nomor simpul adalah nilai USHORT daripada UCHAR, untuk mengakomodasi jumlah simpul yang berpotensi lebih besar pada sistem dengan lebih dari 64 prosesor logis. Selain itu, prosesor yang ditentukan dengan atau diambil oleh fungsi yang diperluas mencakup grup prosesor; prosesor yang ditentukan dengan atau diambil oleh fungsi yang tidak terduga adalah relatif grup. Untuk detailnya, lihat topik referensi fungsi individual.

Aplikasi yang sadar grup dapat menetapkan semua utasnya ke simpul tertentu dengan cara yang mirip dengan yang dijelaskan sebelumnya dalam topik ini, menggunakan fungsi NUMA yang diperluas yang sesuai. Aplikasi ini menggunakan GetLogicalProcessorInformationEx untuk mendapatkan daftar semua prosesor pada sistem. Perhatikan bahwa aplikasi tidak dapat mengatur masker afinitas proses kecuali proses ditetapkan ke satu grup dan simpul yang dimaksudkan terletak di grup tersebut. Biasanya aplikasi harus memanggil SetThreadGroupAffinity untuk membatasi utasnya ke simpul yang dimaksud.

Perilaku dimulai dengan Windows 10 Build 20348

Catatan

Dimulai dengan Windows 10 Build 20348, perilaku ini dan fungsi NUMA lainnya telah dimodifikasi untuk mendukung sistem yang lebih baik dengan simpul yang berisi lebih dari 64 prosesor.

Membuat simpul "palsu" untuk mengakomodasi pemetaan 1:1 antara grup dan simpul telah mengakibatkan perilaku yang membingungkan di mana jumlah simpul NUMA yang tidak terduga dilaporkan dan karenanya, dimulai dengan Windows 10 Build 20348, OS telah berubah untuk memungkinkan beberapa grup dikaitkan dengan simpul, dan jadi sekarang topologi NUMA yang sebenarnya dari sistem dapat dilaporkan.

Sebagai bagian dari perubahan ini pada OS, sejumlah API NUMA telah berubah untuk mendukung pelaporan beberapa grup yang sekarang dapat dikaitkan dengan satu simpul NUMA. API yang diperbarui dan baru diberi label dalam tabel di bagian NUMA API di bawah ini.

Karena penghapusan pemisahan simpul berpotensi memengaruhi aplikasi yang ada, nilai registri tersedia untuk memungkinkan memilih kembali ke simpul warisan yang memisahkan perilaku. Pemisahan simpul dapat diaktifkan kembali dengan membuat nilai REG_DWORD bernama "SplitLargeNodes" dengan nilai 1 di bawah HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NUMA. Perubahan pada pengaturan ini memerlukan reboot agar berlaku.

reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\NUMA" /v SplitLargeNodes /t REG_DWORD /d 1

Catatan

Aplikasi yang diperbarui untuk menggunakan fungsionalitas API baru yang melaporkan topologi NUMA yang sebenarnya akan terus berfungsi dengan baik pada sistem di mana pemisahan simpul besar telah diaktifkan kembali dengan kunci registri ini.

Contoh berikut pertama-tama menunjukkan potensi masalah dengan prosesor pemetaan tabel build ke simpul NUMA menggunakan API afinitas warisan, yang tidak lagi menyediakan penutup penuh semua prosesor dalam sistem, ini dapat mengakibatkan tabel yang tidak lengkap. Implikasi ketidaklengkapan tersebut bergantung pada konten tabel. Jika tabel hanya menyimpan nomor simpul yang sesuai, kemungkinan ini hanya masalah performa dengan prosesor yang tidak terungkap yang dibiarkan sebagai bagian dari node 0. Namun, jika tabel berisi pointer ke struktur konteks per simpul, ini dapat mengakibatkan dereferensi NULL pada runtime.

Selanjutnya, contoh kode mengilustrasikan dua solusi untuk masalah tersebut. Yang pertama adalah bermigrasi ke API afinitas simpul multi-grup (mode pengguna dan mode kernel). Yang kedua adalah menggunakan KeQueryLogicalProcessorRelationship untuk langsung mengkueri simpul NUMA yang terkait dengan nomor prosesor tertentu.


//
// Problematic implementation using KeQueryNodeActiveAffinity.
//

USHORT CurrentNode;
USHORT HighestNodeNumber;
GROUP_AFFINITY NodeAffinity;
ULONG ProcessorIndex;
PROCESSOR_NUMBER ProcessorNumber;

HighestNodeNumber = KeQueryHighestNodeNumber();
for (CurrentNode = 0; CurrentNode <= HighestNodeNumber; CurrentNode += 1) {

    KeQueryNodeActiveAffinity(CurrentNode, &NodeAffinity, NULL);
    while (NodeAffinity.Mask != 0) {

        ProcessorNumber.Group = NodeAffinity.Group;
        BitScanForward(&ProcessorNumber.Number, NodeAffinity.Mask);

        ProcessorIndex = KeGetProcessorIndexFromNumber(&ProcessorNumber);

        ProcessorNodeContexts[ProcessorIndex] = NodeContexts[CurrentNode;]

        NodeAffinity.Mask &= ~((KAFFINITY)1 << ProcessorNumber.Number);
    }
}

//
// Resolution using KeQueryNodeActiveAffinity2.
//

USHORT CurrentIndex;
USHORT CurrentNode;
USHORT CurrentNodeAffinityCount;
USHORT HighestNodeNumber;
ULONG MaximumGroupCount;
PGROUP_AFFINITY NodeAffinityMasks;
ULONG ProcessorIndex;
PROCESSOR_NUMBER ProcessorNumber;
NTSTATUS Status;

MaximumGroupCount = KeQueryMaximumGroupCount();
NodeAffinityMasks = ExAllocatePool2(POOL_FLAG_PAGED,
                                    sizeof(GROUP_AFFINITY) * MaximumGroupCount,
                                    'tseT');

if (NodeAffinityMasks == NULL) {
    return STATUS_NO_MEMORY;
}

HighestNodeNumber = KeQueryHighestNodeNumber();
for (CurrentNode = 0; CurrentNode <= HighestNodeNumber; CurrentNode += 1) {

    Status = KeQueryNodeActiveAffinity2(CurrentNode,
                                        NodeAffinityMasks,
                                        MaximumGroupCount,
                                        &CurrentNodeAffinityCount);
    NT_ASSERT(NT_SUCCESS(Status));

    for (CurrentIndex = 0; CurrentIndex < CurrentNodeAffinityCount; CurrentIndex += 1) {

        CurrentAffinity = &NodeAffinityMasks[CurrentIndex];

        while (CurrentAffinity->Mask != 0) {

            ProcessorNumber.Group = CurrentAffinity.Group;
            BitScanForward(&ProcessorNumber.Number, CurrentAffinity->Mask);

            ProcessorIndex = KeGetProcessorIndexFromNumber(&ProcessorNumber);

            ProcessorNodeContexts[ProcessorIndex] = NodeContexts[CurrentNode];

            CurrentAffinity->Mask &= ~((KAFFINITY)1 << ProcessorNumber.Number);
        }
    }
}

//
// Resolution using KeQueryLogicalProcessorRelationship.
//

ULONG ProcessorCount;
ULONG ProcessorIndex;
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX ProcessorInformation;
ULONG ProcessorInformationSize;
PROCESSOR_NUMBER ProcessorNumber;
NTSTATUS Status;

ProcessorCount = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);

for (ProcessorIndex = 0; ProcessorIndex < ProcessorCount; ProcessorIndex += 1) {

    Status = KeGetProcessorNumberFromIndex(ProcessorIndex, &ProcessorNumber);
    NT_ASSERT(NT_SUCCESS(Status));

    ProcessorInformationSize = sizeof(ProcessorInformation);
    Status = KeQueryLogicalProcessorRelationship(&ProcessorNumber,
                                                    RelationNumaNode,
                                                    &ProcessorInformation,
                                                    &ProcesorInformationSize);
    NT_ASSERT(NT_SUCCESS(Status));

    NodeNumber = ProcessorInformation.NumaNode.NodeNumber;

    ProcessorNodeContexts[ProcessorIndex] = NodeContexts[NodeNumber];
}

NUMA API

Tabel berikut menjelaskan NUMA API.

Fungsi Deskripsi
AllocateUserPhysicalPagesNuma Mengalokasikan halaman memori fisik untuk dipetakan dan tidak dipetakan dalam wilayah Address Windowing Extensions (AWE) dari proses tertentu dan menentukan simpul NUMA untuk memori fisik.
CreateFileMappingNuma Membuat atau membuka objek pemetaan file bernama atau tidak bernama untuk file tertentu, dan menentukan simpul NUMA untuk memori fisik.
GetLogicalProcessorInformation Diperbarui di Windows 10 Build 20348. Mengambil informasi tentang prosesor logis dan perangkat keras terkait.
GetLogicalProcessorInformationEx Diperbarui di Windows 10 Build 20348. Mengambil informasi tentang hubungan prosesor logis dan perangkat keras terkait.
GetNumaAvailableMemoryNode Mengambil jumlah memori yang tersedia dalam simpul yang ditentukan.
GetNumaAvailableMemoryNodeEx Mengambil jumlah memori yang tersedia dalam simpul yang ditentukan sebagai nilai USHORT .
GetNumaHighestNodeNumber Mengambil simpul yang saat ini memiliki angka tertinggi.
GetNumaNodeProcessorMask Diperbarui di Windows 10 Build 20348. Mengambil masker prosesor untuk simpul yang ditentukan.
GetNumaNodeProcessorMask2 Baru di Windows 10 Build 20348. Mengambil masker prosesor multi-grup dari simpul yang ditentukan.
GetNumaNodeProcessorMaskEx Diperbarui di Windows 10 Build 20348. Mengambil masker prosesor untuk simpul yang ditentukan sebagai nilai USHORT .
GetNumaProcessorNode Mengambil nomor simpul untuk prosesor yang ditentukan.
GetNumaProcessorNodeEx Mengambil nomor simpul sebagai nilai USHORT untuk prosesor yang ditentukan.
GetNumaProximityNode Mengambil nomor simpul untuk pengidentifikasi kedekatan yang ditentukan.
GetNumaProximityNodeEx Mengambil nomor simpul sebagai nilai USHORT untuk pengidentifikasi kedekatan yang ditentukan.
GetProcessDefaultCpuSetMasks Baru di Windows 10 Build 20348. Mengambil daftar Set CPU dalam set default proses yang ditetapkan oleh SetProcessDefaultCpuSetMasks atau SetProcessDefaultCpuSets.
GetThreadSelectedCpuSetMasks Baru di Windows 10 Build 20348. Mengatur penetapan Set CPU yang dipilih untuk utas yang ditentukan. Penugasan ini mengambil alih penetapan default proses, jika ditetapkan.
MapViewOfFileExNuma Memetakan tampilan pemetaan file ke ruang alamat proses panggilan, dan menentukan simpul NUMA untuk memori fisik.
SetProcessDefaultCpuSetMasks Baru di Windows 10 Build 20348. Mengatur penetapan Set CPU default untuk utas dalam proses yang ditentukan.
SetThreadSelectedCpuSetMasks Baru di Windows 10 Build 20348. Mengatur penetapan Set CPU yang dipilih untuk utas yang ditentukan. Penugasan ini mengambil alih penetapan default proses, jika ditetapkan.
VirtualAllocExNuma Mencadangkan atau menerapkan wilayah memori dalam ruang alamat virtual dari proses yang ditentukan, dan menentukan simpul NUMA untuk memori fisik.

 

Fungsi QueryWorkingSetEx dapat digunakan untuk mengambil simpul NUMA tempat halaman dialokasikan. Misalnya, lihat Mengalokasikan Memori dari Simpul NUMA.

Mengalokasikan Memori dari Simpul NUMA

Beberapa Prosesor

Grup Prosesor