Pregunta

Tengo una aplicación Arduino (bueno en realidad una biblioteca) que tiene una serie de indicadores de estado en ella - y en un principio simplemente les declara como enteros (así uint8_t caracteres de modo de 8 bits sin signo en este caso). Pero pude los he combinado en un número entero y se utiliza máscara de bits operaciones para establecer y probar el estado.

Un ejemplo de lo anterior:

if (_shift == HIGH)
{
    _shift = LOW;
}
else
{
    _shift = HIGH;
}

un ejemplo de este último

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
   bitWrite(_flags, SHIFT_BIT, LOW);
}
else
{
   bitWrite(_flags, SHIFT_BIT, HIGH);
}

El ex lee mejor, pero el último es más eficiente (espacio y tiempo). En caso de que la eficiencia del espacio y el tiempo siempre gana en esta situación o se trata de un tipo de optimización que sólo debería ocurrir cuando sea necesario?

(Agregado)

Para completar aquí es la definición de cableado de esos bitWrite etc. macros:

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
¿Fue útil?

Solución

Raymond Chen excelente tomar en este problema. En resumen, es necesario hacer algún cálculo detallado para averiguar si este último caso es en realidad más eficiente, dependiendo del número de objetos que hay frente a la cantidad de callsites la creación efectiva de estos estados.

En cuanto a la legibilidad, parece que usted está haciendo esto con variables miembro, lo que significa que probablemente les ha encapsulado en funciones interesantes. En ese caso, no estoy tan preocupado con la legibilidad ya que al menos el código para las personas que utilizan la clase se ve bien. Sin embargo, siempre se puede encapsular en eventos privados si es una preocupación.

Otros consejos

En función del cumplimiento del compilador AVR-GCC, que estoy seguro acerca de, usted puede hacer algo como esto y mantener todo limpio y ordenado.

struct flags {
    unsigned int flag1 : 1;  //1 sets the length of the field in bits
    unsigned int flag2 : 4;
}; 

flags data;

data.flag1 = 0;
data.flag2 = 12;

if (data.flag1 == 1)
{
    data.flag1 = 0;
}
else
{
    data.flag1 = 1;
}

Si también quiere tener acceso a toda la int bandera a la vez, entonces:

union 
{
    struct {
        unsigned int flag1 : 1;  //1 sets the length of the field in bits
        unsigned int flag2 : 4;
    } bits;
    unsigned int val;
} flags;

A continuación, puede acceder a un bit con 2 niveles de indirección: variable.bits.flag1 <- Muestra la marca de un solo bit o con un solo nivel para conseguir el valor total de las banderas int: variable.val <- int

Puede ser más clara si se elimina la necesidad de utilizar las constantes HIGH y LOW, dividiéndose en dos métodos. Simplemente haga métodos bitSet y bitClear. bitSet establece el bit de HIGH y bitClear establece el bit de LOW. Entonces se convierte en:

#define SHIFT_BIT 0

if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
    bitClear(_flags, SHIFT_BIT);
}
else
{
    bitSet(_flags, SHIFT_BIT);
}

Y, por supuesto, si sólo tiene HIGH == 1 y LOW == 0, entonces no es necesario el cheque ==.

A mis ojos, incluso su último código es todavía muy legible. Al dar un nombre a cada una de sus banderas, el código puede ser leído sin mucho esfuerzo.

Una mala manera de hacerlo sería utilizar los números "mágicos":

if( _flags | 0x20 ) {  // What does this number mean?
   do_something();
}

Si no es necesario para optimizar, no lo haga y utilice la solución más simple.

Si usted necesita para optimizar, usted debe saber por qué:

  • La primera versión sería mínimamente más rápida si ponemos o borrar el bit en lugar de alternar, porque entonces no es necesario leer la memoria.

  • La primera versión es mejor w.r.t. concurrencia. En la segunda has lectura-modificación-escritura, por lo que necesita para asegurarse de que el byte de la memoria no se accede al mismo tiempo. Por lo general, usted deshabilitar interrupciones, lo que aumenta su latencia de interrupción alguna. Además, olvidándose de deshabilitar las interrupciones puede conducir a muy desagradable y difícil de encontrar errores (el insecto más desagradable que me encontré hasta ahora era exactamente de este tipo).

  • La primera versión es mínimamente mejor w.r.t. el tamaño del código (menos uso de flash), porque cada acceso es una única operación de carga o tienda. El segundo enfoque necesita operaciones de bits adicionales.

  • La segunda versión es usa menos memoria RAM, especialmente si usted tiene una gran cantidad de estos bits.

  • La segunda versión es también más rápido si quieres probar varios bits a la vez (por ejemplo, es uno de los bits puestos).

Si estamos hablando de legibilidad, bit-conjuntos y C ++, ¿por qué no encontrar nada en std::bitset ahí? Yo entiendo que la carrera de programador incorporado es bastante cómodo con máscaras de bits, y se ha desarrollado una ceguera por su gran fealdad (las máscaras, las carreras no :), pero aparte de las máscaras y los campos de bits, la biblioteca estándar tiene una solución muy elegante, también.

Un ejemplo:

#include <bitset>

enum tFlags { c_firstflag, c_secondflag, c_NumberOfFlags };

...

std::bitset<c_NumberOfFlags> bits;

bits.set( c_firstflag );
if( bits.test( c_secondflag ) ) {
  bits.clear();
}

// even has a pretty print function!
std::cout << bits << std::endl;// does a "100101" representation.

En los campos de bits, es mejor utilizar las operaciones lógicas, por lo que podría hacer:

if (flags & FLAG_SHIFT) {
   flags &= ~FLAG_SHIFT;
} else {
   flags |= FLAG_SHIFT;
}

Esto ahora tiene el aspecto de los primeros con la eficiencia de este último. Ahora usted podría tener macros en lugar de funciones, por lo que (si he entendido bien - que sería algo así):

#define bitIsSet(flags,bit) flags | bit
#define bitSet(flags,bit) flags |= bit
#define bitClear(flags,bit) flags &= ~bit

Usted no tiene la sobrecarga de llamar a las funciones y el código se vuelve más fácil de leer de nuevo.

No he jugado con Arduino (aún), pero podría haber ya macros para este tipo de cosas, no sé.

Yo diría que la primera cosa que me preocupa es: "#Define SHIFT 0" ¿Por qué no hacer uso de una constante en lugar de una macro? En cuanto a la eficiencia llegar, la constante permite determinar el tipo, por lo tanto asegurarse de que no será necesaria la conversión después.

En cuanto a la eficiencia de su técnica: - en primer lugar, deshacerse de la cláusula else (¿por qué establece el bit de alto si su valor es ya alto?) - en segundo lugar, prefieren tener algo legible primeros, organismos inline / captadores utilizando bits de enmascaramiento internamente haría el trabajo, ser eficiente y sean legibles

.

Como para el almacenamiento, para C ++ Me tienden a utilizar una bitset (combinado con una enumeración).

¿Es demasiado sencillo sólo para decir:

flags ^= bit;
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top