Me gustaría obtener información sobre cómo pensar correctamente sobre los cierres de C ++ 11 y std::function
en términos de cómo se implementan y cómo se maneja la memoria.
Aunque no creo en la optimización prematura, tengo la costumbre de considerar detenidamente el impacto en el rendimiento de mis elecciones al escribir código nuevo. También hago una buena cantidad de programación en tiempo real, por ejemplo, en microcontroladores y para sistemas de audio, donde se deben evitar las pausas de asignación / desasignación de memoria no deterministas.
Por lo tanto, me gustaría desarrollar una mejor comprensión de cuándo usar o no usar lambdas de C ++.
Mi entendimiento actual es que una lambda sin cierre capturado es exactamente como una devolución de llamada C. Sin embargo, cuando el entorno se captura por valor o por referencia, se crea un objeto anónimo en la pila. Cuando se debe devolver un cierre de valor desde una función, uno lo envuelve std::function
. ¿Qué pasa con la memoria de cierre en este caso? ¿Se copia de la pila al montón? ¿Se libera siempre que std::function
se libera, es decir, se cuenta como referencia como a std::shared_ptr
?
Imagino que en un sistema en tiempo real podría configurar una cadena de funciones lambda, pasando B como un argumento de continuación a A, de modo que A->B
se cree una canalización de procesamiento . En este caso, los cierres A y B se asignarían una vez. Aunque no estoy seguro de si estos se asignarían en la pila o en el montón. Sin embargo, en general, esto parece seguro de usar en un sistema en tiempo real. Por otro lado, si B construye alguna función lambda C, que devuelve, entonces la memoria para C se asignaría y desasignaría repetidamente, lo que no sería aceptable para el uso en tiempo real.
En pseudocódigo, un bucle DSP, que creo que será seguro en tiempo real. Quiero realizar el procesamiento del bloque A y luego B, donde A llama a su argumento. Ambas funciones devuelven std::function
objetos, por f
lo que será un std::function
objeto, donde su entorno se almacena en el montón:
auto f = A(B); // A returns a function which calls B
// Memory for the function returned by A is on the heap?
// Note that A and B may maintain a state
// via mutable value-closure!
for (t=0; t<1000; t++) {
y = f(t)
}
Y uno que creo que podría ser malo para usar en código en tiempo real:
for (t=0; t<1000; t++) {
y = A(B)(t);
}
Y uno en el que creo que la memoria de pila probablemente se use para el cierre:
freq = 220;
A = 2;
for (t=0; t<1000; t++) {
y = [=](int t){ return sin(t*freq)*A; }
}
En el último caso, el cierre se construye en cada iteración del ciclo, pero a diferencia del ejemplo anterior, es barato porque es como una llamada de función, no se realizan asignaciones de montón. Además, me pregunto si un compilador podría "levantar" el cierre y realizar optimizaciones en línea.
¿Es esto correcto? Gracias.
operator()
. No hay que hacer ningún "levantamiento", las lambdas no son nada especial. Son solo una abreviatura de un objeto de función local.