Quem der uma vista de olhos por código meu, em especial em situações onde é necessária alta velocidade de execução – como no caso dos Jogos de Loto – encontrará, várias vezes, duas instruções que dão imenso jeito, unsafe e fixed.

A instrução unsafe é a mais simples e serve para marcar uma zona de código como não segura. Isto serve imensos propósitos, em particular o nosso amigo fixed.

Mas antes de irmos à explicação do fixed, é preciso explicar porque é que precisamos dele quando a velocidade de execução é fundamental.

As linguagens .NET são linguagens de compilação JIT (Just In Time), o que quer dizer que o código em que são feitas (C#, VB.NET, F#, etc.) são assembladas para um formato intermédio (CIL – Common Intermediate Language), que forma os executáveis e librarias normais (exe e dll). Ao serem executadas, o compilador JIT – inserido na máquina virtual, que é a framework .NET, de instalação obrigatória – transforma esse formato intermédio na linguagem máquina mais apropriada para o ambiente (sistema operativo, especificações de hardware) em que está a correr.

A máquina virtual .NET (e, na realidade, todas as máquinas virtuais, desde o Java até ao Flash Player) têm também um mecanismo chamado garbage colection (GC) (recolha de lixo, na tradução literal). O GC foi uma excelente ideia, quando apareceu pela primeira vez em 1959 (John McCarthy): retira das mãos do programador a responsabilidade de eliminar objectos, variáveis e ponteiros que já não estão em uso. Quem vem dum background em C ou C++ conhece bem o sintoma de se usar um ponteiro para uma área de memória vazia ou com dados inválidos (normalmente, segmentation fault e respectivo core dump; na pior das hipóteses, crash do sistema). Hoje em dia, já começa a ser difícil encontrar linguagens sem GC (com a honrosa excepção do C++ não-.NET) o que é, em quase todas as circunstâncias, óptimo!

No entanto, o mecanismo do GC implica que não haja ponteiros estáticos paras as posições de memória onde estão os objectos, porque o próprio GC pode mudar as posições dos mesmos, levando a que os ponteiros apontassem para posições inválidas – tal como acabei de explicar, a ideia do GC é precisamente evitar que isto aconteça. Só que, como subproduto desta coisa boa, temos uma coisa má: qualquer acesso a um objecto tem um atraso associado à consulta da posição de memória do mesmo. Quando queremos aceder e tratar um conjunto de dados de tamanho considerável, esses pequenos atrasos acumulam-se.

A maneira de contornar isto é forçando a linguagem a declarar um ponteiro estático, que avisa o GC para não mudar a posição de memória do objecto em questão. No caso do C#, essa instrução é o fixed. Como essa operação é potencialmente perigosa, todas as instruções fixed têm de estar rodeadas pela instrução unsafe.

Por exemplo, aceder a um array de números pode ser feito da seguinte maneira:

int[] meuArray = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int tamanhoMeuArray = meuArray.Length;
int somatorio = 0;

for(int i = 0; i < tamanhoMeuArray; i++)
    somatorio += meuArray[i];

Com o recurso a fixed e unsafe fica assim:

int[] meuArray = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int tamanhoMeuArray = meuArray.Length;
int somatorio = 0;

unsafe
{
    fixed(int* pMeuArray = meuArray)
    {
        for(int i = 0; i < tamanhoMeuArray; i++)
            somatorio += pMeuArray[i];
    }
}

Mais código? Sim. Diferenças de performance? No mínimo, 4 vezes mais rápido, dependendo dos casos… Imaginem o que é passar aquele processo violento que demora 1 hora para apenas 15 minutos, na pior das hipóteses.

A instrução fixed até deixa passar vários parâmetros duma só vez, desde que sejam todos do mesmo tipo. Por exemplo, uma hipotética solução para multiplicação de matrizes (não usem isto! Existem algoritmos muito melhores para a multiplicação de matrizes – google it: Strassen's fast multiplication of matrices)

int[,] matriz1 = new int[4, 3] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10, 11, 12 } };
int[,] matriz2 = new int[3, 4] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };

int alturaMatriz1 = matriz1.GetLength(0);
int larguraMatriz1 = matriz1.GetLength(1);

int alturaMatriz2 = matriz2.GetLength(0);
int larguraMatriz2 = matriz2.GetLength(1);

int[,] matrizMult = new int[alturaMatriz1, larguraMatriz2];

/* 
* Este controlo é absolutamente necessário!
* Não só porque só assim uma multiplicação de matrizes faz sentido,
* mas também porque seria altamente perigoso aceder a posições de
* memória que não fizessem parte dos arrays!
*/
if(larguraMatriz1 != alturaMatriz2)
    return;

unsafe
{
    fixed (int* pMatriz1 = matriz1, pMatriz2 = matriz2, pMatrizMult = matrizMult)
    {
        for (int i = 0; i < alturaMatriz1; i++)
        {
            for (int j = 0; j < larguraMatriz2; j++)
            {
                /* 
                 * reparem como o acesso ao ponteiro não é feito
                 * pelo endereço do array, mas sim sequencialmente,
                 * à posição de memória correspondente:
                 *    alturaActual * larguraDoArray + larguraActual
                 * 
                 * ex:    0 * 3 + 0 -> posição [0,0], endereço 0
                 *        1 * 3 + 2 -> posição [1,2], endereço 5
                 */
                pMatrizMult[i * larguraMatriz2 + j] = 0;

                /* 
                 * aqui seria igual usar larguraMatriz1 ou
                 * alturaMatriz2, devido à confirmação exterior
                 */
                for (int k = 0; k < larguraMatriz1; k++)
                    pMatrizMult[i * larguraMatriz2 + j] += pMatriz1[i * larguraMatriz1 + k] * pMatriz2[k * larguraMatriz2 + j];
            }
        }
    }
}

Agora que já sabem, go nuts, mas tenham muito, muito cuidado com acesso a posições inválidas!

Adenda:

Se tentarem compilar código com zonas unsafe, o mais provável é o compilador dar erro (Unsafe code may only appear if compiling with /unsafe). Tal como diz o erro, é só executar o compilador com o parâmetro /unsafe. Quem usa o Visual Studio, tem de activar a definição Allow unsafe code, nas propriedades do projecto, separador Build. Isto serve como mais um controlo para utilizadores distraídos, e como alerta de que a operação é potencialmente perigosa.

Partilhar no Sapo Links Partilhar no del.icio.us Partilhar no Digg Partilhar no Twitter Partilhar no StumbleUpon Partilhar no MySpace Partilhar no Facebook

Comentários Deixar um comentário

 Categorias
 Arquivo
 Projectos em Destaque
 Últimas Postas no Blog
 Últimos Comentários do Blog