Esta es la segunda y última parte de mi hoja de trucos de abreviaturas que un desarrollador de C ++ debe conocer. C ++ se menciona aquí solo porque creé la hoja de trucos principalmente para mí, pero soy el mismo desarrollador de C ++.
De hecho, esta parte contiene conceptos cuyo alcance no se limita a C ++. Por lo tanto, la selección puede ser de interés para un público más amplio.

Como en la
primera parte , las abreviaturas se agrupan, si eso tiene sentido. Si no tiene sentido, se enumeran alfabéticamente.
Concurrencia y operaciones atómicas:•
CAS•
ABA•
FAA•
UCRAlmacenamiento de datos:•
ÁCIDO•
CAP•
PACELC•
BASEPrincipios de desarrollo de software:•
SECO•
BESO•
YAGNI•
NIH•
FTSE•
GRASP•
SÓLIDOOtros:•
ABI•
VACA•
FBC, FBCP•
LRUConcurrencia y operaciones atómicas
Cas
Comparar e intercambiar. Comparación con el intercambio. Esta es una instrucción atómica con tres argumentos: variable atómica o dirección de memoria, valor esperado, nuevo valor. Si y solo si el valor de la variable coincide con el valor esperado, la variable recibe un nuevo valor y la instrucción se completa con éxito.
CAS simplemente devuelve un valor booleano (y luego se puede llamar Comparar y establecer), o si falla, también devuelve el valor actual del primer argumento.
Pseudocódigobool cas(int* addr, int& expected, int new_value) { if (*addr != expected) { expected = *addr; return false; } *addr = new_value; return true; }
En C ++,
CAS está representado por las familias de métodos
std::atomic<T>::compare_exchange_weak
,
std::atomic<T>::compare_exchange_strong
y funciones libres
std::atomic_compare_exchange_weak
,
std::atomic_compare_exchange_strong
. La diferencia entre
* débil y
* fuerte es que el primero puede producir resultados falsos negativos. Es decir si se espera el valor, devolverán
false
y no lo reemplazarán por uno nuevo. La razón de la existencia de
* operaciones
débiles es que en algunas arquitecturas
* fuertes es relativamente costoso. En la mayoría de los casos, las instrucciones
CAS giran en un bucle (el llamado bucle CAS), por lo que usar
* débil en lugar de
* fuerte no cambiará la lógica, pero puede mejorar el rendimiento.
Las instrucciones
CAS se utilizan para implementar primitivas de sincronización (como mutexes y semáforos) y algoritmos sin bloqueo. A menudo conducen a un problema
ABA .
Leer más:
una vez (ruso) ,
dos (inglés)ABA
Problema ABA. Problema ABA. Este problema surge en algoritmos paralelos basados en comparaciones con intercambios (ver
CAS ), por ejemplo, en algoritmos sin bloqueo. La conclusión es que el hilo lee el valor de la variable atómica, hace algo más y actualiza esta variable a través de la comparación con el intercambio. Es decir La lógica de flujo es la siguiente: si la variable aún contiene el valor anterior, entonces nada ha cambiado, todo está en orden. Pero esto puede no ser así. Una descripción más formal del problema:
- el hilo 1 lee el valor de la variable, es igual a A
- el hilo 1 se está forzando a salir, el hilo 2 está comenzando
- el hilo 2 cambia el valor de la variable de A a B, realiza un montón de cambios (cambia algún valor asociado con la variable o simplemente libera memoria), y luego cambia nuevamente el valor, de B a A
- el subproceso 1 reanuda la operación, compara el valor obtenido previamente con el actual y concluye que nada ha cambiado
Posibles soluciones al problema:
- Lo más simple y obvio es usar cerraduras. Esto dará como resultado el algoritmo habitual de seguridad de subprocesos con secciones críticas. Pero dejará de estar libre de cerraduras. Pero si se trata de CAS y ABA , entonces probablemente no sea una opción.
- Agregue etiquetas especiales a los valores comparados. Por ejemplo, un contador del número de cambios. Por un lado, este contador puede desbordarse, pero por otro lado, los procesadores x86_64 modernos admiten operaciones CAS de 128 bits. Es decir al comparar punteros con un contador, puede dar hasta 64 bits, y alguien pensó que esto es suficiente para 10 años de operación continua del algoritmo.
- Algunas arquitecturas (ARM, por ejemplo) proporcionan instrucciones LL / SC (carga vinculada, almacenamiento condicional) que no solo le permiten obtener el valor actual de la dirección en la memoria, sino también comprender si este valor ha cambiado desde la última lectura.
Para utilizar estructuras de datos como una pila, una lista o una cola, que están libres de bloqueos, en general, donde existe el riesgo de quedarse con un puntero colgante a un nodo remoto, existe toda una familia de soluciones al problema
ABA basado en la eliminación diferida de nodos. Esto incluye el recolector de basura, los punteros de peligro y el mecanismo de lectura-modificación-escritura (ver
RCU ).
Leer más:
uno (ruso) ,
dos (inglés) ,
tres (inglés)FAA
Obtener y agregar. Ejem ... obtener y agregar (parece que este concepto nunca se traduce al ruso). Una operación atómica con dos argumentos: una variable atómica o una dirección en la memoria, y el valor por el cual esta variable debe cambiarse. Si la arquitectura lo permite, la operación devuelve el valor anterior de la variable modificada (x86 lo permite desde i486). A diferencia de
CAS , la
FAA siempre tiene éxito.
Pseudocódigo int faa(int* addr, int diff) { int value = *addr; *addr = value + diff; return value; }
En C ++, se implementa como las familias de métodos
std::atomic<T>::fetch_add
,
fetch_sub
,
fetch_and
,
fetch_or
,
fetch_xor
y las funciones libres correspondientes
std::atomic_fetch_add
, etc.
Como corresponde a una instrucción atómica, la
FAA se utiliza en implementaciones de primitivas de sincronización y algoritmos y estructuras de datos sin bloqueo.
Leer más:
una vez (ruso) ,
dos (inglés)UCR
Leer-Copiar-Actualizar. Leer-modificar-escribir. Este es un mecanismo sin bloqueo para sincronizar el acceso a la estructura de datos (sin bloqueo, por supuesto). Se utiliza en casos donde la velocidad de lectura es crítica. Es un ejemplo de la compensación del tiempo y la memoria (compensación espacio-tiempo).
La idea de
RCU es que la secuencia del escritor no cambia los datos en su lugar, sino que crea una copia, realiza los cambios necesarios y cambia atómicamente los datos actuales y la copia modificada. Al mismo tiempo, los hilos de lectura tienen acceso constante a los datos, viejos o nuevos, quien tenga tiempo. Cuando no quedan lectores trabajando con la versión desactualizada, el escritor elimina los datos que ya no son necesarios, liberando memoria.
La RCU muy simplificada funciona así:
- Muchos lectores, un escritor.
- La lectura y el cambio ocurren simultáneamente.
- Los lectores usan sincronización muy ligera. De hecho, el lector solo tiene que notificar al escritor al momento de ingresar a la sección crítica y al momento de abandonarla. El trabajo con datos sincronizados ocurre solo en la sección crítica.
- El escritor, tan pronto como reemplaza atómicamente los datos con una copia, anuncia el comienzo del llamado período de gracia (período de gracia). El período de gracia finaliza cuando todos los lectores que se encontraban en secciones críticas al comienzo de este período abandonaron sus secciones críticas. Ahora el escritor puede eliminar de forma segura datos obsoletos. Se da a entender que todas las secciones críticas son finitas, lo que garantiza la finitud del período de gracia.
RCU es ideal para datos que a menudo se leen y rara vez se actualizan. Este mecanismo se usa activamente en el kernel de Linux, donde es bastante simple determinar cuándo termina el período de gracia.
Desventajas
- Pobre para sincronizar el acceso a datos frecuentemente modificados.
- Difícil de implementar en el espacio de usuario.
- Depende de la capacidad de cambiar atómicamente los punteros a una dirección en la memoria, pero no todas las arquitecturas proporcionan esta capacidad.
Leer más:
una vez (ruso) ,
dos (inglés)Almacenamiento de datos
ÁCIDO
Atomicidad, Consistencia, Aislamiento, Durabilidad. Atomicidad, consistencia, aislamiento, durabilidad. Este es un conjunto de requisitos de transacción en un DBMS.
ACID proporciona una operación DBMS confiable y predecible incluso en caso de errores.
- Atomicity asegura que la transacción se complete por completo o no haga nada. Un estado intermedio es imposible, no será tal que una operación de transacción haya sido exitosa y la otra no. Todo o nada
- La consistencia garantiza que todos los datos en la base de datos satisfagan todas las reglas y restricciones especificadas tanto antes del inicio de la transacción como después de su finalización. Durante la ejecución de la transacción, se puede violar la coherencia.
- El aislamiento asegura que las transacciones concurrentes no se afecten entre sí. Ninguna transacción tiene acceso a datos inconsistentes procesados por otra transacción.
- La durabilidad significa que el resultado de una transacción exitosa se almacena en la base de datos y no se puede perder, sin importar lo que le pase a la base de datos inmediatamente después de que se complete la transacción.
Todos los DBMS relacionales principales son totalmente compatibles con
ACID . En el mundo de NoSQL, este soporte completo es más probable que sea una excepción.
Leer más: una
vez (inglés) ,
dos (inglés)Gorra
Teorema CAP. Teorema de la PAC. El teorema establece que cualquier sistema distribuido no puede tener más de dos propiedades de la lista: consistencia de datos (
Consistencia ), disponibilidad (
Disponibilidad ), resistencia a la separación (
Tolerancia de partición ).
- La consistencia en este caso significa consistencia (simplificada) consistente. Es decir Tan pronto como la operación de actualización de datos en un nodo se haya completado con éxito, todos los demás nodos ya tienen estos datos actualizados. En consecuencia, todos los nodos están en un estado consistente. Esta no es la consistencia requerida por ACID .
- Disponibilidad significa que cada nodo fallido devuelve una respuesta correcta a cada solicitud (tanto para lectura como para escritura) en un período de tiempo razonable. No hay garantía de que las respuestas de los diferentes nodos coincidan.
- La resistencia a la separación significa que el sistema continuará funcionando correctamente en caso de pérdida de un número arbitrario de mensajes entre sus nodos.
Porque las tres propiedades son inalcanzables; desde el punto de vista del teorema
CAP , todos los sistemas distribuidos se dividen en tres clases:
CA ,
CP y
AP .
Los sistemas de
CA obviamente no tienen resistencia a la separación. Porque en la gran mayoría de los casos, la distribución implica distribución en una red real, y en una red real siempre hay una probabilidad distinta de cero de perder un paquete, entonces los sistemas de
CA son de poco interés.
La elección es entre
CP y
AP , es decir, entre consistencia y disponibilidad. Los DBMS relacionales tradicionales que siguen los principios de
ACID prefieren la consistencia. Mientras que muchas soluciones NoSQL eligen accesibilidad y
BASE .
En el caso del funcionamiento normal de la red, es decir, cuando no hay separación de red, el teorema de
CAP no impone ninguna restricción sobre la consistencia y la disponibilidad. Es decir donar algo no es necesario.
Leer más:
uno (ruso) ,
dos (inglés) ,
tres (inglés)Pacelc
Teorema PACELC. Teorema de PACELC. Esta es una extensión del teorema
CAP , que establece que un sistema distribuido en el caso de una partición de red (
Partición ) se ve obligado a elegir entre
Disponibilidad (
Consistencia ), y en el caso de operación normal de la red (De lo
contrario ), debe elegir entre latencia (
Consistencia) )
En consecuencia, si el teorema
CAP distinguía 2 clases de sistemas que son estables con separación de red, entonces
PACELC tiene 4 de ellos:
PA / EL ,
PA / EC ,
PC / EL y
PC / EC . Algunas bases de datos NoSQL pueden cambiar su clase según la configuración.
Leer más:
una vez (ruso) ,
dos (inglés)BASE
Básicamente disponible, estado blando, consistencia eventual. Disponibilidad básica, estado frágil, consistencia a largo plazo. De acuerdo con el teorema de
CAP en sistemas distribuidos, algo tendrá que ser abandonado. La coherencia estricta generalmente se abandona a favor de la coherencia a largo plazo. Lo que significa que en ausencia de cambios en los datos, el sistema algún día eventualmente llegará a un estado consistente.
Para indicar tal compromiso, la abreviatura
BASE comenzó a usarse con cierta
rigidez , y resultó un juego de términos químicos (
ACID - acidez,
BASE - basicidad).
- Básicamente disponible significa que el sistema garantiza la disponibilidad de datos, responde a cada solicitud. Pero la respuesta puede ser datos obsoletos o inconsistentes (o falta de ellos)
- El estado suave significa que el estado del sistema puede cambiar con el tiempo incluso en ausencia de solicitudes de cambios de datos. Porque en cualquier momento, los datos pueden llevarse a un estado coherente.
- La coherencia eventual significa que si los datos dejan de cambiar, por supuesto terminarán en un estado coherente. Es decir La misma solicitud a diferentes nodos dará lugar a las mismas respuestas.
Leer más: una
vez (inglés) ,
dos (inglés)Principios de desarrollo de software
SECO
No te repitas. No repetir Este es el principio del desarrollo de software, cuya idea principal es reducir la cantidad de información duplicada en el sistema, y el objetivo es reducir la complejidad del sistema y aumentar su capacidad de administración.
En el libro original (
El libro del
programador pragmático , de
Andrew Hunt y
David Thomas ), este principio se formula de la siguiente manera: "Cada conocimiento debe tener una representación única, consistente y autorizada dentro del sistema". En este caso, se entiende por conocimiento cualquier parte de un área temática o algoritmo: un código, un esquema de base de datos, un cierto protocolo de interacción, etc. Por lo tanto, para realizar un cambio en el sistema, solo se necesita actualizar un "conocimiento" en un lugar.
Un ejemplo tonto: el cliente y el servidor transmiten datos estructurados entre sí. Porque Estas son diferentes aplicaciones que se ejecutan en diferentes máquinas, entonces ambas deben tener sus propias implementaciones de estas estructuras. Si algo cambia, los cambios deberán realizarse en dos lugares. Un paso obvio para evitar esta repetición es asignar el código común a una biblioteca separada. El siguiente paso es generarlo de acuerdo con la descripción de las estructuras (Google Protocol Buffers, por ejemplo), para no escribir el mismo tipo de código para acceder a los campos de las estructuras.
La duplicación de código es solo un caso especial de violación de
DRY . Y ese no es siempre el caso. Si dos partes del código se ven iguales, pero cada una implementa su propia lógica de negocios,
DRY no se rompe.
Como cualquier otro principio,
DRY es una herramienta, no un dogma. Cuanto más grande es el sistema, más fácil es violar este principio. Primero, el ideal es inalcanzable. Y en segundo lugar, si seguir a ciegas
DRY conduce a un código más complicado y hace que sea más difícil de entender, entonces es mejor abandonarlo.
Leer más:
uno (ruso) ,
dos (ruso)BESO
Mantenlo simple, estúpido. Hazlo más fácil (estúpido en este caso no es una llamada). Este es el principio de diseño de que la mayoría de los sistemas funcionan mejor si siguen siendo simples. El principio se originó en la industria aeronáutica y se aplica mucho donde, incluso en el desarrollo de software.
En este último caso,
KISS es útil tanto en el diseño como en la escritura de código directamente. La arquitectura y el código simples no solo son más fáciles de entender, sino que también son más fáciles de usar, mantener y desarrollar. MapReduce no debe ser engañado si un par productor-consumidor es suficiente. La magia de la metaprogramación es excesivamente compleja si puede hacer un par de funciones ordinarias y lograr el nivel de rendimiento requerido.
Con todo esto, uno no debe olvidar que la simplicidad no es un objetivo, sino solo un requisito no funcional. Lo principal es lograr el objetivo de diseño / implementación, y es recomendable hacerlo de la manera más simple posible.
Leer más:
uno (ruso) ,
dos (ruso) ,
tres (inglés)YAGNI
No lo vas a necesitar. No lo necesitas Este es el principio del desarrollo de software, cuya idea principal es el rechazo de la funcionalidad excesiva, y el objetivo es ahorrar recursos gastados en el desarrollo.
YAGNI dice que no necesita diseñar o implementar funciones que no son necesarias en este momento. Incluso si está seguro de que serán necesarios en el futuro. No es necesario agregar abstracciones innecesarias, cuyos beneficios aparecerán más adelante.
El problema es que las personas no predicen bien el futuro. Por lo tanto, lo más probable es que todo lo que se haga "en reserva" simplemente no sea útil. Y resulta que se desperdicia el tiempo y el dinero gastados en dicho desarrollo, pruebas y documentación. Además, el software se ha vuelto más complicado; se deben gastar recursos adicionales para respaldarlo nuevamente. Peor aún, cuando resulta que era necesario hacerlo de manera diferente. Se gastará dinero y tiempo también en la corrección.
El principio
YAGNI es
algo más radical que
DRY y
KISS . Si
dividen el sistema en partes comprensibles y hacen que las decisiones sean simples,
YAGNI simplemente elimina partes y soluciones innecesarias.
Leer más:
uno (ruso) ,
dos (inglés) ,
tres (inglés)Nih
No inventado aquí. No inventado aquí. Este es un síndrome de rechazo de los desarrollos de otras personas, una posición que prácticamente bordea la invención de una bicicleta. A menudo, el síndrome se acompaña de la creencia de que crear tecnología dentro de la empresa será más rápido y más barato, y la tecnología en sí misma satisfará mejor las necesidades de la empresa. Sin embargo, en la mayoría de los casos este no es el caso, y
NIH es un antipatrón.
Aquí hay algunos casos que justifican a los
NIH :
- La calidad de la solución de terceros no es lo suficientemente alta, o el precio no es lo suficientemente bajo.
- Una solución de terceros tiene restricciones de licencia.
- El uso de una solución de terceros crea dependencia en su proveedor y por lo tanto amenaza al negocio.
Leer más:
uno (ruso) ,
dos (inglés) ,
tres (inglés)Ftse
Teorema fundamental de la ingeniería de software. Teorema fundamental del desarrollo de software. En realidad, esto no es un teorema; no tiene pruebas. Este es un famoso dicho de Andrew Koenig:
Cualquier problema puede resolverse agregando otra capa de abstracción.
A veces se agregan a esta frase "... excepto por el problema de demasiadas capas de abstracción". En general, el "teorema" no es algo serio, pero vale la pena saberlo.
Leer más: una
vez (inglés) ,
dos (inglés)GRASP
Patrones de software de asignación de responsabilidad general. Plantillas de asignación de responsabilidad general. Estos nueve patrones están formulados en
Aplicación de UML y Patrones por
Craig Larman . Cada plantilla es una solución típica para un problema de diseño de software (pero bastante general).
- Experto en información . Problema: ¿cuál es el principio general de la distribución de responsabilidades entre objetos? Decisión: asignar un deber a alguien que tenga la información requerida para cumplir con esta obligación.
- Creador Problema: ¿quién debería ser responsable de crear un nuevo objeto? Solución: la clase B debe crear instancias de la clase A si se cumple una o más de las siguientes condiciones:
- la clase B agrega o contiene instancias de A
- B escribe A
- B usa activamente A
- B tiene datos de inicialización A - Acoplamiento bajo Problema: ¿cómo reducir el impacto del cambio? ¿Cómo aumentar la posibilidad de reutilización? Solución: distribuya las responsabilidades para que la conectividad sea baja. El acoplamiento es una medida de cuán rígidamente están conectados los elementos, cuánto dependen unos de otros. Es decir Se recomienda conectar objetos para que se conozcan solo el mínimo necesario.
- Engranaje alto ( alta cohesión ). Problema: ¿Cómo manejo la complejidad? Solución: distribuya las responsabilidades para mantener un alto compromiso. Un alto compromiso significa que las responsabilidades de un elemento se centran en un área.
- Controlador Problema: ¿quién debería ser responsable de manejar los eventos de entrada? Solución: asigne una clase como responsable, que represente todo el sistema o subsistema como un todo (controlador externo) o un script específico (script o controlador de sesión). Al mismo tiempo, el controlador no implementa una reacción a los eventos; lo delega a los ejecutores relevantes.
- Polimorfismo ( polimorfismo ). Problema: ¿cómo manejar diferentes comportamientos según el tipo? : , , , .
- ( Pure Fabrication ). : , ? : , .
- ( Indirection ). : , Low Coupling ? : .
- Resistencia a los cambios ( Variaciones protegidas ). Problema: ¿cómo diseñar objetos y subsistemas para que los cambios en ellos no tengan un efecto indeseable en otros elementos? Solución: encuentre posibles puntos de inestabilidad y cree una interfaz estable a su alrededor, comuníquese solo a través de dicha interfaz.
Los patrones GRASP se cruzan constantemente con los patrones Gang Four y los principios SOLID . Esto es normal, porque todos resuelven el problema común: simplificar la creación de software de alta calidad.Leer más: uno (ruso) , dos (inglés) , tres (ruso)SÓLIDO
Los principios de SOLID. Estos son los cinco principios de programación y diseño orientado a objetos (la abreviatura se hace a partir de las primeras letras de sus nombres). Ganó fama gracias a Robert Martin a principios de la década de 2000. El objetivo principal de los principios es crear software que sea fácil de entender, mantener y expandir.- El principio de la responsabilidad individual ( Individual Responsabilidad Principio, la SRP ). Una clase o módulo debe tener una sola responsabilidad. “Haz solo una cosa, pero hazlo bien”.
- / ( Open Closed Principle, OCP ). (, , ) , . : , .
- ( Liskov Substitution Principle, LSP ). , . Es decir - .
- ( Interface Segregation Principle, ISP ). , . , . , , . , .
- Dependencia Inversion Principio ( de Dependencia Inversion principio, el DIP ). Los módulos de nivel superior no deben depender de los módulos de nivel inferior. Todos los módulos deben depender de abstracciones. Las abstracciones no deberían depender de los detalles. Los detalles deben depender de las abstracciones. Por ejemplo, ni las interfaces ni las clases concretas deberían contener o aceptar otras clases concretas como argumentos de sus métodos, sino solo interfaces (en el sentido de Java y C #).
Leer más: uno (ruso) , dos (inglés) , tres (inglés)Otros
Abi
Interfaz binaria de aplicación. Interfaz de aplicación binaria. Este es un conjunto de convenciones que define la interacción de los módulos binarios (archivos ejecutables, bibliotecas, SO). Deben crearse dos módulos para cumplir con un ABI ; este es un requisito previo para su compatibilidad binaria, en este caso pueden interactuar sin problemas (por ejemplo, el archivo ejecutable está vinculado a la biblioteca y ejecutado por el sistema operativo).Ejemplos de ABI son los formatos de archivo ejecutables ELF en Linux y PE en Windows. Cada sistema operativo espera que los datos necesarios (recursos, punto de entrada, etc.) se encuentren en un archivo binario de acuerdo con el formato correspondiente. Obviamente, ELF y PE son diferentes, porque los programas de Linux no se ejecutan directamente en Windows y viceversa.A nivel de bibliotecas y archivos ejecutables, ABI puede determinar la ubicación de los campos dentro de una clase, las clases base dentro de los descendientes, el mecanismo para implementar funciones virtuales, el formato del marco de la pila de llamadas, las reglas para pasar argumentos a la función llamada, etc. etc.C ++ no tiene un solo ABI estándar , lo cual no es sorprendente, porque depende de la arquitectura y el sistema operativo. Por ejemplo, los compiladores de C ++ para muchos sistemas operativos tipo Unix (Linux, FreeBSD, MacOS) en x86_64 siguen System V AMD64 ABI , en ARM - ARM C ++ ABI . Visual C ++ ABI no se publica oficialmente, pero al menos parcialmente rediseñado. Es muy diferente del Sistema V ABI, tienen reglas completamente diferentes para ocultar nombres (destrozar) ypasar argumentos a la función llamada (Linux usa 6 registros, Windows usa 4 registros) y muchas otras diferencias.Incluso si la API y la ABI siguen siendo las mismas, y solo cambian los detalles de implementación, la compatibilidad binaria puede romperse . Por ejemplo, en C ++ 11, se requería que las cadenas almacenaran caracteres secuencialmente (como en un vector). Debido a esto, GCC 5 tuvo que cambiar la implementación de cadenas (antes se usaba COW ), lo que condujo a una incompatibilidad binaria.Leer más: una vez (ruso) , dos (inglés) y todos los enlaces de los dos párrafos anteriores.Vaca
Copia en escritura. Copia en la grabación. Este es un mecanismo de gestión de recursos, también conocido como uso compartido implícito y copia diferida. La idea es que cuando se requiere una copia, el recurso no se copia realmente, sino que se crea un enlace. Y solo cuando hay una solicitud de cambios, en el original o en la "copia", solo entonces se crea una copia completa.La ventaja de COW es obvia: la copia de cualquier objeto ocurre instantáneamente. Si los objetos a menudo se copian pero rara vez se modifican, las ganancias de rendimiento pueden ser significativas.Ejemplos de uso de VACA :- Proceso virtual de gestión de memoria en Linux. Cuando
fork()
se llaman las páginas, la memoria del proceso no se copia, sino que solo se marca como compartida. - Instantáneas en algunos sistemas de archivos (Btrfs, ZFS) y bases de datos (MS SQL Server).
- Antes de C ++ 11, algunas implementaciones
std::string
usaban COW . En C ++ 11, los requisitos para std::string
han cambiado (ver ABI ). - Muchos tipos en Qt usan COW .
Leer más: una vez (inglés) , dos (inglés)FBC, FBCP
Clase base frágil (problema). El problema de una clase base frágil. Este es un problema fundamental de OOP, su esencia es que el cambio correcto de la clase base puede conducir a un error en uno de los herederos.Por ejemplo, a la recursión infinita. struct Base { virtual void method1() {
Puede resolver el problema de FBC solo abandonando la herencia en favor de la composición, por ejemplo, o extendiendo las interfaces en la terminología de Java (en C ++, esto se heredará solo a las clases base abstractas sin implementaciones de estado y método). En otros casos, solo puede intentar minimizar la probabilidad de FBCP con los siguientes consejos:- No permita la herencia o la redefinición donde no sean necesarias (palabra clave
final
en C ++ y Java). - El heredero no debe tener acceso a los interiores de la clase base, la comunicación solo puede pasar a través de la interfaz pública.
- El método heredero solo puede llamar a los métodos virtuales que se invocan en el método redefinido de la clase base y el método redefinido en sí.
Leer más: uno (inglés) , dos (inglés) , tres (inglés)LRU
Menos utilizado recientemente. La extrusión es larga sin usar. Este es uno de los algoritmos de almacenamiento en caché (también son políticas preventivas). En general, la memoria caché puede considerarse un almacenamiento rápido de pares clave-valor, una de cuyas características principales es la proporción de aciertos. Cuanto mayor sea este nivel, más a menudo el valor deseado se encuentra en la memoria caché rápida y con menos frecuencia debe buscarse en un almacenamiento lento. Pero como la memoria nunca es gomosa, el tamaño del caché tiene que ser limitado. La tarea de los algoritmos de almacenamiento en caché es determinar qué elemento arrojar fuera del caché lleno, si es necesario, para maximizar el nivel de visitas.LRUreemplaza su elemento de caché, al que nadie ha accedido más tiempo. Este es quizás el algoritmo de almacenamiento en caché más famoso. Probablemente debido a la combinación de eficiencia y simplicidad. El consumo de memoria de LRU es O (n), el tiempo promedio de acceso al valor es O (1), el tiempo promedio para agregar un elemento también es O (1). Para la implementación, generalmente se usa una tabla hash y una lista doblemente vinculada.Por ejemplo, entonces template <class K, class V> class LRU { private: using Queue = std::list<std::pair<K, V>>; using Iterator = typename Queue::iterator; using Hash = std::unordered_map<K, Iterator>; Queue queue_; Hash hash_; const size_t limit_; public: LRU(size_t limit) : limit_(limit) { } std::optional<V> get(const K& key) { const auto it = hash_.find(key); if (it == hash_.end()) { return {}; } it->second = reorder(it->second); return { it->second->second }; } void add(K&& key, V&& value) { if (hash_.size() >= limit_) { pop(); } queue_.emplace_front(std::move(key), std::move(value)); const auto it = queue_.begin(); hash_[it->first] = it; } private: Iterator reorder(Iterator it) { queue_.emplace_front(std::move(it->first), std::move(it->second)); queue_.erase(it); return queue_.begin(); } void pop() { hash_.erase(queue_.back().first); queue_.pop_back(); } };
El inconveniente obvio de LRU es el alto consumo de memoria, ya que utiliza dos estructuras de n elementos cada una. Además de LRU, hay muchos otros algoritmos de almacenamiento en caché para una variedad de casos: MRU (uso más reciente), LFU (uso menos frecuente), LRU segmentado, 2Q, etc.Lea una vez más: una vez (inglés) , dos (ruso) , tresPS
Si me perdí algo o me equivoqué en alguna parte, escriba los comentarios.