[LINUX] Lors de l'augmentation de la mémoire disponible du nœud, elle peut être limitée par vm.max_map_count

environnement

--Langue: node v14.7.0

Résumé

Sur Ubuntu avec 64 Go de mémoire, lorsque j'ai déplacé le nœud avec la consommation de mémoire maximale étendue à 48 Go avec l'option --max-old-space-size, 48 Go ont été augmentés en fonction du contenu du programme. Il y avait deux comportements, l'un était lorsqu'il était épuisé et l'autre était lorsqu'une erreur de mémoire se produisait alors qu'environ 20 Go étaient consommés.

La cause était la différence dans la méthode d'allocation de mémoire en raison de la différence dans le fonctionnement du programme, et l'appel système mMap a généré ENOMEM en raison du nombre de mappages dépassant dans le programme qui n'a pas pu utiliser la mémoire.

En raison de l'extension du nombre maximum de mappages du système avec la commande sudo sysctl -w vm.max_map_count = 655300, le programme peut utiliser la mémoire donnée.

introduction

Affichage de la consommation de mémoire

Le code de bloc suivant montre la ligne unique qui affiche la consommation de mémoire du nœud en cours d'exécution sur bash.

python


$ node -e 'console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'
0.02828216552734375

Voici un aperçu de la consommation de mémoire après la création d'un tableau constitué de 10 000 000 de nombres de 0.

python


$ node -e 'var a = Array.from({length: 10000000}, v => 0); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'
0.10385513305664062

La consommation de mémoire a légèrement augmenté de 0,028 Go à 0,103 Go. Un entier de 10 méga a généré environ 70 Mo de consommation.

Si vous ne spécifiez pas --max-old-space-size, la limite de mémoire sera immédiatement atteinte.

Et si vous générez un tableau de 100 0 au lieu de 0?

python


$ node -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 100}, v => 0)); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'

Cet environnement a environ 64 Go de mémoire, donc je pense que c'est physiquement suffisant. Cependant, lorsque le nœud consomme environ 5 Go, l'erreur suivante apparaît.

python


$ node -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 100}, v => 0)); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'

<--- Last few GCs --->

[27037:0x648f650]    45191 ms: Scavenge 4093.1 (4100.7) -> 4093.0 (4101.5) MB, 5.7 / 0.0 ms  (average mu = 0.125, current mu = 0.081) allocation failure
[27037:0x648f650]    45204 ms: Scavenge (reduce) 4094.0 (4105.5) -> 4094.0 (4106.2) MB, 5.6 / 0.0 ms  (average mu = 0.125, current mu = 0.081) allocation failure
[27037:0x648f650]    45218 ms: Scavenge (reduce) 4095.1 (4100.5) -> 4095.0 (4103.7) MB, 6.2 / 0.0 ms  (average mu = 0.125, current mu = 0.081) allocation failure


<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x9fd5f0 node::Abort() [node]
 2: 0x94a45d node::FatalError(char const*, char const*) [node]
 3: 0xb7099e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [node]
 4: 0xb70d17 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [node]
 5: 0xd1a905  [node]
 6: 0xd1b48f  [node]
 7: 0xd294fb v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [node]
 8: 0xd2d0bc v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [node]
 9: 0xcfb7bb v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [node]
10: 0x1040c4f v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [node]
11: 0x13cc8f9  [node]
Aborted (core dumped)

«Mark-compacts inefficaces près de la limite du tas Échec de l'allocation --Si vous recherchez Google avec le tas JavaScript hors de mémoire», vous pouvez étendre la limite supérieure de la mémoire que le nœud peut consommer avec «--max-old-space-size» comme suit. J'ai beaucoup d'articles.

Cet environnement a 64 Go de mémoire, essayons donc de consommer jusqu'à 48 Go.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 100}, v => 0)); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'
8.26059341430664

Après avoir consommé 8,2 Go de mémoire, il s'est terminé normalement.

La mémoire spécifiée ne peut pas être utilisée

Alors que faire si vous générez un "tableau composé de 1000 tableaux vides" au lieu de "un tableau composé de 100 0"? En premier lieu, la consommation de mémoire doit être de 10 fois, donc la mémoire doit être insuffisante. De plus, au lieu de 0, générons ici un tableau vide.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 1000}, v => [])); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'

On s'attend à ce que 48 Go soient utilisés et qu'il se bloque avec une erreur due au manque de mémoire. Avant que les 48 Go ne soient épuisés, il est possible que la vitesse de fonctionnement devienne extrêmement lente en raison du ramasse-miettes.


Le résultat s'est terminé par l'erreur suivante lors de la consommation de seulement 16,8 Go: D'après les résultats de la section précédente, il est clair que l'effet de --max-old-space-size apparaît.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 1000}, v => [])); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'

<--- Last few GCs --->

[10261:0x648f730]    77051 ms: Scavenge 16379.4 (16414.4) -> 16377.4 (16428.1) MB, 32.2 / 0.0 ms  (average mu = 0.794, current mu = 0.795) allocation failure
[10261:0x648f730]    77103 ms: Scavenge 16393.4 (16428.1) -> 16395.3 (16430.4) MB, 27.8 / 0.0 ms  (average mu = 0.794, current mu = 0.795) allocation failure
[10261:0x648f730]    77189 ms: Scavenge 16395.3 (16430.4) -> 16393.6 (16441.6) MB, 86.3 / 0.0 ms  (average mu = 0.794, current mu = 0.795) allocation failure


<--- JS stacktrace --->

FATAL ERROR: Scavenger: semi-space copy Allocation failed - JavaScript heap out of memory
Segmentation fault (core dumped)

De plus, après l'avoir exécutée plusieurs fois, l'instruction d'erreur peut être aléatoire et la suivante.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'var a = Array.from({length: 10000000}, v => Array.from({length: 1000}, v => [])); console.log(process.memoryUsage().rss / 1024 / 1024 / 1024);'
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

Pourquoi cela se termine-t-il avec seulement 16,8 Go utilisés même si 48 Go est spécifié avec --max-old-space-size? Comment puis-je utiliser jusqu'à 48 Go?

Enquête

Code qui peut utiliser jusqu'à 48 Go et code qui ne peut pas être utilisé

Le code suivant augmente le nombre d'impressions de consommation de mémoire.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'Array.from({length: 10000}, v => { console.log(process.memoryUsage().rss / 1024 / 1024 / 1024); return Array.from({length: 1000000}, v => []); });'

Lorsque je l'ai exécuté, la consommation de mémoire était affichée et elle s'est terminée anormalement à environ 20 Go.

python


<Omis>

20.046581268310547
20.084598541259766
20.122615814208984
20.160381317138672
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
Aborted (core dumped)

Le code suivant lui permet de générer "un tableau de 1" au lieu de "un tableau de tableaux vides".

python


$ node --max-old-space-size=$((1024 * 48)) -e 'Array.from({length: 10000}, v => { console.log(process.memoryUsage().rss / 1024 / 1024 / 1024); return Array.from({length: 1000000}, v => 1); });'

Ce code ne s'arrête pas à 20 Go, il manque des 48 Go de mémoire spécifiés et se termine! De plus, comme la consommation de mémoire dépassait 46 Go, le ramasse-miettes était fréquemment bégayé, et la consommation de mémoire n'augmentait pas facilement ~~ Achille et Kame ~~.

python


<Omis>

48.053977966308594
48.06153106689453
48.06904602050781
48.076324462890625
48.08384323120117

<--- Last few GCs --->

<Omis>

<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x9fd5f0 node::Abort() [node]

<Omis>

17: 0x1018255 v8::internal::Runtime_NewArray(int, unsigned long*, v8::internal::Isolate*) [node]
18: 0x13cc8f9  [node]
Aborted (core dumped)

La seule chose qui a changé est de savoir si la partie la plus profonde de la structure de données est un tableau vide ou l'un des nombres. Dans les deux cas, il n'y a pas assez de mémoire physique en premier lieu, donc il n'y a pas de changement dans la terminaison anormale finale.

std :: bad_alloc semble être une erreur lorsque le nouveau dans C ++ échoue

Une recherche Google sur std :: bad_alloc a révélé qu'il provenait de nouveau en C ++.

Le nœud est écrit en C ++, ce qui signifie qu'il y a un endroit où le nouveau est fait. std :: bad_alloc peut être capturé par une instruction try C ++, mais cela peut se produire à la fois dans les emplacements capturés et non capturés, car l'emplacement général est nouveau. Je m'attends à ce que ce soit la cause de la modification aléatoire de la déclaration d'erreur.

En regardant strace, j'ai trouvé que mMap donnait l'erreur ENOMEM

strace est une commande qui peut générer un appel système d'un processus.

Pour le moment, je l'ai placé devant le nœud et l'ai exécuté comme suit.

python


$ strace node --max-old-space-size=$((1024 * 48)) -e 'Array.from({length: 10000}, v => { console.log(process.memoryUsage().rss / 1024 / 1024 / 1024); return Array.from({length: 1000000}, v => 1); });'

Le journal en colère est sorti, mais la fin est la suivante.

python


<Omis>

mprotect(0x29960bbc0000, 262144, PROT_READ|PROT_WRITE) = 0
brk(0x1bf9a000)                         = 0x1bf9a000
mmap(0x2ebbbafc0000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x2ebbbafc0000
munmap(0x2ebbbb000000, 258048)          = 0
mprotect(0x2ebbbafc0000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x3ba59c200000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3ba59c200000
munmap(0x3ba59c240000, 258048)          = 0
mprotect(0x3ba59c200000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x3f3009d40000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x3f3009d40000
munmap(0x3f3009d80000, 258048)          = 0
mprotect(0x3f3009d40000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x330b57380000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x330b57380000
munmap(0x330b573c0000, 258048)          = 0
mprotect(0x330b57380000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x207b9d440000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x207b9d440000
munmap(0x207b9d480000, 258048)          = 0
mprotect(0x207b9d440000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x300db2380000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x300db2380000
munmap(0x300db23c0000, 258048)          = 0
mprotect(0x300db2380000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x8e44e340000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x8e44e340000
munmap(0x8e44e380000, 258048)           = 0
mprotect(0x8e44e340000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x1a79a5c00000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x1a79a5c00000
munmap(0x1a79a5c40000, 258048)          = 0
mprotect(0x1a79a5c00000, 262144, PROT_READ|PROT_WRITE) = 0
mmap(0x9abb4d00000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x9abb4d00000, 520192, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mprotect(0xafbb8382000, 86016, PROT_READ|PROT_WRITE) = 0
mprotect(0xafbb83c2000, 249856, PROT_READ|PROT_WRITE) = 0
mprotect(0xafbb8402000, 4096, PROT_READ|PROT_WRITE) = 0
mprotect(0xafbb8442000, 4096, PROT_READ|PROT_WRITE) = 0
mprotect(0xafbb8482000, 4096, PROT_READ|PROT_WRITE) = 0
mmap(NULL, 1040384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
brk(0x1c0a2000)                         = 0x1bf9a000
mmap(NULL, 1175552, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(0x7efbe4000000, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 134217728, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)
mmap(NULL, 1040384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
futex(0x7efbfebab1a0, FUTEX_WAKE_PRIVATE, 2147483647) = 0
write(2, "terminate called after throwing "..., 48terminate called after throwing an instance of ') = 48
write(2, "std::bad_alloc", 14std::bad_alloc)          = 14
write(2, "'\n", 2'
)                      = 2
write(2, "  what():  ", 11  what():  )             = 11
write(2, "std::bad_alloc", 14std::bad_alloc)          = 14
write(2, "\n", 1
)                       = 1
rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [], 8) = 0
getpid()                                = 15400
gettid()                                = 15400
tgkill(15400, 15400, SIGABRT)           = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=15400, si_uid=1003} ---
+++ killed by SIGABRT (core dumped) +++
Aborted (core dumped)

Ici, vous pouvez voir à partir de la ligne suivante que l'erreur «ENOMEM» apparaît dans la partie appelée mMap. Pourquoi est-il «Impossible d'allouer de la mémoire» lorsqu'il y a un excès de mémoire?

python


mmap(NULL, 67108864, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = -1 ENOMEM (Cannot allocate memory)

Les conditions pour ENOMEM sont une mémoire insuffisante et un dépassement du nombre de mappages.

Des informations importantes ont été écrites dans l'homme de mmap.

ENOMEM Il n'y a pas de mémoire libre ou le nombre de mappages pour le processus en cours de traitement a dépassé le maximum.

Si "le nombre de mappages dépasse le nombre maximum", ENOMEM sera affiché même s'il y a un excès de mémoire. L'erreur elle-même est un ENOMEM courant, donc il est probablement impossible de la distinguer du point de vue d'un programme. Il est compréhensible que le message d'erreur soit «JavaScript heap out of memory».

Vous pouvez augmenter le nombre maximum de mappages avec sudo sysctl -w vm.max_map_count =

Un moyen d'augmenter le nombre maximal de mappages a été obtenu à partir de l'emplacement suivant.

Vous pouvez trouver la valeur actuelle avec sysctl vm.max_map_count. Vous pouvez définir la valeur avec sudo sysctl -w vm.max_map_count = 65536.

Les modifications affectent l'ensemble du système et semblent être annulées lors d'un redémarrage.

Contre-mesures

J'ai essayé de multiplier la valeur de vm.max_map_count par 10 fois la valeur actuelle.

python


$ sysctl vm.max_map_count
vm.max_map_count = 65530
$ sudo sysctl -w vm.max_map_count=655300
vm.max_map_count = 655300

résultat

Après la contre-mesure, j'ai commencé le code pour générer à nouveau le tableau de tableau vide.

python


$ node --max-old-space-size=$((1024 * 48)) -e 'Array.from({length: 10000}, v => { console.log(process.memoryUsage().rss / 1024 / 1024 / 1024); return Array.from({length: 1000000}, v => []); });'

En conséquence, la mémoire d'environ 48 Go donnée comme prévu a été utilisée jusqu'à la fin, puis elle s'est terminée anormalement.

<Omis>

48.66949462890625
48.756752014160156
48.79401397705078
48.831199645996094
48.86867141723633

<--- Last few GCs --->

<Omis>

<--- JS stacktrace --->

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
 1: 0x9fd5f0 node::Abort() [node]

<Omis>

11: 0x13cc8f9  [node]
Aborted (core dumped)

Épilogue

Après tout, ce n'était pas un problème spécifique au nœud. Ce n'était même pas un problème C ++.

Au départ, la taille du tas est limitée séparément car elle s'arrête à "16,8 Go", je me demande si l'utilisation de la mémoire est une telle chose à cause de GC, et si 2,2 milliards d'objets sont nouveaux en C ++, un entier 32 bits suffit Je pensais qu'il pourrait disparaître et échouer à nouveau.

Recommended Posts

Lors de l'augmentation de la mémoire disponible du nœud, elle peut être limitée par vm.max_map_count
PyQtGraph peut ne pas être disponible dans l'interpréteur.
Soyez prudent lors de la différenciation des vecteurs propres d'une matrice
Lors de l'utilisation de tf.print (), le contenu du tenseur ne peut pas être affiché s'il se trouve dans une f-string.
Trouver le diamètre du graphique par recherche de priorité de largeur (mémoire Python)
Aiguille en or pour quand elle devient une pierre en regardant la formule du traitement d'image