Custo de manutenção da base de código de programação SIMD

14

Questão:

O consenso da indústria de software é que o código limpo e simples é fundamental para a viabilidade a longo prazo da base de código e da organização que a possui. Essas propriedades levam a custos de manutenção mais baixos e maior probabilidade de continuidade da base de códigos.

No entanto, o código SIMD é diferente do código geral do aplicativo, e eu gostaria de saber se existe um consenso semelhante em relação ao código limpo e simples que se aplica especificamente ao código SIMD.


Antecedentes da minha pergunta.

Escrevo bastante código SIMD (instrução única, vários dados) para várias tarefas de processamento e análise de imagem. Recentemente, também tive que portar um pequeno número dessas funções de uma arquitetura (SSE2) para outra (ARM NEON).

O código foi escrito para software compactado, portanto, não pode depender de idiomas proprietários sem direitos de redistribuição irrestritos, como o MATLAB.

Um exemplo de estrutura de código típica:

  • Usando o tipo de matriz do OpenCV ( Mat) para todo o gerenciamento de memória, buffer e tempo de vida.
  • Após verificar o tamanho (dimensões) dos argumentos de entrada, os ponteiros para o endereço inicial de cada linha de pixels são obtidos.
  • A contagem de pixels e os endereços iniciais de cada linha de pixels de cada matriz de entrada são passados ​​para algumas funções C ++ de baixo nível.
  • Essas funções C ++ de baixo nível usam intrínsecas SIMD (para Intel Architecture e ARM NEON ), carregando e salvando em endereços de ponteiro não processados.
  • Características dessas funções C ++ de baixo nível:
    • Exclusivamente unidimensional (consecutivo na memória)
    • Não lida com alocações de memória.
      (Toda alocação, incluindo temporários, é tratada pelo código externo usando os recursos do OpenCV.)
    • O intervalo de comprimentos dos nomes dos símbolos (intrínsecas, nomes de variáveis ​​etc.) tem aproximadamente 10 a 20 caracteres, o que é bastante excessivo.
      (Lê como tagarelar techno.)
    • A reutilização de variáveis ​​SIMD é desencorajada porque os compiladores são bastante problemáticos ao analisar corretamente o código que não está gravado no estilo de codificação "atribuição única".
      (Arquivei vários relatórios de erros do compilador.)

Quais aspectos da programação do SIMD poderiam fazer com que a discussão diferisse do caso geral? Ou, por que o SIMD é diferente?

Em termos de custo inicial de desenvolvimento

  • É sabido que o custo inicial de desenvolvimento do código C ++ SIMD com bom desempenho é de cerca de 10x - 100x (com uma ampla margem) em comparação com o código C ++ gravado casualmente .
  • Conforme observado nas respostas para a escolha entre desempenho versus código legível / limpador? , a maioria dos códigos (incluindo código escrito casualmente e código SIMD) não é nem limpa nem rápida .
  • As melhorias evolutivas no desempenho do código (no código escalar e no SIMD) são desencorajadas (porque são vistas como uma espécie de retrabalho de software ), e o custo e o benefício não são rastreados.

Em termos de propensão
(por exemplo, o princípio de Pareto, também conhecido como a regra 80-20 )

  • Mesmo que o processamento de imagens compreenda apenas 20% de um sistema de software (em tamanho e funcionalidade do código), o processamento de imagens é comparativamente lento (quando visto como uma porcentagem do tempo gasto na CPU), levando mais de 80% do tempo.
    • Isso se deve ao efeito do tamanho dos dados: Um tamanho típico de imagem é medido em megabytes, enquanto o tamanho típico de dados sem imagem é medido em kilobytes.
  • Dentro do código de processamento de imagem, um programador SIMD é treinado para reconhecer automaticamente o código de 20% que compreende os pontos de acesso, identificando a estrutura do loop no código C ++. Assim, da perspectiva de um programador SIMD, 100% do "código que importa" é um gargalo de desempenho.
  • Geralmente, em um sistema de processamento de imagens, existem vários pontos de acesso e ocupam proporções comparáveis ​​de tempo. Por exemplo, pode haver 5 pontos de acesso cada um ocupando (20%, 18%, 16%, 14%, 12%) do tempo total. Para obter um ganho de alto desempenho, todos os pontos de acesso precisam ser reescritos no SIMD.
    • Isso é resumido como a regra de estourar balões: um balão não pode ser estourado duas vezes.
    • Suponha que haja alguns balões, digamos 5 deles. A única maneira de dizimá-los é estourá-los um por um.
    • Depois que o primeiro balão é acionado, os 4 balões restantes agora representam uma porcentagem maior do tempo total de execução.
    • Para obter mais ganhos, é preciso então estourar outro balão.
      (Isto é em desafio para a 80-20 regra de otimização: um bom resultado econômico pode ser alcançado após os 20% de frutas de menor suspensão foi escolhido.)

Em termos de legibilidade e manutenção

  • O código SIMD é claramente difícil de ler.

    • Isso é verdade mesmo que se siga todas as práticas recomendadas de engenharia de software, por exemplo, nomeação, encapsulamento, correção de const (e tornando óbvios os efeitos colaterais), decomposição de funções etc.
    • Isso é verdade mesmo para programadores SIMD experientes.
  • O código SIMD ideal é muito distorcido (consulte a observação) em comparação com seu código de protótipo C ++ equivalente.

    • Existem várias maneiras de contornar o código SIMD, mas apenas 1 em cada 10 tentativas alcançará resultados aceitáveis ​​rapidamente.
    • (Ou seja, na faixa dos ganhos de desempenho de 4x-10x, a fim de justificar um alto custo de desenvolvimento. Na prática, ganhos ainda maiores foram observados.)

(Observação)
Esta é a principal tese do projeto MIT Halide - citando o título do artigo literalmente:

"dissociar algoritmos de agendas para facilitar a otimização de pipelines de processamento de imagem"

Em termos de aplicabilidade futura

  • O código SIMD está estritamente vinculado a uma única arquitetura. Cada nova arquitetura (ou cada ampliação de registros SIMD) requer uma reescrita.
  • Ao contrário da maioria do desenvolvimento de software, cada parte do código SIMD é tipicamente escrita para um único objetivo que nunca muda.
    (Com exceção de portar para outras arquiteturas.)
  • Algumas arquiteturas mantêm perfeita compatibilidade com versões anteriores (Intel); alguns ficam aquém de uma quantidade trivial (ARM AArch64, substituindo vtblpor vtblq), mas que é suficiente para causar a falha na compilação de algum código.

Em termos de habilidades e treinamento

  • Não está claro quais pré-requisitos de conhecimento são necessários para treinar adequadamente um novo programador para escrever e manter o código SIMD.
  • Os graduados que aprenderam programação SIMD na escola parecem desprezá-la e descartá-la como uma carreira impraticável.
  • A leitura de desmontagem e o perfil de desempenho de baixo nível são citados como duas habilidades fundamentais para escrever código SIMD de alto desempenho. No entanto, não está claro como treinar sistematicamente programadores nessas duas habilidades.
  • A arquitetura moderna da CPU (que diverge significativamente do que é ensinado nos livros didáticos) torna o treinamento ainda mais difícil.

Em termos de correção e custos relacionados a defeitos

  • Uma única função de processamento SIMD é realmente coesa o suficiente para que se possa estabelecer a correção por:
    • Aplicação de métodos formais (com caneta e papel) e
    • Verificando intervalos inteiros de saída (com código de protótipo e executados fora do tempo de execução) .
  • O processo de verificação é, no entanto, muito caro (gasta 100% do tempo em revisão de código e 100% em verificação de modelo de protótipo), o que triplica o já caro custo de desenvolvimento do código SIMD.
  • Se, de alguma forma, um bug conseguir passar por esse processo de verificação, é quase impossível "reparar" (consertar), exceto substituir (reescrever) a função defeituosa suspeita.
  • O código SIMD sofre com a falta de defeitos no compilador C ++ (otimizando o gerador de código).
    • O código SIMD gerado usando modelos de expressão C ++ também sofre muito com defeitos do compilador.

Em termos de inovações disruptivas

  • Muitas soluções foram propostas pela academia, mas poucas estão vendo uso comercial generalizado.

    • MIT Halide
    • Stanford Darkroom
    • NT2 (Numerical Template Toolbox) e o Boost.SIMD relacionado
  • Bibliotecas com amplo uso comercial não parecem ser fortemente habilitadas para SIMD.

    • As bibliotecas de código aberto parecem mornas para o SIMD.
      • Recentemente, tenho essa observação em primeira mão depois de criar um perfil de um grande número de funções da API OpenCV, a partir da versão 2.4.9.
      • Muitas outras bibliotecas de processamento de imagem que eu criei perfil também não fazem uso pesado de SIMD, ou elas perdem os verdadeiros hotspots.
    • Bibliotecas comerciais parecem evitar completamente o SIMD.
      • Em alguns casos, eu já vi bibliotecas de processamento de imagem revertendo código otimizado para SIMD em uma versão anterior para código não SIMD em uma versão posterior, resultando em severas regressões de desempenho.
        (A resposta do fornecedor é que era necessário evitar erros do compilador.)

A pergunta deste programador: o código de baixa latência às vezes precisa ser "feio"? está relacionado, e eu escrevi anteriormente uma resposta a essa pergunta para explicar meus pontos de vista há alguns anos atrás.

No entanto, essa resposta é basicamente "apaziguamento" ao ponto de vista "otimização prematura", ou seja, ao ponto de vista que:

  • Todas as otimizações são prematuras por definição (ou a curto prazo por natureza ) e
  • A única otimização que traz benefícios a longo prazo é a simplicidade.

Mas esses pontos de vista são contestados neste artigo do ACM .


Tudo isso me leva a perguntar: o
código SIMD é diferente do código geral do aplicativo, e eu gostaria de saber se existe um consenso semelhante no setor em relação ao valor do código limpo e simples para o código SIMD.

rwong
fonte
2
Tem requisitos de desempenho? Você pode atender aos seus requisitos de desempenho sem usar o SIMD? Caso contrário, a questão é discutível.
Charles E. Grant
4
Isso é muito longo para uma pergunta, provavelmente porque boa parte dela é efetivamente uma tentativa de responder à pergunta e longa até mesmo para uma resposta (em parte porque aborda muito mais aspectos do que a maioria das respostas razoáveis).
3
Eu gosto de ter o código limpo / simples / lento (para prova de conceito inicial e para fins de documentação posteriores), além da (s) alternativa (s) otimizada (s). Isso facilita a compreensão (como as pessoas podem apenas ler o código limpo / simples / lento) e a verificação (comparando a versão otimizada com a versão limpa / simples / lenta manualmente e em testes de unidade)
Brendan
2
@Brendan Estive em um projeto semelhante e usei a abordagem de teste com código simples / lento. Embora seja uma opção que vale a pena considerar, também possui limitações. Primeiro, a diferença de desempenho pode se tornar proibitiva: testes usando código não otimizado podem ser executados por horas ... dias. Em segundo lugar, para processamento de imagem pode revelar-se que bit-por-bit comparação simplesmente não vai funcionar, quando o código otimizado produz resultados ligeiramente diferentes - de modo que um teria que usar comparação mais sofisticado, como raiz ef diff quadrado médio
mosquito
2
Estou votando para encerrar esta questão como fora de tópico, porque não é um problema de programação conceitual, conforme descrito na Central de Ajuda .
durron597

Respostas:

6

Eu não escrevi muito código SIMD para mim, mas muito código assembler algumas décadas atrás. O AFAIK usando intrínsecas SIMD é essencialmente programação de assembler, e toda a sua pergunta pode ser reformulada substituindo "SIMD" pela palavra "assembly". Por exemplo, os pontos que você já mencionou, como

  • o código leva de 10 a 100 vezes para ser desenvolvido em vez de "código de alto nível"

  • está ligado a uma arquitetura específica

  • o código nunca é "limpo" nem fácil de refatorar

  • você precisa de especialistas para escrevê-lo e mantê-lo

  • depurar e manter é difícil, evoluindo muito difícil

não são de forma alguma "especiais" para o SIMD - esses pontos são verdadeiros para qualquer tipo de linguagem assembly e são todos "consenso da indústria". E a conclusão na indústria de software também é praticamente a mesma da montadora:

  • não escreva se não precisar - use uma linguagem de alto nível sempre que possível e permita que os compiladores façam o trabalho duro

  • se os compiladores não forem suficientes, pelo menos encapsule as partes de "baixo nível" em algumas bibliotecas, mas evite espalhar o código por todo o programa

  • como é quase impossível escrever código SIMD ou montador "auto-documentado", tente equilibrar isso com muita documentação.

Certamente, há realmente uma diferença na situação do assembly "clássico" ou do código de máquina: atualmente, os compiladores modernos geralmente produzem código de máquina de alta qualidade a partir de uma linguagem de alto nível, que geralmente é melhor otimizada do que o código de assembler escrito manualmente. Para as arquiteturas SIMD que são populares hoje em dia, a qualidade dos compiladores disponíveis é AFAIK muito abaixo disso - e talvez nunca chegue a isso, pois a vetorização automática ainda é um tópico de pesquisa científica. Veja, por exemplo, este artigo que descreve as diferenças de opimização entre um compilador e um humano, fornecendo uma noção de que pode ser muito difícil criar bons compiladores SIMD.

Como você já descreveu na sua pergunta, também existe um problema de qualidade nas bibliotecas atuais de ponta. Portanto, o melhor que podemos esperar do IMHO é que, nos próximos anos, a qualidade dos compiladores e bibliotecas aumente, talvez o hardware SIMD precise mudar para se tornar mais "amigável ao compilador", talvez linguagens de programação especializadas que suportem uma vetorização mais fácil (como o Halide, que você mencionou duas vezes) se tornará mais popular (isso já não era uma força do Fortran?). Segundo a Wikipedia , o SIMD se tornou "um produto em massa" cerca de 15 a 20 anos atrás (e Halide tem menos de 3 anos de idade, quando interpreto os documentos corretamente). Compare isso com os compiladores de tempo para a linguagem assembly "clássica" necessária para amadurecer. De acordo com este artigo da WikipediaLevou quase 30 anos (de ~ 1970 até o final dos anos 90) até os compiladores excederem o desempenho de especialistas humanos (na produção de código de máquina não paralelo). Portanto, talvez tenhamos que esperar mais 10 a 15 anos até que o mesmo aconteça com os compiladores habilitados para SIMD.

Doc Brown
fonte
por minha leitura do artigo Wikipedia , parece haver um general consenso da indústria que o código otimizado em nível baixo é "considerado difícil de usar, devido aos inúmeros detalhes técnicos que devem ser lembradas"
mosquito
@gnat: sim, com certeza, mas acho que, se eu adicionar isso à minha resposta, devo fazer uma dúzia de outras coisas já mencionadas pelo OP em outras palavras, em sua pergunta muito longa.
Doc Brown
Concordo, a análise em sua resposta parece ser bom o suficiente como é, acrescentando que a referência seria implicam um risco de "sobrecarga"-lo
mosquito
4

Minha organização lidou com esse problema exato. Nossos produtos estão no espaço de vídeo, mas grande parte do código que escrevemos é de processamento de imagem que funcionaria também para imagens estáticas.

Nós "resolvemos" (ou talvez "resolvemos") o problema escrevendo nosso próprio compilador. Isso não é tão louco quanto parece à primeira vista. Tem um conjunto restrito de entradas. Sabemos que todo o código está funcionando em imagens, principalmente imagens RGBA. Definimos algumas restrições, como que os buffers de entrada e saída nunca podem se sobrepor, portanto não há alias de ponteiro. Coisas assim.

Em seguida, escrevemos nosso código na OpenGL Shading Language (glsl). Ele é compilado para código escalar, SSE, SSE2, SSE3, AVX, Neon e, claro, glsl real. Quando precisamos oferecer suporte a uma nova plataforma, atualizamos o compilador para gerar o código para essa plataforma.

Também fazemos o mosaico das imagens para melhorar a coerência do cache e coisas assim. Mas, mantendo o processamento da imagem em um pequeno kernel e usando o glsl (que nem suporta ponteiros) reduzimos bastante a complexidade de compilar o código.

Essa abordagem não é para todos e tem seus próprios problemas (você precisa garantir a correção do compilador, por exemplo). Mas funcionou bastante bem para nós.

user1118321
fonte
Isso soa 🔥🔥! Este produto você vende ou disponibiliza separadamente? (Além disso, é 'AVC' = AVX?)
Ahmed Fasih
Desculpe, sim, eu quis dizer AVX (eu vou consertar isso). Atualmente, não vendemos o compilador como um produto independente, embora isso possa acontecer no futuro.
usuário1118321
Não é brincadeira, isso parece muito legal. A coisa mais próxima que eu já vi assim é como o compilador CUDA costumava ser capaz de criar programas "seriais" que são executados na CPU para depuração - esperávamos que isso se generalizasse de maneira a escrever código de CPU SIMD multiencadeado, mas infelizmente. A próxima coisa mais próxima que consigo pensar é no OpenCL - vocês avaliaram o OpenCL e o acharam inferior ao seu compilador GLSL para todos?
Ahmed Fasih
1
Bem, o OpenCL não existia quando começamos, eu não acho. (Ou, se foi, foi relativamente novo.) Portanto, não entrou na equação.
user1118321
0

Parece não adicionar muita sobrecarga de manutenção se você considerar usar um idioma de nível superior:

Vector<float> values = GetValues();
Vector<float> increment = GetIncrement();

// Perform addition as a vector operation:
List<float> result = (values + increment).ToList();

vs

List<float> values = GetValues();
List<float> increment = GetIncrement();

// Perform addition as a monadic sequence operation:
List<float> result = values.Zip(increment, (v, i) => v + i).ToList();

É claro que você terá que enfrentar as limitações da biblioteca, mas não a manterá por conta própria. Pode ser um bom equilíbrio entre o custo de manutenção e o ganho de desempenho.

http://blogs.msdn.com/b/dotnet/archive/2014/04/07/the-jit-finally-proposed-jit-and-simd-are-getting-married.aspx

http://blogs.msdn.com/b/dotnet/archive/2014/05/13/update-to-simd-support.aspx

Den
fonte
de acordo com a minha leitura, a opção de usar bibliotecas externas já foi investigada e abordada pelo asker: "Bibliotecas com amplo uso comercial não parecem ser fortemente habilitadas para SIMD ..."
gnat
@gnat Na verdade, eu li esse parágrafo inteiro, não apenas os tópicos de nível superior, e o pôster não menciona nenhuma biblioteca SIMD de uso geral, apenas as de visão computacional e processamento de imagem. Sem mencionar que a análise de aplicativos de linguagens de nível superior está completamente ausente, apesar de nenhuma tag C ++ e nenhuma especificidade C ++ refletida no título da pergunta. Isso me leva a acreditar que, embora minha pergunta não seja considerada primária, é provável que agregue valor, conscientizando as pessoas sobre outras opções.
Den
1
No meu entender, o OP está perguntando se existem soluções com amplo uso comercial. Embora eu aprecie sua dica (talvez eu possa usar a lib para um projeto aqui), pelo que vejo, o RyuJIT está longe de ser um "padrão do setor amplamente aceito".
Doc Brown
@DocBrown talvez, mas sua pergunta real é formulada para ser mais genérica: "... consenso da indústria sobre o valor do código limpo e simples para o código SIMD ...". Duvido que exista algum consenso (oficial), mas afirmo que linguagens de nível superior podem reduzir a diferença entre o código "usual" e o SIMD, assim como o C ++, vamos esquecer o assembly, reduzindo assim os custos de manutenção.
Den
-1

Eu fiz programação de montagem no passado, não programação SIMD recentemente.

Você já pensou em usar um compilador compatível com SIMD como o da Intel? É um guia para Vectorization com Intel® C ++ compiladores interessante?

Vários de seus comentários, como "estourar balões", sugerem o uso de um compilador (para obter benefícios, se você não tiver um único ponto de acesso).

ChrisW
fonte
por minha leitura, esta abordagem foi julgado pelo consulente, consulte menciona de bugs do compilador / defeitos no pergunta
mosquito
O OP não disse se eles tentaram o compilador Intel , que também é o assunto deste tópico do Programmers.SE . A maioria das pessoas ainda não experimentou. Não é para todos; mas pode atender aos negócios / pergunta do OP (melhor desempenho para custos mais baixos de codificação / design / manutenção).
ChrisW
bem, o que eu li na pergunta sugere que o asker está ciente dos compiladores da Intel e de outras arquiteturas: "Algumas arquiteturas mantêm perfeita compatibilidade com versões anteriores (Intel); outras ficam aquém ..."
gnat
"Intel" nessa frase significa o Intel-o-chip-designer, não o Intel-o-compilador-escritor.
ChrisW