Pergunta

I têm um laço escrita em C ++ que é executado para cada elemento de uma matriz inteiro grande. Dentro do loop, eu mascarar alguns bits do número inteiro e, em seguida, encontrar os valores mínimo e máximo. Ouvi dizer que se eu usar instruções SSE para estas operações vai correr muito mais rápido em comparação com um ciclo normal de escrita usando bit a bit AND, e if-else condições. A minha pergunta é que eu deveria ir para estes instruções SSE? Além disso, o que acontece se o meu código é executado em um processador diferente? Será que ainda o trabalho ou estas instruções são específicas processador?

Foi útil?

Solução

  1. instruções SSE são específicos do processador. Você pode procurar qual processador suportes que versão SSE na wikipedia.
  2. Se o código SSE será mais rápido ou não, depende de muitos fatores: O primeiro é, naturalmente, se o problema está ligado à memória ou no limite da CPU. Se o barramento de memória é o gargalo SSE não vai ajudar muito. Tente simplificar seus cálculos inteiros, se isso torna o código mais rápido, provavelmente é vinculado à CPU, e você tem uma boa chance de acelerar.
  3. Esteja ciente de que escrever SIMD-code é muito mais difícil do que escrever C ++ - código, e que o código resultante é muito mais difícil de mudança. Sempre mantenha o código C ++ em dia, você vai querer isso como um comentário e verificar a exatidão de seu código assembler.
  4. Pense em usar uma biblioteca como o IPP, que as operações de instrumentos comuns de baixo nível SIMD otimizado para vários processadores.

Outras dicas

SIMD, dos quais SSE é um exemplo, permite que você faça a mesma operação em vários pedaços de dados. Assim, você não vai obter qualquer vantagem de usar SSE como um substituto direto para as operações com números inteiros, você só vai conseguir vantagens, se você pode fazer as operações em vários itens de dados ao mesmo tempo. Isto envolve a carga alguns valores de dados que são contíguas na memória, fazer o processamento necessário e, em seguida, entrar para o próximo conjunto de valores na matriz.

Problemas:

1 Se o caminho de código é dependente dos dados que estão sendo processados, SIMD torna-se muito mais difícil de implementar. Por exemplo:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

Não é fácil fazer como SIMD:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2 Se os dados não é contigous então carregar os dados para as instruções SIMD é complicado

3 O código é específico do processador. SSE é apenas em IA32 (Intel / AMD) e nem todos os IA32 cpus suportar SSE.

Você precisa analisar o algoritmo e os dados para ver se ele pode ser SSE'd e que requer saber como SSE trabalha. Há uma abundância de documentação no site da Intel.

Este tipo de problema é um exemplo perfeito de onde um profiler bom nível baixo é essencial. (Algo como VTune) Pode dar-lhe uma idéia muito mais informado de onde seus hotspots mentir.

Meu palpite, desde que você descreve é ??que seu hotspot provavelmente será falhas de previsão de balcões para min / max cálculos usando if / else. Portanto, usando intrínsecos SIMD deve permitir que você utilize as instruções / max mínimo, no entanto, pode valer a pena apenas tentando usar um branchless min cálculo Apresentamos / max no lugar. Isso pode alcançar a maioria dos ganhos com menos dor.

Algo parecido com isto:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}

Se você usar instruções SSE, você está obviamente limitado a processadores que suportam estes. Isso significa x86, que remonta ao Pentium 2 ou assim (não lembro exatamente quando eles foram introduzidos, mas é um longo tempo atrás)

SSE2, que, tanto quanto me lembro, é a única que oferece inteiros operações, é um pouco mais recente (Pentium 3? Embora os primeiros processadores AMD Athlon não apoiá-los)

Em qualquer caso, você tem duas opções para usar estas instruções. Quer escrever todo o bloco de código em assembly (provavelmente uma má idéia. Isso faz com que seja praticamente impossível para o compilador para otimizar seu código, e é muito difícil para um ser humano para escrever assembler eficiente).

Como alternativa, use os intrínsecos disponíveis com seu compilador (se a memória serve, eles são geralmente definidas em xmmintrin.h)

Mas, novamente, o desempenho não pode melhorar. código de SSE coloca exigências adicionais dos dados que processa. Principalmente, o único a ter em mente é que os dados devem ser alinhados em limites de 128 bits. Também deve haver pouca ou nenhuma dependência entre os valores carregados no mesmo registo (uma de 128 bits SSE registo pode conter 4 ints. Adicionando o primeiro e o segundo um conjunto não é óptima. Mas a adição de todos os quatro ints para os correspondentes 4 ints em outro registo será rápido)

Pode ser tentador usar uma biblioteca que envolve toda a baixo nível SSE mexer, mas que também pode arruinar qualquer benefício potencial de desempenho.

Eu não sei como suporte à operação inteiro boa do SSE é, de modo que também pode ser um fator que pode limitar o desempenho. SSE destina-se essencialmente a acelerar as operações de ponto flutuante.

Se você pretende usar o Microsoft Visual C ++, você deve ler este:

http://www.codeproject.com/KB/recipes/sseintro.aspx

Temos implementado um código de processamento de imagem, semelhante ao que você descreve, mas em um array de bytes, em SSE. O aumento de velocidade em relação ao código C é considerável, dependendo do algoritmo exato mais de um fator de 4, mesmo em relação ao compilador Intel. No entanto, como já mencionado você tem os seguintes inconvenientes:

  • Portabilidade. O código será executado em todos os Intel-like CPU, assim também AMD, mas não em outros CPUs. Isso não é um problema para nós, porque controlar o hardware alvo. Alternando compiladores e até mesmo para um 64 bits do sistema operacional também pode ser um problema.

  • Você tem uma curva de aprendizagem, mas eu achei que depois que você compreender os princípios de escrever novos algoritmos não é tão difícil.

  • manutenção. A maioria programadores C ou C ++ não tem nenhum conhecimento de montagem / SSE.

Meu conselho a você será para ir para ele apenas se você realmente precisa da melhoria de desempenho, e você não pode encontrar uma função para o seu problema em uma biblioteca como o Intel IPP, e se você pode viver com os problemas de portabilidade .

Eu posso dizer do meu experince que SSE traz uma enorme (4x e para cima) aceleração sobre uma versão c planície do código (sem asm em linha, há intrínsecos utilizado), mas montador otimizado mão pode bater Compiler gerado montagem se o compilador não pode descobrir o que o programador destina (acredite em mim, compiladores não cobrem todas as combinações possíveis e nunca será). Oh e, o compilador não pode toda disposição os dados que ele é executado na velocidade mais rápida-possível. Mas você precisa de muito experimentar para uma aceleração ao longo de um Intel-compiler (se possível).

instruções SSE foram originalmente apenas em chips Intel, mas recentemente (desde Athlon?) AMD apoia-los, bem, então se você fizer código contra o conjunto de instruções SSE, você deve ser portável para a maioria dos procs x86.

Dito isto, pode não valer a pena seu tempo para aprender SSE codificação a menos que você já está familiarizado com assembler em x86 do - uma opção mais fácil pode ser para verificar seus documentos de compilador e ver se há opções para permitir que o compilador gerar automaticamente código de SSE para você. Alguns compiladores fazem muito bem vectorizing laços desta forma. (Você provavelmente não está surpreso ao ouvir que os compiladores da Intel fazer um bom trabalho deste:)

Escrever código que ajuda o compilador entender o que você está fazendo. GCC vai entender e otimizar o código SSE como esta:

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

Só não se esqueça de ter -msse -msse2 em seus parâmetros de construção!

Embora seja verdade que SSE é específica para alguns processadores (SSE pode ser relativamente segura, SSE2 muito menos na minha experiência), você pode detectar a CPU em tempo de execução, e carregar o código de forma dinâmica, dependendo da CPU-alvo.

intrínsecos SIMD (como SSE2) pode acelerar esse tipo de coisa para cima, mas ter experiência para usar corretamente. Eles são muito sensíveis ao alinhamento e a latência do pipeline; uso descuidado pode fazer desempenho ainda pior do que teria sido sem elas. Você obterá uma aceleração muito mais fácil e imediata de simplesmente usando cache de pré-busca para garantir que todos os seus ints estão em L1 na hora de você para operar sobre eles.

A menos que sua função precisa de uma taxa de transferência de melhor do que 100.000.000 inteiros por segundo, SIMD provavelmente não vale a pena para você.

Apenas para adicionar rapidamente para o que tem sido dito antes sobre as diferentes versões SSE estar disponível em diferentes CPUs: Isto pode ser verificado, olhando para os respectivos sinalizadores de recurso retornados pela instrução CPUID (ver, por exemplo documentação da Intel para detalhes).

Tenha um olhar em em linha assembler para C / C ++, aqui é um DDJ artigo . A menos que você está 100% certo que seu programa será executado em uma plataforma compatível você deve seguir as recomendações muitos deram aqui.

Eu concordo com os cartazes anteriores. Os benefícios podem ser bastante grande, mas para obtê-lo pode exigir muito trabalho. documentação Intel nestas instruções é sobre páginas de 4K. Você pode querer verificar para fora EasySSE (c ++ wrappers biblioteca sobre intrínsecos + exemplos) livre de Ocali Inc.

Eu assumo minha afiliação com este EasySSE é clara.

Eu não recomendo fazer isso sozinho, a menos que você está bastante acostumado com a montagem. Usando SSE irá, mais do que provável, exigem reorganização cuidado de seus dados, como pontos Skizz para fora, eo benefício é muitas vezes questionável.

Ele provavelmente seria muito melhor para você escrever muito pequenos laços e manter seus dados muito bem organizado e apenas contar com o compilador fazendo isso por você. Tanto a Intel C Compiler e GCC (desde 4.1) pode auto-vetorizar seu código, e provavelmente vai fazer um trabalho melhor do que você. (Basta adicionar -ftree-vectorize a seus CXXFLAGS.)

Editar : Outra coisa que eu devo mencionar é que o apoio de vários compiladores montagem intrínsecos , que provavelmente, IMO, ser mais fácil de usar do que o asm () ou __asm ??{ } sintaxe.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top