c++ - ¿Multithreading enfatiza la fragmentación de la memoria?
memory openmp (3)
Al vincular el programa de prueba con la biblioteca tcmalloc de google , el ejecutable no solo se ejecuta ~ 10% más rápido, sino que también muestra una fragmentación de memoria muy reducida o insignificante:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
13441 byron 20 0 379m 334m 1220 R 187 8.4 0:02.63 ompmemtestgoogle
13441 byron 20 0 1085m 1.0g 1220 R 194 26.2 0:08.52 ompmemtestgoogle
13441 byron 20 0 1111m 1.0g 1220 R 195 26.9 0:14.42 ompmemtestgoogle
13441 byron 20 0 1131m 1.1g 1220 R 195 27.4 0:20.30 ompmemtestgoogle
13441 byron 20 0 1137m 1.1g 1220 R 195 27.6 0:26.19 ompmemtestgoogle
13441 byron 20 0 1137m 1.1g 1220 R 195 27.6 0:32.05 ompmemtestgoogle
13441 byron 20 0 1149m 1.1g 1220 R 191 27.9 0:37.81 ompmemtestgoogle
13441 byron 20 0 1149m 1.1g 1220 R 194 27.9 0:43.66 ompmemtestgoogle
13441 byron 20 0 1161m 1.1g 1220 R 188 28.2 0:49.32 ompmemtestgoogle
13441 byron 20 0 1161m 1.1g 1220 R 194 28.2 0:55.15 ompmemtestgoogle
13441 byron 20 0 1161m 1.1g 1220 R 191 28.2 1:00.90 ompmemtestgoogle
13441 byron 20 0 1161m 1.1g 1220 R 191 28.2 1:06.64 ompmemtestgoogle
13441 byron 20 0 1161m 1.1g 1356 R 192 28.2 1:12.42 ompmemtestgoogle
De los datos que tengo, la respuesta parece ser:
El acceso multiproceso al heap puede enfatizar la fragmentación si la biblioteca de heap no funciona bien con el acceso simultáneo y si el procesador no puede ejecutar los hilos de manera verdaderamente simultánea .
La biblioteca tcmalloc no muestra una fragmentación de memoria significativa que ejecute el mismo programa que anteriormente causaba la pérdida de ~ 400 MB en la fragmentación.
Pero, ¿por qué sucede eso?
La mejor idea que tengo para ofrecer aquí es algún tipo de artefacto de bloqueo dentro del montón.
El programa de prueba asignará bloques de memoria de tamaño aleatorio, liberando bloques asignados al principio del programa para mantenerse dentro de su límite de memoria. Cuando un hilo está en el proceso de liberar memoria vieja que está en un bloque de montón en la ''izquierda'', en realidad podría detenerse ya que otro hilo está programado para ejecutarse, dejando un bloqueo (suave) en ese bloque de montón. El subproceso recién programado desea asignar memoria, pero es posible que ni siquiera lea ese bloque de pila en el lado ''izquierdo'' para comprobar la memoria libre tal como se está modificando actualmente. Por lo tanto, podría terminar utilizando un nuevo bloque de montón innecesariamente desde el ''derecho''.
Este proceso podría verse como un cambio de bloque de pila, donde los primeros bloques (a la izquierda) solo se usan y fragmentan escasamente, lo que obliga a utilizar bloques nuevos a la derecha.
Vamos a replantear que este problema de fragmentación solo se produce para mí si uso 4 o más subprocesos en un sistema dual core que solo puede manejar dos subprocesos más o menos al mismo tiempo. Cuando solo se utilizan dos subprocesos, los bloqueos (suaves) en el montón se mantendrán lo suficientemente cortos como para no bloquear el otro subproceso que desea asignar memoria.
Además, como descargo de responsabilidad, no revisé el código real de la implementación del glibc heap, ni soy nada más que novato en el campo de los asignadores de memoria; todo lo que escribí es cómo me parece que lo convierte en pura especulación.
Otra lectura interesante podría ser la documentación de tcmalloc , que establece problemas comunes con montones y acceso de subprocesos múltiples, algunos de los cuales también pueden haber desempeñado su papel en el programa de prueba.
Vale la pena señalar que nunca devolverá la memoria al sistema (consulte el párrafo Advertencias en la documentación de tcmalloc )
Descripción
Al asignar y desasignar trozos de memoria de tamaño aleatorio con 4 o más subprocesos usando el paralelo de openmp para constructo, el programa parece comenzar a filtrar cantidades considerables de memoria en la segunda mitad del tiempo test-program''s ejecución test-program''s . Por lo tanto, aumenta su memoria consumida de 1050 MB a 1500 MB o más sin utilizar realmente la memoria extra.
Como valgrind no muestra problemas, debo suponer que lo que parece ser una pérdida de memoria en realidad es un efecto enfatizado de la fragmentación de la memoria.
Curiosamente, el efecto no se muestra aún si 2 subprocesos realizan 10000 asignaciones cada uno, pero se muestra con fuerza si 4 subprocesos realizan 5000 asignaciones cada uno. Además, si el tamaño máximo de los trozos asignados se reduce a 256 Kb (de 1 mb), el efecto se vuelve más débil.
¿Puede la concurrencia pesada enfatizar tanto la fragmentación? ¿O es más probable que sea un error en el montón?
Descripción del programa de prueba
El programa de demostración está construido para obtener un total de 256 MB de fragmentos de memoria de tamaño aleatorio del montón, realizando 5000 asignaciones. Si se alcanza el límite de memoria, los fragmentos asignados primero serán desasignados hasta que el consumo de memoria caiga por debajo del límite. Una vez que se realizaron 5000 asignaciones, se libera toda la memoria y el ciclo finaliza. Todo este trabajo se hace para cada hilo generado por openmp.
Este esquema de asignación de memoria nos permite esperar un consumo de memoria de ~ 260 MB por hilo (incluidos algunos datos de contabilidad).
Programa de demostración
Como esto es realmente algo que quizás desee probar, puede descargar el programa de ejemplo con un simple archivo MAKE desde test-program''s .
Cuando ejecute el programa como está, debe tener al menos 1400 MB de RAM disponibles. Siéntase libre de ajustar las constantes en el código para satisfacer sus necesidades.
Para completar, el código real sigue:
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <deque>
#include <omp.h>
#include <math.h>
typedef unsigned long long uint64_t;
void runParallelAllocTest()
{
// constants
const int NUM_ALLOCATIONS = 5000; // alloc''s per thread
const int NUM_THREADS = 4; // how many threads?
const int NUM_ITERS = NUM_THREADS;// how many overall repetions
const bool USE_NEW = true; // use new or malloc? , seems to make no difference (as it should)
const bool DEBUG_ALLOCS = false; // debug output
// pre store allocation sizes
const int NUM_PRE_ALLOCS = 20000;
const uint64_t MEM_LIMIT = (1024 * 1024) * 256; // x MB per process
const size_t MAX_CHUNK_SIZE = 1024 * 1024 * 1;
srand(1);
std::vector<size_t> allocations;
allocations.resize(NUM_PRE_ALLOCS);
for (int i = 0; i < NUM_PRE_ALLOCS; i++) {
allocations[i] = rand() % MAX_CHUNK_SIZE; // use up to x MB chunks
}
#pragma omp parallel num_threads(NUM_THREADS)
#pragma omp for
for (int i = 0; i < NUM_ITERS; ++i) {
uint64_t long totalAllocBytes = 0;
uint64_t currAllocBytes = 0;
std::deque< std::pair<char*, uint64_t> > pointers;
const int myId = omp_get_thread_num();
for (int j = 0; j < NUM_ALLOCATIONS; ++j) {
// new allocation
const size_t allocSize = allocations[(myId * 100 + j) % NUM_PRE_ALLOCS ];
char* pnt = NULL;
if (USE_NEW) {
pnt = new char[allocSize];
} else {
pnt = (char*) malloc(allocSize);
}
pointers.push_back(std::make_pair(pnt, allocSize));
totalAllocBytes += allocSize;
currAllocBytes += allocSize;
// fill with values to add "delay"
for (int fill = 0; fill < (int) allocSize; ++fill) {
pnt[fill] = (char)(j % 255);
}
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " New alloc " << pointers.size() << ", bytes:" << allocSize << " at " << (uint64_t) pnt << "/n";
}
// free all or just a bit
if (((j % 5) == 0) || (j == (NUM_ALLOCATIONS - 1))) {
int frees = 0;
// keep this much allocated
// last check, free all
uint64_t memLimit = MEM_LIMIT;
if (j == NUM_ALLOCATIONS - 1) {
std::cout << "Id " << myId << " about to release all memory: " << (currAllocBytes / (double)(1024 * 1024)) << " MB" << std::endl;
memLimit = 0;
}
//MEM_LIMIT = 0; // DEBUG
while (pointers.size() > 0 && (currAllocBytes > memLimit)) {
// free one of the first entries to allow previously obtained resources to ''live'' longer
currAllocBytes -= pointers.front().second;
char* pnt = pointers.front().first;
// free memory
if (USE_NEW) {
delete[] pnt;
} else {
free(pnt);
}
// update array
pointers.pop_front();
if (DEBUG_ALLOCS) {
std::cout << "Id " << myId << " Free''d " << pointers.size() << " at " << (uint64_t) pnt << "/n";
}
frees++;
}
if (DEBUG_ALLOCS) {
std::cout << "Frees " << frees << ", " << currAllocBytes << "/" << MEM_LIMIT << ", " << totalAllocBytes << "/n";
}
}
} // for each allocation
if (currAllocBytes != 0) {
std::cerr << "Not all free''d!/n";
}
std::cout << "Id " << myId << " done, total alloc''ed " << ((double) totalAllocBytes / (double)(1024 * 1024)) << "MB /n";
} // for each iteration
exit(1);
}
int main(int argc, char** argv)
{
runParallelAllocTest();
return 0;
}
El sistema de prueba
Por lo que veo hasta ahora, el hardware importa mucho. La prueba puede necesitar ajustes si se ejecuta en una máquina más rápida.
Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz
Ubuntu 10.04 LTS 64 bit
gcc 4.3, 4.4, 4.6
3988.62 Bogomips
Pruebas
Una vez que haya ejecutado el archivo MAKE, debe obtener un archivo llamado ompmemtest
. Para consultar el uso de la memoria a lo largo del tiempo, utilicé los siguientes comandos:
./ompmemtest &
top -b | grep ompmemtest
Que produce la fragmentación bastante impresionante o el comportamiento de fuga. El consumo de memoria esperado con 4 hilos es 1090 MB, que se convirtió en 1500 MB en el tiempo:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
11626 byron 20 0 204m 99m 1000 R 27 2.5 0:00.81 ompmemtest
11626 byron 20 0 992m 832m 1004 R 195 21.0 0:06.69 ompmemtest
11626 byron 20 0 1118m 1.0g 1004 R 189 26.1 0:12.40 ompmemtest
11626 byron 20 0 1218m 1.0g 1004 R 190 27.1 0:18.13 ompmemtest
11626 byron 20 0 1282m 1.1g 1004 R 195 29.6 0:24.06 ompmemtest
11626 byron 20 0 1471m 1.3g 1004 R 195 33.5 0:29.96 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 194 33.5 0:35.85 ompmemtest
11626 byron 20 0 1469m 1.3g 1004 R 195 33.6 0:41.75 ompmemtest
11626 byron 20 0 1636m 1.5g 1004 R 194 37.8 0:47.62 ompmemtest
11626 byron 20 0 1660m 1.5g 1004 R 195 38.0 0:53.54 ompmemtest
11626 byron 20 0 1669m 1.5g 1004 R 195 38.2 0:59.45 ompmemtest
11626 byron 20 0 1664m 1.5g 1004 R 194 38.1 1:05.32 ompmemtest
11626 byron 20 0 1724m 1.5g 1004 R 195 40.0 1:11.21 ompmemtest
11626 byron 20 0 1724m 1.6g 1140 S 193 40.1 1:17.07 ompmemtest
Tenga en cuenta que podría reproducir este problema al compilar con gcc 4.3, 4.4 y 4.6 (troncal) .
Ok, recogió el cebo.
Esto es en un sistema con
Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz
4x5666.59 bogomips
Linux meerkat 2.6.35-28-generic-pae #50-Ubuntu SMP Fri Mar 18 20:43:15 UTC 2011 i686 GNU/Linux
gcc version 4.4.5
total used free shared buffers cached
Mem: 8127172 4220560 3906612 0 374328 2748796
-/+ buffers/cache: 1097436 7029736
Swap: 0 0 0
Naive run
Lo acabo de ejecutar
time ./ompmemtest
Id 0 about to release all memory: 258.144 MB
Id 0 done, total alloc''ed -1572.7MB
Id 3 about to release all memory: 257.854 MB
Id 3 done, total alloc''ed -1569.6MB
Id 1 about to release all memory: 257.339 MB
Id 2 about to release all memory: 257.043 MB
Id 1 done, total alloc''ed -1570.42MB
Id 2 done, total alloc''ed -1569.96MB
real 0m13.429s
user 0m44.619s
sys 0m6.000s
Nada espectacular. Aquí está la salida simultánea de vmstat -SM 1
Datos en bruto de Vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
0 0 0 3892 364 2669 0 0 24 0 701 1487 2 1 97 0
4 0 0 3421 364 2669 0 0 0 0 1317 1953 53 7 40 0
4 0 0 2858 364 2669 0 0 0 0 2715 5030 79 16 5 0
4 0 0 2861 364 2669 0 0 0 0 6164 12637 76 15 9 0
4 0 0 2853 364 2669 0 0 0 0 4845 8617 77 13 10 0
4 0 0 2848 364 2669 0 0 0 0 3782 7084 79 13 8 0
5 0 0 2842 364 2669 0 0 0 0 3723 6120 81 12 7 0
4 0 0 2835 364 2669 0 0 0 0 3477 4943 84 9 7 0
4 0 0 2834 364 2669 0 0 0 0 3273 4950 81 10 9 0
5 0 0 2828 364 2669 0 0 0 0 3226 4812 84 11 6 0
4 0 0 2823 364 2669 0 0 0 0 3250 4889 83 10 7 0
4 0 0 2826 364 2669 0 0 0 0 3023 4353 85 10 6 0
4 0 0 2817 364 2669 0 0 0 0 3176 4284 83 10 7 0
4 0 0 2823 364 2669 0 0 0 0 3008 4063 84 10 6 0
0 0 0 3893 364 2669 0 0 0 0 4023 4228 64 10 26 0
¿Esa información significa algo para ti?
Google Thread Caching Malloc
Ahora, para la diversión real, agrega un poco de especias
time LD_PRELOAD="/usr/lib/libtcmalloc.so" ./ompmemtest
Id 1 about to release all memory: 257.339 MB
Id 1 done, total alloc''ed -1570.42MB
Id 3 about to release all memory: 257.854 MB
Id 3 done, total alloc''ed -1569.6MB
Id 2 about to release all memory: 257.043 MB
Id 2 done, total alloc''ed -1569.96MB
Id 0 about to release all memory: 258.144 MB
Id 0 done, total alloc''ed -1572.7MB
real 0m11.663s
user 0m44.255s
sys 0m1.028s
Parece más rápido, ¿no?
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
4 0 0 3562 364 2684 0 0 0 0 1041 1676 28 7 64 0
4 2 0 2806 364 2684 0 0 0 172 1641 1843 84 14 1 0
4 0 0 2758 364 2685 0 0 0 0 1520 1009 98 2 1 0
4 0 0 2747 364 2685 0 0 0 0 1504 859 98 2 0 0
5 0 0 2745 364 2685 0 0 0 0 1575 1073 98 2 0 0
5 0 0 2739 364 2685 0 0 0 0 1415 743 99 1 0 0
4 0 0 2738 364 2685 0 0 0 0 1526 981 99 2 0 0
4 0 0 2731 364 2685 0 0 0 684 1536 927 98 2 0 0
4 0 0 2730 364 2685 0 0 0 0 1584 1010 99 1 0 0
5 0 0 2730 364 2685 0 0 0 0 1461 917 99 2 0 0
4 0 0 2729 364 2685 0 0 0 0 1561 1036 99 1 0 0
4 0 0 2729 364 2685 0 0 0 0 1406 756 100 1 0 0
0 0 0 3819 364 2685 0 0 0 4 1159 1476 26 3 71 0
En caso de que quisiera comparar los resultados de vmstat
Valgrind --tool massif
Este es el encabezado de salida de ms_print
después de valgrind --tool=massif ./ompmemtest
(malloc predeterminado):
--------------------------------------------------------------------------------
Command: ./ompmemtest
Massif arguments: (none)
ms_print arguments: massif.out.beforetcmalloc
--------------------------------------------------------------------------------
GB
1.009^ :
| ##::::@@:::::::@@::::::@@::::@@::@::::@::::@:::::::::@::::::@:::
| # :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@:::
| # :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@:::
| :# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@:::
| :# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@:::
| :# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| : ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| : ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| :: ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| :: ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::: ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::: ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
| ::: ::# :: :@ :::: ::@ : ::::@ :: :@ ::@::::@: ::@:::::: ::@::::::@::::
0 +----------------------------------------------------------------------->Gi
0 264.0
Number of snapshots: 63
Detailed snapshots: [6 (peak), 10, 17, 23, 27, 30, 35, 39, 48, 56]
Google HEAPPROFILE
Desafortunadamente, valgrind
vainilla no funciona con tcmalloc
, así que cambié de caballos midrace a heap profiling con google-perftools
gcc openMpMemtest_Linux.cpp -fopenmp -lgomp -lstdc++ -ltcmalloc -o ompmemtest
time HEAPPROFILE=/tmp/heapprofile ./ompmemtest
Starting tracking the heap
Dumping heap profile to /tmp/heapprofile.0001.heap (100 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0002.heap (200 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0003.heap (300 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0004.heap (400 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0005.heap (501 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0006.heap (601 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0007.heap (701 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0008.heap (801 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0009.heap (902 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0010.heap (1002 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0011.heap (2029 MB allocated cumulatively, 1031 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0012.heap (3053 MB allocated cumulatively, 1030 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0013.heap (4078 MB allocated cumulatively, 1031 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0014.heap (5102 MB allocated cumulatively, 1031 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0015.heap (6126 MB allocated cumulatively, 1033 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0016.heap (7151 MB allocated cumulatively, 1029 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0017.heap (8175 MB allocated cumulatively, 1029 MB currently in use)
Dumping heap profile to /tmp/heapprofile.0018.heap (9199 MB allocated cumulatively, 1028 MB currently in use)
Id 0 about to release all memory: 258.144 MB
Id 0 done, total alloc''ed -1572.7MB
Id 2 about to release all memory: 257.043 MB
Id 2 done, total alloc''ed -1569.96MB
Id 3 about to release all memory: 257.854 MB
Id 3 done, total alloc''ed -1569.6MB
Id 1 about to release all memory: 257.339 MB
Id 1 done, total alloc''ed -1570.42MB
Dumping heap profile to /tmp/heapprofile.0019.heap (Exiting)
real 0m11.981s
user 0m44.455s
sys 0m1.124s
Póngase en contacto conmigo para obtener registros completos / detalles
Actualizar
A los comentarios: actualicé el programa
--- omptest/openMpMemtest_Linux.cpp 2011-05-03 23:18:44.000000000 +0200
+++ q/openMpMemtest_Linux.cpp 2011-05-04 13:42:47.371726000 +0200
@@ -13,8 +13,8 @@
void runParallelAllocTest()
{
// constants
- const int NUM_ALLOCATIONS = 5000; // alloc''s per thread
- const int NUM_THREADS = 4; // how many threads?
+ const int NUM_ALLOCATIONS = 55000; // alloc''s per thread
+ const int NUM_THREADS = 8; // how many threads?
const int NUM_ITERS = NUM_THREADS;// how many overall repetions
const bool USE_NEW = true; // use new or malloc? , seems to make no difference (as it should)
Corrió por más de 5 m3. Cerca del final, una captura de pantalla de htop enseña que, de hecho, el conjunto reservado es ligeramente más alto, yendo hacia 2.3g:
1 [||||||||||||||||||||||||||||||||||||||||||||||||||96.7%] Tasks: 125 total, 2 running
2 [||||||||||||||||||||||||||||||||||||||||||||||||||96.7%] Load average: 8.09 5.24 2.37
3 [||||||||||||||||||||||||||||||||||||||||||||||||||97.4%] Uptime: 01:54:22
4 [||||||||||||||||||||||||||||||||||||||||||||||||||96.1%]
Mem[||||||||||||||||||||||||||||||| 3055/7936MB]
Swp[ 0/0MB]
PID USER NLWP PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
4330 sehe 8 20 0 2635M 2286M 908 R 368. 28.8 15:35.01 ./ompmemtest
Comparando los resultados con una ejecución de tcmalloc: 4m12s, las estadísticas superiores similares tienen diferencias menores; la gran diferencia está en el conjunto de VIRT (pero eso no es particularmente útil a menos que tenga un espacio de direcciones muy limitado por proceso). El conjunto RES es bastante similar, si me preguntas. Lo más importante a notar es el paralelismo se incrementa; todos los núcleos ahora están al máximo. Esto se debe obviamente a la menor necesidad de bloquear operaciones en montón cuando se usa tcmalloc:
Si la lista libre está vacía: (1) Buscamos un grupo de objetos de una lista libre central para esta clase de tamaño (la lista libre central es compartida por todos los hilos). (2) Colóquelos en la lista gratuita local de subprocesos. (3) Devuelve uno de los objetos recién recuperados a las aplicaciones.
1 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%] Tasks: 172 total, 2 running
2 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%] Load average: 7.39 2.92 1.11
3 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%] Uptime: 11:12:25
4 [|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||100.0%]
Mem[|||||||||||||||||||||||||||||||||||||||||||| 3278/7936MB]
Swp[ 0/0MB]
PID USER NLWP PRI NI VIRT RES SHR S CPU% MEM% TIME+ Command
14391 sehe 8 20 0 2251M 2179M 1148 R 379. 27.5 8:08.92 ./ompmemtest
Sí, el malloc predeterminado (Dependiendo de la versión de Linux) hace algunas cosas locas que fallan de manera masiva en algunas aplicaciones de múltiples hilos. Específicamente, se mantiene casi por montones de hilos (arenas) para evitar el bloqueo. Esto es mucho más rápido que un solo montón para todos los hilos, pero memoria masiva ineficiente (a veces). Puedes sintonizar esto usando un código como este que apaga las múltiples arenas (¡esto mata el rendimiento así que no lo hagas si tienes muchas asignaciones pequeñas!)
rv = mallopt(-7, 1); // M_ARENA_TEST
rv = mallopt(-8, 1); // M_ARENA_MAX
O como otros sugirieron usar varios reemplazos para malloc.
Básicamente, es imposible que un Malloc de uso general sea siempre eficiente ya que no sabe cómo se va a utilizar.
ChrisP.