Neste artigo, discutimos vários cenários de uso para conjunto sequencial, também chamado de inline asm. Para iniciantes, apresentamos sintaxe básica, referência de operando, restrições e armadilhas comuns das quais os novos usuários precisam estar cientes. Para usuários intermediários, discutimos a lista de sobrescrever, bem como tópicos de ramificação que facilitam o uso das instruções de ramificação dentro das sob-rotinas de conjunto sequencial no código C/C++. Por fim, discutimos sobrescrever memória e o atributo volátil para usuários avançados que usam conjunto sequencial para otimizar o código. Concluímos com um exemplo de bloqueio multiencadeado com conjunto sequencial.
No bloco de conjunto sequencial mostrado no código na Listagem 1, a instrução addc é usada para adicionar duas variáveis, op1 e op2. Em qualquer bloco de conjunto sequencial, as instruções de conjunto aparecem primeiro, seguidas pelas entradas e saídas, que são separadas por uma vírgula. As instruções de conjunto podem consistir em uma ou mais cadeias de caracteres entre aspas. A primeira vírgula separa os operandos de saída; a segunda vírgula separa os operandos de entrada. Se houver registradores de sobrescrever, eles são inseridos após a terceira vírgula. Se não houver entradas de sobrescrever para o bloco de conjunto sequencial, a terceira vírgula pode ser omitida, como a Listagem 2 mostra.
Listagem 1. Opcodes, entradas, saídas e sobrescrever
int res=0;
int op1=20;
int op2=30;
asm ( " addc. %0,%1,%2 \n"
: "=r"(res)
: "b"(op1), "r"(op2)
: "r0" );
|
Listagem 2. Nenhuma entrada de sobrescrita para o bloco do conjunto sequencial, então a terceira vírgula é omitida
asm ( " addc. %0,%1,%2 \n"
: "=r"(res)
: "b"(op1), "r"(op2) );
|
Observação:
A lista de sobrescrever é discutida mais adiante nesta seção.
Cada instrução "espera" que entradas e saídas sejam passadas em certo formato. No exemplo anterior, a instrução addc. espera que seus operandos sejam passados através de registros, assim, op1 e op2 são passados para o bloco do conjunto sequencial com as restrições "b" e "r" . Para uma listagem completa de todas as restrições de conjunto sequencial legais para o compilador IBM XL C e C++, consulte a referência de linguagem do compilador.
Registrar restrições nas declarações de variável
Em alguns programas, você desejará vincular variáveis a certos registros de hardware. Isso é feito na declaração de variável. O exemplo a seguir vincula a variável res à GPR0 em toda a vida do programa:
int register res asm("r0")=0;
|
Quando o tipo de variável não é combinado com o tipo de registro de hardware de destino, você receberá um aviso de erro de compilação.
Depois de uma variável ser vinculada a um registro específico, não é possível usar outro registro para reter a mesma variável. Por exemplo, o código a seguir causará um erro de compilação, a variável res é associada no tempo de declaração com GPR0, mas no bloco do conjunto sequencial, o usuário tenta usar qualquer registro, exceto GPR0, para passar para dentro res.
Listagem 3. Erro de compilação quando restrições conflitantes são usadas em uma variável
int register res asm("r0")=0;
asm ( " addc. %0,%1,%2 \n"
: "=b"(res)
: "b"(op1), "r"(op2)
: "r0" );
|
No exemplo na Listagem 4, não há operando saída para a instrução stw , assim, a seção de saídas do conjunto sequencial está vazia. Nenhum dos registros é modificado, então eles são todos operandos de entrada, e o endereço de destino é passado para dentro com os operandos de entrada. Entretanto, algo é modificado: o local da memória endereçada. Mas esse local não é explicitamente mencionado, então a saída da instrução é implícita, em vez de explícita.
Listagem 4. Instruções sem operandos de saída
int res [] = {0,0};
int a=45;
int *pointer = &res[0];
asm ( " stw %0,%1(%2) \n"
:
: "r"(a), "i"(sizeof(int)),"r"(pointer));
|
Listagem 5. Instruções com operandos preservados
int res [] = {0,0};
int a=45;
asm ( " stw %0,%1(%2) \n"
: "+r"(res[0])
: "r"(a), "i"(sizeof(int)),"r"(pointer));
|
Na Listagem 5, se desejar preservar o valor inicial de uma variável de resultado que não é necessariamente modificado pelo bloco do conjunto sequencial, é preciso usar a restrição + (sinal de mais) para preservar o valor inicial dessa variável, como mostrado em res[0].
Endereços de memória de destino no conjunto sequencial
Se uma instrução especificar dois dos seus argumentos em uma forma semelhante a D(RA),
em que D é um valor literal e RA é um registro geral, isso é tomado para significar que D+RA é um endereço efetivo. Neste caso, as restrições adequadas são "m" ou "o". Tanto "m" quanto "o" se referem a argumentos de memória. A restrição "o" é descrita como um local de memória compensável . Mas na arquitetura IBM® POWER® , quase todas as referências de memória requerem uma compensação, então "m" e "o" são equivalentes. Neste caso, é possível usar uma única restrição para referir-se a dois operandos na instrução. A Listagem 6 é um exemplo.
Listagem 6. Uma única restrição para referir-se a dois operandos na instrução
int res [] = {0,0};
int a=45;
asm ( " stb %1,%0 \n"
: "=m"(res[1])
: "r"(a));
|
A forma da instrução stb (da referência de linguagem do conjunto) é: stb RS,D(RA).
Embora a instrução stb tecnicamente tome três operandos (um registro de origem, um registro de endereço e um deslocamento imediato), a descrição do seu conjunto usa apenas duas restrições. A restrição "=m" é usada para notificar o compilador de que o endereço de memória de res deve ser usado para o resultado da instrução de armazenamento (a instrução "sync" é frequentemente usada para esse fim, mas há outras disponíveis, como descrito no IBM Support Assistant do POWER. Consulte os Recursos para um link). O "=m" indica que o operando é um local de memória modificado. Não é preciso conhecer o endereço do local antecipadamente, pois essa tarefa é deixada para o compilador. Isso permite ao compilador escolher o registro certo (r1 para uma variável automática, por exemplo) e aplicar o deslocamento certo automaticamente. Isso é necessário, pois geralmente seria impossível para um programado r de conjunto saber qual registro de endereço e qual deslocamento usar. Em outros casos, também é possível substituir esse comportamento calculando manualmente o endereço de destino como no seguinte exemplo.
Listagem 7. Calculando manualmente o endereço de destino
int res [] = {0,0};
int a=45;
asm ( " stb %0,%1(%2) \n"
:
: "r"(a), "i"(sizeof(int)),"r"(&res));
|
Nesse código, a especificação %1(%2)
representa um endereço de base e uma compensação, em que %2 representa o endereço de base e res[0] e %1 representam a compensação o sizeof(int). Como resultado, o armazenamento é realizado no endereço efetivo, res.
Observação:
Para algumas instruções, GPR0
não pode ser usado como endereço de base. Especificar GPR0 diz ao assembler para não usar nenhum registro de base que seja. Para garantir que o compilador não escolha r0 para um operando, é possível usar a restrição "b" em vez de "r".
Modos de endereçamento para instruções POWER e PowerPC
O tipo de arquitetura IBM POWER é RISC. Instruções normalmente operam com três argumentos de registro (dois registros para argumentos de origem, um registro para deter um resultado) ou com dois registros e um valor imediato (um registro e um valor imediato para os argumentos de origem, e um registro para deter o resultado). Há exceções a esse padrão, mas de um modo geral ele é verdadeiro.
Entre as instruções que tomam dois registros e um valor imediato, há duas subclasses especiais: instruções de carregamento e instruções de armazenamento. Essas instruções usam o valor imediato como uma compensação ao valor no registro de origem para formar um "endereço efetivo". O valor de compensação é tipicamente uma compensação na pilha (r1 é o ponteiro da pilha), ou é uma compensação para o índice ―r2 é o ponteiro do índice). O índice é usado para promover o desenvolvimento de um código independente de posição que permite o carregamento dinâmico eficiente de bibliotecas compartilhadas nessas máquinas.
Ao usar um conjunto sequencial, não é preciso usar registros específicos nem desenvolver manualmente endereços efetivos. As restrições de argumento são usadas para direcionar o compilador para escolher registros ou desenvolver endereços efetivos adequados para os requisitos dessas instruções. Assim, se um registro geral for requerido pela instrução, é possível usar a restrição "r" ou "b"
. A restrição "b" é de interesse porque muitas instruções usam a designação de registro 0 especialmente –- uma designação de registro 0
não significa que r0 é usado, mas, em vez disso, um valor literal de 0. Para essas instruções, é inteligente usar "b" para denotar os operandos de entrada para evitar que o compilador escolha r0. Se o compilador escolher r0, e a instrução tomar isso com um sentido literal de 0, a instrução produzirá resultados incorretos.
Listagem 8. r0 e seu significado especial na instrução stbx
char res[8]={'a','b','c','d','e','f','g','h'};
char a='y';
int index=7;
asm (" stbx %0,%1,%2 \n"
:
: "r"(a), "r"(index), "r"(res) );
|
Aqui, a cadeia de caractere de resultado esperada é
abcdefgy, mas se o compilador escolher r0 para %1, então o resultado seria, incorretamente, ybcdefgh. Para evitar que isso aconteça, use "b"
como a Listagem 9 mostra.
Listagem 9. Usando a restrição "b" para significar GPR não zero
char res[8]={'a','b','c','d','e','f','g','h'};
char a='y';
int index=7;
asm (" stbx %0,%1,%2 \n"
:
: "r"(a), "b"(index), "r"(res) );
|
Outro exemplo está no seguinte bloco de ASM. Embora pareça que o bloco de conjunto abaixo faça res=res+4, esse é o comportamento funcional real do código.
Listagem 10. Significado de r0 no segundo operando com o código de operação addi
int register res asm("r0")=5;
int b=4;
asm ( " addi %0,%0,%1 \n"
: "+r"(res)
: "i"(b)
: "r0");
where: addi %0(result operand),%0(input operand res),%3(immediate operand b)
|
Já que res está vinculado a
r0, a tradução do código do conjunto em visual do conjunto torna-se:
addi 0,0,4
O segundo operando não converte para registro zero. Em vez disso, ele converte para o número zero imediato. Na verdade, o seguinte é o resultado da operação addi:
res=0+4
Esse caso é especial para
addi opcode. Se, em vez disso,
res estivesse vinculado a
r1, então o comportamento original pretendido teria sido obtido:res=res+4
Em casos em que os registros não diretamente vinculados às entradas/saídas são usados dentro do bloco de conjunto, o usuário deve especificar esses registros dentro da lista de sobrescrever.
O clobbers list é usado para notificar o compilador de que os registros contidos dentro da lista podem, potencialmente, ter seus valores alterados. Assim, não devem ser usados para deter outros dados que não para as instruções para as quais são usados.
No exemplo na Listagem 11, os registros 8 e 7 são adicionados à lista de sobrescrever porque são usados nas instruções, mas não estão explicitamente vinculados a nenhum operando de entrada/saída. Ainda, o campo zero do registro de condição é adicionado à lista de sobrescrever pelo mesmo motivo. Embora não esteja presente nos operandos de entrada/saída, a instrução mfocrf lê esse bit do registro de condição e move o valor no registro 8.
Listagem 11. Exemplo de lista de sobrescrever
asm (" addc. %0,%2,%3 \n"
" mfocrf 8,0x1 \n"
" andi. 7,8,0xF \n"
" stw 7,%1 \n"
: "=r"(res),"=m"(c_bit)
: : "b"(a), "r"(b)
: "r0","r7","r8","cr0" ); clobbers list
|
Se, em vez disso, a instrução mfocrf ler do campo 1 do registro de condição 1 (cr1), então esse campo precisaria ser adicionado à lista de sobrescrever, em vez disso. Ainda, o ponto no final das instruções addc. e andi. significa que seus resultados são comparados com zero, e o resultado da comparação é armazenado no campo 0 do registro de condição.
Quando os registros sobrescritos são omitidos da lista de sobrescrever, os resultados das operações de conjunto podem não estar corretos. Isso acontece porque esses registros de sobrescrever podem ser reutilizados para reter valores intermediários para outras operações. A menos que o compilador detecte que esses registros estão sobrescritos, os dados intermediários podem ser usados para realizar as instruções do programador, com resultados imprecisos. Ainda, as instruções de conjunto do usuário podem sobrescrever valores usados pelo compilador.
Exceções à lista de sobrescrever
Quase todos os registros podem ser sobrescritos, exceto por aqueles listados na Tabela 1.
Tabela 1. Registros que não podem ser sobrescritos
| Registro | Descrição |
|---|---|
| r1 | ponteiro de pilha |
| r2 | ponteiro de índice |
| r11 | ponteiro de ambiente |
| r13 | ponteiro de dados local de encadeamento de modo de 64 bits |
| r30 | frequentemente usado pelo compilador como um ponteiro de estrutura da pilha, ponteiro para área constante |
| r31 | frequentemente usado pelo compilador como um ponteiro de estrutura da pilha, ponteiro para área constante |
Sobrescrever memória implica uma, e também tem impacto sobre como o compilador trata alias de dados em potencial. Sobrescrever memória diz que o bloco do conjunto modifica a memória que de outra forma não é mencionada nas instruções do conjunto. Assim, por exemplo, um uso correto da memória seria ao usar uma instrução que limpa uma linha de cache. O compilador presumirá que praticamente quaisquer dados podem ser passar por alias com a memória alterada para aquela instrução. Como resultado, todos os dados requeridos usados após o bloco do conjunto serão recarregados a partir da memória após o conjunto ser concluído. Isso é muito mais dispendioso que a fence simples implícita pelo atributo "volátil" (discutido mais adiante).
Lembre-se de que, por que sobrescrever memória diz que qualquer coisa pode passar por alias, tudo que é usado precisa ser recarregado após o conjunto, independentemente de ter qualquer relação com o conjunto. Sobrescrever memória pode ser adicionado à lista de sobrescrever simplesmente usando a palavra "memória", em vez de um nome de registro.
A ramificação pode ser difícil com conjunto sequencial, porque é preciso conhecer o endereço da instrução para a qual ramificar antes do tempo de compilação. Embora isso não seja possível, é possível usar rótulos. Usando rótulos, o endereço de ramificação pode ser projetado com um identificador único que pode ser usado como endereço de ramificação de destino.
Dentro de um único arquivo de origem, os rótulos não podem ser repetidos dentro de um bloco de conjunto sequencial, nem dentro de blocos de conjunto vizinhos dentro da mesma origem. Em um dado programa, cada rótulo é exclusivo. Há uma exceção a essa regra, porém, e é que se você usar ramificação relativa (mais sobre isso adiante). Com ramificação relativa, mais de um rótulo com o mesmo identificador podem ser encontrados dentro do mesmo programa e dentro do mesmo bloco de conjunto.
Observação:
Os rótulos não podem ser usados no conjunto para definir macros em função de possíveis conflitos de namespace.
No exemplo na Listagem 12, a ramificação ocorre quando o bit LT, o bit 0 do registro de condição é definido. Se não estiver definido, então a ramificação não é tomada.
Listagem 12. Exemplo de uma ramificação tomada quando o bit LT de CR0 é definido (0x80000000)
asm ( " addic. %0,%2,%4 \n"
" bc 0xC,0,here \n"
" there: add %1,%2,%3 \n"
" here: mul %0,%2,%3 \n"
: "=r"(res),"=r"(res2)
: "r" (a),"r"(b),"r"(c)
: "cr0" );
|
Da mesma forma, uma ramificação poderia ocorrer se o bit GT (bit 1) do registro de condição estivesse definido, como o código na Listagem 13.
Listagem 13. Exemplo de ramificação tomada quando o bit GT de CR0 é definido (0x40000000)
asm ( " addic. %0,%2,%4 \n"
" bc 0xC,1,here \n"
" there: add %1,%2,%3 \n"
" here: mul %0,%2,%3 \n"
: "=r"(res),"=r"(res2)
: "r" (a),"r"(b),"r"(c)
: "cr0" );
|
Com o conjunto sequencial, é perfeitamente legal ramificar dentro do mesmo bloco de conjunto; entretanto, não é possível ramificar entre diferentes blocos de conjunto, mesmo se estiverem contidos na mesma origem.
Como discutido anteriormente, a ramificação relativa permite reutilizar o nome de um rótulo mais de uma vez dentro do mesmo programa. É usado predominantemente, porém, para determinar a posição do endereço de destino com relação à instrução de ramificação. Esses são exemplos dos códigos de ramificação relativa que podem ser usados:
- F -para frente
- B -para trás
Observação:
Eles devem ter rótulos numéricos como sufixo para estarem sintaticamente corretos.
Neste exemplo (Listagem 14), observe que o endereço de destino é referido como "Hereb". Neste caso, usamos o rótulo do endereço de destino anexado com um sufixo que determina onde esse rótulo é colocado em relação à instrução de ramificação em si. O rótulo "Here" está localizado antes da instrução de ramificação, assim, o uso do sufixo "b" em "Hereb."
Listagem 14. Precisa de legenda
asm ( " 10: lwarx %0,0,%2 \n"
" cmpwi %0,0 \n"
" bne- 20f \n"
" ori %0,%0,1 \n"
" stwcx. %0,0,%2 \n"
" bne- 10b \n"
" sync \n"
" ori %1,%1,1 \n"
" 20: \n" :)
|
O registro de condição é usado para capturar informações sobre os resultados de certas instruções.
Para instruções de ponto não flutuantes com o sufixo ponto final (.) que define CR, o resultado da operação é comparado a zero.
- Se o resultado for maior que zero, então o bit 1 do campo CR é definido (0x4).
- Se for menor que zero, então o bit 0 é definido para (0x8).
- Se o resultado for igual a zero, então o bit 2 é definido (0x2).
Para todas as instruções de comparação, os dois valores são comparados e qualquer campo CR pode ser definido (não apenas CR0). A Tabela 2 lista os bits e seus significados correspondentes (há oito desses conjuntos de 4 bits no registro de condição, chamados "cr0, cr1, cr2 … cr7").
Tabela 2. Bits de um campo CR e os significados de diferentes configurações
| Bit | Nome | Descrição |
|---|---|---|
| 0 | LT | RA < 0 |
| 1 | GT | RA > 0 |
| 2 | EQ | RA = 0 |
| 3 | U | Estouro para operações de número inteiro. Não ordenado, para operações de ponto flutuantes |
Observação:
Para instruções de ponto flutuantes com um ponto de sufixo, CR1 é definido para os 4 bits superiores de FPSCR.
Tornar um bloco de conjunto sequencial "volátil" como neste exemplo garante que, conforme otimiza, o compilador não move nenhuma instrução acima ou abaixo do bloco de instruções de conjunto.
asm volatile(" addic. %0,%1,%2\n" : "=r"(res): "=r"(a),"r"(a))
|
Isso pode ser particularmente importante em casos em que o código está acessando a memória compartilhada. Isso será ilustrado na próxima seção sobre bloqueio multiencadeado.\
Um dos usos mais comuns de conjunto sequencial é para escrever segmentos breves de instruções para gerenciar bloqueios multiencadeados. Em função do modelo de memória solta na arquitetura POWER, desenvolver esses bloqueios requer uso cuidadoso de algumas instruções:
- Uma instrução que carrega a palavra de bloqueio e cria uma "reserva"
- Outra que atualiza a palavra de bloqueio se a reserva não tiver sido perdida no meio tempo
Observação:
Se a reserva tiver sido perdida, um loop pode ser usado para tentar novamente repetidamente.
A Listagem 15 mostra uma função sequencial básica que tenta obter um bloqueio (hpa vários problemas com esse código, que discutiremos após estes exemplos).
Listagem 15. Exemplo da função de bloqueio Acquire no conjunto
inline bool acquireLock(int *lock){
bool returnvalue = false;
int lockval;
asm (
"0: lwarx %0,0,%2 \n" //load lock and reserve
" cmpwi 0,%0,0 \n" //compare the lock value to 0
" bne 1f \n" //not 0 then exit function
" ori %0,%0,1 \n" //set the lock to 1
" stwcx. %0,0,%2 \n" //try to acquire the lock
" bne 0b \n" //reservation lost, try again
" ori %1,%1,1 \n" //set the return value to true
"1: \n" //didn't get lock, return false
: "+r" (lockval), "+r" (returnvalue)
: "r"(lock) //parameter lock is an address
: "cr0" ); //cmpwi, stwcx both clobber cr0
return returnvalue;
}
|
A Listagem 16 é um exemplo de como essa função sequencial poderia ser usada.
Listagem 16. Exemplo de como a função acquireLock pode ser usada
if (acquireLock(lockWord)){
//begin to use the shared region
temp = x + 1;
. . .
}
|
Porque a função é sequencial, o código resultante não terá uma chamada real nele. Em vez disso, ele o uso da região compartilhada x será antecedido pelas instruções para obter o bloqueio.
O primeiro problema a observar com esse código é a falta de uma instrução de sincronização. Um dos principais aprimoramentos de desempenho possibilitado pelo modelo de memória solta da arquitetura POWER a habilidade da máquina de reordenar cargas e armazenamentos para fazer um uso mais eficiente dos canais internos. Porém, às vezes o programador precisa abreviar esse reordenamento em algum grau para acessar adequadamente o armazenamento compartilhado. No caso de um bloqueio, você não desejaria que um carregamento de dados da região compartilhada ("x" no caso acima) fosse reordenado de modo que ocorresse antes de o bloqueio na região ser obtido. Por esse motivo, uma instrução de sincronização deve ser inserida para indicar à máquina que limite o reordenamento neste caso. A instrução sync muitas vezes é usada para esse fim, mas há outras opções disponíveis, como descrito no IBM Support Assistant do POWER (consulte os Recursos). No exemplo de código na Listagem 17, inserimos a instrução sync para evitar o reordenamento de carregamentos de "x" (isso é chamado de "barreira de importação"):
Listagem 17. Exemplo de sincronização
inline bool acquireLock(int *lock){
bool returnvalue = false;
int lockval;
asm (
"0: lwarx %0,0,%2 \n" //load lock and reserve
" cmpwi 0,%0,0 \n" //compare the lock value to 0
" bne 1f \n" //not 0 then exit function
" ori %0,%0,1 \n" //set the lock to 1
" stwcx. %0,0,%2 \n" //try to acquire the lock
" bne 0b \n" //reservation lost, try again
" sync \n" //import barrier
" ori %1,%1,1 \n" //set the return value to true
"1: \n" //didn't get lock, return false
: "+r" (lockval), "+r" (returnvalue)
: "r"(lock) //parameter lock is an address
: "cr0" ); //cmpwi, stwcx both clobber cr0
return returnvalue;
}
|
Nesse bloco de conjunto, a sincronização evitará que quaisquer cargas subsequentes ocorram até que seja mostrado que caminho a ramificação anterior tomou. Assim, a variável x não será carregada, a menos que a ramificação não tenha sido tomada e acquireLock retorne true.
Então estamos prontos agora? Infelizmente não. Ainda precisamos nos preocupar com o que o compilador pode fazer.
Compiladores de otimização modernos podem ser muito agressivos ao movimentar o código ― e inclusive removê-lo completamente ― se parecer que as alterações podem fazer o programa operar mais rápido sem alterar a semântica do código. Entretanto, os compiladores normalmente não estão cientes das complexidades envolvidas no acesso à memória compartilhada. Por exemplo, um compilador pode não mover a instrução temp = x + 1; para um local superior no programa se determinar que o resultado seria planejado com mais eficiência (e presume que o "se" é sempre tomado). É claro, isso seria desastroso do ponto de vista de acessar os dados compartilhados. Para evitar o movimento de qualquer carregamento (ou qualquer instrução que seja) de abaixo do conjunto sequencial para um local acima dele, é possível usar a palavra-chave "volátil" (também conhecida como atributo volátil) para modificar o bloco do conjunto, como mostra a Listagem 18.
Listagem 18. Exemplo de palavra-chave volátil
inline bool acquireLock(int *lock){
bool returnvalue = false;
int lockval;
asm volatile (
"0: lwarx %0,0,%2 \n" //load lock and reserve
. . .
"1: \n" //didn't get lock, return false
: "+r" (lockval), "+r" (returnvalue)
: "r"(lock) //parameter lock is an address
: "cr0" ); //cmpwi, stwcx both clobber cr0
return returnvalue;
}
|
Quando isso é feito, uma fence interna é colocada antes e depois do bloco de conjunto, que impede que as instruções sejam movidas além dela. E lembre-se de que esse bloco do conjunto é sequencial, então evitará que o acesso a x seja movido acima do bloqueio implementado do conjunto.
Sobrescrever memória em bloqueio multiencadeado
A discussão de bloqueio multiencadeado não estaria completa sem mencionar sobrescrever memória. A palavra-chave memory frequentemente é adicionada à lista de sobrescrever nessas situações, embora nem sempre esteja claro por que ela seria necessária. O uso de memória na lista de sobrescrever signifique a memória é alterada de maneira imprevisível pelo bloco do conjunto.
Entretanto, modificações de memória no exemplo de bloqueio fornecido são bastante previsíveis. Apesar de o bloqueio de variável ser um ponteiro (que aponta para um local de bloqueio, isso não é mais imprevisível que a expressão "*lock" em um programa C. Nesses casos, um compilador comportado provavelmente associaria a expressão "*lock" a todas as variáveis do tipo adequado, então recarregaria corretamente quaisquer variáveis afetadas após o ponteiro ter sido usado para modificar os dados. Contudo, o uso de sobrescrever memória parece ser uma prática difundida, provavelmente conduzida por um excesso de cuidado ao lidar com regiões compartilhadas. Os programadores devem estar cientes, no entanto, das penalidades de desempenho envolvidas e das abordagens alternativas.
Quando um conjunto sequencial inclui "memória" na lista de sobrescrever, isso significa que qualquer variável no programa pode ter sido modificada pelo conjunto, de modo que ela deve ser recarregada antes de ser usada. Esse requisito pode minar os esforços de otimização por parte do compilador. Uma abordagem potencialmente mais leve seria tornar a região compartilhada volátil (além do bloco de conjunto em si). Tornar uma variável volátil significa que seu valor deve ser recarregado antes que ela seja usada em uma dada expressão. Se a região compartilhada em questão for uma estrutura de dados, como uma lista ou fila, isso garantirá que a estrutura atualizada seja recarregada após o bloqueio ser adquirido. Entretanto, todos os acessos de dados não compartilhados podem desfrutar do complemento total das otimizações do compilador.
Dica:
Se a estrutura de dados compartilhados for acessada por um ponteiro (digamos, *p), certifique-se de declarar o ponteiro de modo a indicar que é o objeto apontado para ele que é volátil, e não o ponteiro em si. Por exemplo, isso declara que a lista apontada para p é volátil:
volatile list *p
|
Obrigado, Ian McIntosh, Christopher Lapkowski, Jim McInnes e Jae Broadhurst. Cada um de vocês desempenhou um importante papel na publicação deste artigo.
Aprender
- Para alternativas à instrução de sincronização, consulte PDF IBM Power ISA (Arquitetura do Conjunto de Instruções).
- Visite a área do software Rational no developerWorks para obter recursos técnicos e boas práticas para os produtos do Rational Software Delivery Platform.
- Fique por dentro dos eventos técnicos e Webcasts do developerWorks com ênfase em uma série de produtos da IBM e tópicos do segmento de mercado de TI.
- Participe de um briefing ao vivo e gratuito do developerWorks para se atualizar rapidamente sobre produtos e ferramentas IBM, bem como tendências do segmento de mercado de TI.
- Acompanhe as demos on demand do developerWorks, variando de demos de instalação e configuração de produtos para iniciantes a funcionalidades avançadas para desenvolvedores experientes.
- Melhore suas qualificações. Consulte o arquivo Treinamento e certificação do Rational , que inclui muitos tipos de cursos em uma ampla variedade de tópicos.
É possível realizar alguns deles em qualquer local, a qualquer momento, e muitos dos cursos para iniciantes são gratuitos.
Obter produtos e tecnologias
- Faça download de uma versão gratuita do software Rational.
- Avalie outros produtos de software da IBM da forma que melhor lhe convier: faça o download da versão de avaliação, experimente-o on-line, use-o em um ambiente de nuvem ou passe algumas horas no SOA Sandbox aprendendo a implementar a Arquitetura Orientada a Serviços de forma eficiente.
Discutir
- Obtenha respostas e participe da comunidade C/C++ na Call Attachment Facility do Rationalé.
- Participe da fóruns do software Rational para fazer perguntas e participar de discussões.
- Classifique ou revise o software Rational. É rápido e fácil. Realmente.
- Compartilhe seu conhecimento e ajude outros a usarem o software Rational escrevendo um artigo para o developerWorks. Você terá um público mundial, organização RSS, reconhecimento de autoria e uma biografia, e o benefício da edição e produção profissional no website do Rational no developerWorks. Descubra quais são as características de um bom artigo do developerWorks e como realizá-lo.
- Siga o software Rational no Facebook, Twitter
(@ibmrational), e
YouTubee adicione seus
comentários e solicitações.
- Faça e responda a perguntas e aumente seus conhecimentos participando dos Fóruns do Rational, cafésé e wikis.
- Entre em contato com outras pessoas que compartilham seus interesses fazendo parte da comunidade do developerWorks e respondendo aos blogs de desenvolvedores.