Definição de bits e a legibilidade do código
-
20-09-2019 - |
Pergunta
Eu tenho um Arduino aplicativo (bem, na verdade, uma biblioteca) que tem um número de sinalizadores de status - e, originalmente, eu simplesmente declarou-los como ints (bem uint8_t, de modo 8 bits não assinados caracteres, neste caso).Mas eu poderia ter combinado-as em um número inteiro e usado máscara de operações para definir e testar o estado.
Um exemplo do primeiro:
if (_shift == HIGH)
{
_shift = LOW;
}
else
{
_shift = HIGH;
}
um exemplo do segundo
#define SHIFT_BIT 0
if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
bitWrite(_flags, SHIFT_BIT, LOW);
}
else
{
bitWrite(_flags, SHIFT_BIT, HIGH);
}
O ex-lê melhor, mas o último é mais eficiente (espaço e tempo).Deve o espaço e o tempo da eficiência de sempre vencer nesta situação, ou é um tipo de otimização que só deve acontecer quando necessário?
(Adicionado)
Para a completude aqui está a Fiação definição desses 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))
Solução
Confira Raymond Chen excelente tomar sobre este problema.Em resumo, você precisa fazer algum cálculo detalhado para descobrir se o caso é realmente mais eficiente, dependendo de quantos objetos há vs.quantas callsites na verdade, esses estados.
Quanto a legibilidade, parece que você está fazendo isso com variáveis de membro, o que significa que você, provavelmente, encapsulado-los em bom funções.Nesse caso, eu não estou tão preocupado com a legibilidade, pois pelo menos o código para o povo usando a classe parece bom.No entanto, você sempre pode compactá-lo em funções privadas se é uma preocupação.
Outras dicas
Dependendo da conformidade do compilador AVR-GCC, sobre o qual não tenho certeza, você pode fazer algo assim e manter as coisas boas e limpas.
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;
}
Se você também deseja acesso a toda a bandeira int de uma só vez, então:
union
{
struct {
unsigned int flag1 : 1; //1 sets the length of the field in bits
unsigned int flag2 : 4;
} bits;
unsigned int val;
} flags;
Você pode acessar um pouco com 2 níveis de indireção: variable.bits.flag1
<-Retorna a bandeira de bits únicos ou com um único nível para obter todo o valor de bandeiras: variable.val
<-retorna int
Pode ser mais claro se você remover a necessidade de usar constantes HIGH
e LOW
, dividindo -se em dois métodos. Apenas faça bitSet
e bitClear
métodos. bitSet
define a parte para HIGH
, e bitClear
define a parte para LOW
. Então se torna:
#define SHIFT_BIT 0
if (bitRead(_flags, SHIFT_BIT) == HIGH)
{
bitClear(_flags, SHIFT_BIT);
}
else
{
bitSet(_flags, SHIFT_BIT);
}
E, claro, se você apenas tem HIGH == 1
e LOW == 0
, então você não precisa do == Verifique.
A meu olho, até o seu último código ainda é bastante legível. Ao dar um nome a cada uma de suas bandeiras, o código pode ser lido sem muito esforço.
Uma maneira ruim de fazer isso seria usar números "mágicos":
if( _flags | 0x20 ) { // What does this number mean?
do_something();
}
Se você não precisa para otimizar, não fazê-lo e usar a solução mais simples.
Se você precisa fazer para otimizar, você deve saber o que:
A primeira versão seria minimamente mais rápido se você apenas definir ou limpar o bit, em vez de alternar-lo, porque então você não precisa ler a memória.
A primeira versão é a melhor de w.r.t.simultaneidade.No segundo você tem de ler-modificar-escrever, então você precisa se certificar de que os bytes de memória não é acessado simultaneamente.Normalmente você desativar as interrupções, o que aumenta a latência da interrupção um pouco.Também, esquecendo-se de desativar as interrupções podem resultar muito desagradável e difícil de encontrar bugs (o pior erro que eu encontrei até agora foi de exatamente esse tipo).
A primeira versão é minimamente melhor w.r.t.tamanho do código (menos uso do flash), porque cada acesso é de uma única carga ou operação de loja.A segunda abordagem necessidades adicionais de operações bit.
A segunda versão utiliza menos memória RAM, principalmente se você tem um monte desses bits.
A segunda versão também é mais rápido, se você quiser testar vários bits de uma só vez (por exemplo,é um dos conjunto de bits).
Se você está falando de legibilidade, bits e C ++, por que não encontro nada sobre std::bitset
lá? Entendo que a corrida de programador incorporada é bastante confortável com as máscaras de bits e desenvolveu uma cegueira por sua pura feia (as máscaras, não as corridas :), mas, além de máscaras e campos de bits, a biblioteca padrão também possui uma solução bastante elegante.
Um exemplo:
#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.
Para campos de bits, é melhor usar operações lógicas, para que você possa fazer:
if (flags & FLAG_SHIFT) {
flags &= ~FLAG_SHIFT;
} else {
flags |= FLAG_SHIFT;
}
Isso agora tem a aparência do primeiro com a eficiência deste último. Agora você poderia ter macros em vez de funções, então (se eu acertar, seria algo como):
#define bitIsSet(flags,bit) flags | bit
#define bitSet(flags,bit) flags |= bit
#define bitClear(flags,bit) flags &= ~bit
Você não tem a sobrecarga de chamar funções, e o código se torna mais legível novamente.
Ainda não brinquei com Arduino (ainda), mas já pode haver macros para esse tipo de coisa, não sei.
Eu diria que a primeira coisa que estou preocupada é: "#Define Shift 0" Por que não fazer uso de uma macro constante e não? No que diz respeito à eficiência, a constante permite determinar o tipo, certificando -se de que não será necessário conversão posteriormente.
Quanto à eficiência da sua técnica: - Primeiro, livre -se da cláusula else (por que definir o bit como alto se seu valor já estiver alto?) - Segundo, prefira ter algo legível primeiro, criadores / getters inlinados usando bits máscara internamente Faria o trabalho, seria eficiente e legível.
Quanto ao armazenamento, para C ++, eu tenderia a usar um bitset (combinado com uma enumeração).
É muito simples apenas dizer:
flags ^= bit;