Estou resolvendo um problema e envolve a classificação de 10 números (int32) muito rapidamente. Meu aplicativo precisa classificar 10 números milhões de vezes o mais rápido possível. Estou amostrando um conjunto de dados de bilhões de elementos e sempre que preciso escolher 10 números (simplificados) e ordená-los (e tirar conclusões da lista de 10 elementos).
Atualmente, estou usando a classificação por inserção, mas imagino que poderia implementar um algoritmo de classificação personalizado muito rápido para o meu problema específico de 10 números, que superaria a classificação por inserção.
Alguém tem alguma idéia de como abordar esse problema?
algorithm
sorting
insertion-sort
sorting-network
bodacydo
fonte
fonte
if
instruções aninhadas deve funcionar melhor. Evite loops.Respostas:
(Seguindo a sugestão do HelloWorld de analisar as redes de classificação.)
Parece que uma rede de 29 comparações / trocas é a maneira mais rápida de fazer uma classificação de 10 entradas. Eu usei a rede descoberta por Waksman em 1969 para este exemplo em Javascript, que deve ser traduzido diretamente para C, pois é apenas uma lista de
if
declarações, comparações e swaps.Aqui está uma representação gráfica da rede, dividida em fases independentes. Para aproveitar o processamento paralelo, o agrupamento 5-4-3-4-4-4-3-2 pode ser alterado para um agrupamento 4-4-4-4-4-4-4-3-2.
fonte
#define SORTPAIR(data, i1, i2) if (data[i1] > data[i2]) { int swap = data[i1]... }
Quando você lida com esse tamanho fixo, dê uma olhada em Classificação de redes . Esses algoritmos têm um tempo de execução fixo e são independentes de suas entradas. Para o seu caso de uso, você não possui uma sobrecarga que alguns algoritmos de classificação possuem.
A classificação bitônica é uma implementação dessa rede. Este funciona melhor com len (n) <= 32 em uma CPU. Em entradas maiores, você pode pensar em mudar para uma GPU. https://en.wikipedia.org/wiki/Sorting_network
Btw, uma boa página para comparar algoritmos de classificação é esta aqui (embora esteja faltando o
bitonic sort
.http://www.sorting-algorithms.com
fonte
Use uma rede de classificação que tenha comparações em grupos de 4, para que você possa fazer isso nos registros SIMD. Um par de instruções mínimas / máximas compactadas implementa uma função comparadora compactada. Desculpe, não tenho tempo agora para procurar uma página que lembro de ter visto sobre isso, mas espero que a pesquisa nas redes de classificação SIMD ou SSE aconteça algo.
O x86 SSE possui instruções mínimas e máximas de inteiro de 32 bits para vetores de quatro ints de 32 bits. O AVX2 (Haswell e posterior) tem o mesmo, mas para vetores 256b de 8 polegadas. Também existem instruções de shuffle eficientes.
Se você tiver muitas classificações pequenas independentes, poderá ser possível realizar 4 ou 8 classificações em paralelo usando vetores. Esp. se você estiver escolhendo elementos aleatoriamente (para que os dados a serem classificados não sejam contíguos na memória de qualquer maneira), você pode evitar embaralhar e simplesmente comparar na ordem que precisar. 10 registros para armazenar todos os dados de 4 (AVX2: 8) listas de 10 polegadas ainda deixam 6 registros para espaço vazio.
As redes de classificação vetorial são menos eficientes se você também precisar classificar os dados associados. Nesse caso, a maneira mais eficiente parece ser usar uma comparação compactada para obter uma máscara de quais elementos foram alterados e usar essa máscara para misturar vetores de (referências a) dados associados.
fonte
Que tal um tipo de seleção desenrolado e sem ramificação?
http://coliru.stacked-crooked.com/a/71e18bc4f7fa18c6
As únicas linhas relevantes são as duas primeiras
#define
.Ele usa duas listas e verifica novamente a primeira por dez vezes, o que seria um tipo de seleção mal implementado, mas evita ramificações e loops de comprimento variável, o que pode compensar os processadores modernos e um conjunto de dados tão pequeno.
Referência
Comparei a rede de classificação e meu código parece ser mais lento. No entanto, tentei remover o desenrolar e a cópia. Executando este código:
Estou sempre obtendo melhores resultados para a classificação de seleção sem ramificação em comparação com a rede de classificação.
fonte
for ( ; i<10; i++) (m > a[i]) && (m = a[i], indx = i );
seja excepcionalmente bem otimizada. (curto-circuito geralmente é uma forma de ramificação)std::shuffle
comfor (int n = 0; n<10; n++) a[n]=g();
. O tempo de execução é reduzido pela metade e a rede está mais rápida agora.std::sort
?std::sort
também, mas o desempenho foi tão ruim que nem o incluí no benchmark. Eu acho que com pequenos conjuntos de dados há bastante sobrecarga.A pergunta não diz que este é algum tipo de aplicativo baseado na Web. A única coisa que chamou minha atenção foi:
Como engenheiro de software e hardware, isso absolutamente grita "FPGA" para mim. Não sei que tipo de conclusões você precisa tirar do conjunto classificado de números ou de onde vêm os dados, mas sei que seria quase trivial processar algo entre cem milhões e um bilhão desses "triagem e classificação". analisar "operações por segundo . Eu fiz o trabalho de sequenciamento de DNA assistido por FPGA no passado. É quase impossível superar o enorme poder de processamento dos FPGAs quando o problema é adequado para esse tipo de solução.
Em algum nível, o único fator limitante se torna a rapidez com que você pode inserir dados em um FPGA e a rapidez com que consegue obtê-los.
Como ponto de referência, projetei um processador de imagem em tempo real de alto desempenho que recebia dados de imagem RGB de 32 bits a uma taxa de cerca de 300 milhões de pixels por segundo. Os dados foram transmitidos através de filtros FIR, multiplicadores de matriz, tabelas de pesquisa, blocos de detecção de arestas espaciais e várias outras operações antes de sair do outro lado. Tudo isso em um FPGA Xilinx Virtex2 relativamente pequeno, com clock interno que varia de 33 MHz a, se bem me lembro, 400 MHz. Ah, sim, ele também teve uma implementação de controlador DDR2 e executou dois bancos de memória DDR2.
Um FPGA pode emitir um tipo de dez números de 32 bits em cada transição de relógio enquanto opera a centenas de MHz. Haveria um pequeno atraso no início da operação, à medida que os dados preenchessem os pipelines de processamento. Depois disso, você poderá obter um resultado por relógio. Ou mais, se o processamento puder ser paralelo através da replicação do pipeline de classificação e análise. A solução, em princípio, é quase trivial.
O ponto é: se o aplicativo não estiver ligado ao PC e o fluxo e o processamento de dados forem "compatíveis" com uma solução FPGA (independente ou como uma placa de coprocessador na máquina), não há como você seguir em frente. ser capaz de superar o nível atingível de desempenho com software escrito em qualquer idioma, independentemente do algoritmo.
EDITAR:
Basta executar uma pesquisa rápida e encontrar um documento que possa ser útil para você. Parece que remonta a 2012. Você pode fazer MUITO melhor desempenho hoje (e até então). Aqui está:
Classificação de redes em FPGAs
fonte
Recentemente, escrevi uma pequena classe que usa o algoritmo Bose-Nelson para gerar uma rede de classificação em tempo de compilação.
Pode ser usado para criar uma classificação muito rápida para 10 números.
Observe que, em vez de uma
if (compare) swap
declaração, codificamos explicitamente os operadores ternários para min e max. Isso é para ajudar a convencer o compilador a usar código sem ramificação.Benchmarks
Os seguintes benchmarks são compilados com clang -O3 e executados no meu macbook air de meados de 2012.
Classificando dados aleatórios
Comparando-o com o código do DarioP, aqui está o número de milissegundos necessários para classificar 1 milhão de matrizes int de 32 bits de tamanho 10:
Rede de classificação codificada por código 10: 88,774 ms Classificação por
Bose-Nelson modelada 10: 27,815 ms
Usando essa abordagem de modelo, também podemos gerar redes de classificação no tempo de compilação para outro número de elementos.
Tempo (em milissegundos) para classificar 1 milhão de matrizes de vários tamanhos.
O número de milissegundos para matrizes de tamanho 2, 4, 8 são 1,943, 8,655, 20,246, respectivamente.
Créditos a Glenn Teitelbaum pela classificação de inserção desenrolada.
Aqui estão os relógios médios por classificação para pequenas matrizes de 6 elementos. O código de referência e os exemplos podem ser encontrados nesta pergunta:
Tipo mais rápido de comprimento fixo 6 int array
Ele executa tão rápido quanto o exemplo mais rápido da pergunta para 6 elementos.
Desempenho para classificar dados classificados
Frequentemente, as matrizes de entrada já podem ser classificadas ou principalmente classificadas.
Nesses casos, a classificação por inserção pode ser uma melhor escolha.
Você pode escolher um algoritmo de classificação apropriado, dependendo dos dados.
O código usado para os benchmarks pode ser encontrado aqui .
fonte
v1 = v0 < v1 ? v1 : v0; // Max
pode ainda ramo, nesse caso, podem ser substituídos comv1 += v0 - t
, porque set
év0
, em seguida,v1 + v0 -t == v1 + v0 - v0 == v1
outra coisat
év1
ev1 + v0 -t == v1 + v0 - v1 == v0
maxss
ouminss
em compiladores modernos. Mas nos casos em que não funciona, outras formas de troca podem ser usadas. :)Embora uma classificação de rede tenha boas chances de ser rápida em matrizes pequenas, às vezes você não pode superar a classificação de inserção se estiver otimizado adequadamente. Por exemplo, inserção em lote com 2 elementos:
fonte
in[y+2]= in[y];
, erro de digitação?Você pode desenrolar totalmente
insertion sort
Para facilitar isso,
template
s recursivos podem ser usados sem sobrecarga de função. Como já é umtemplate
, tambémint
pode ser umtemplate
parâmetro. Isso também torna a criação de tamanhos de matriz de codificação diferentes de 10.Observe que para classificar
int x[10]
a chamada éinsert_sort<int, 9>::sort(x);
porque a classe usa o índice do último item. Isso pode ser resolvido, mas seria mais código para ler.Nos meus testes, isso foi mais rápido que os exemplos de rede de classificação.
fonte
Por razões semelhantes às que descrevi aqui , as seguintes funções de classificação
sort6_iterator()
esort10_iterator_local()
, devem ter um bom desempenho, onde a rede de classificação foi retirada daqui :Para chamar essa função, passei para
std::vector
iterador.fonte
Uma ordenação por inserção requer, em média, 29,6 comparações para ordenar 10 entradas com um melhor caso de 9 e um pior de 45 (dada entrada que está na ordem inversa).
Um {9,6,1} shellsort exigirá, em média, 25,5 comparações para classificar 10 entradas. O melhor caso é 14 comparações, o pior é 34 e classificar uma entrada reversa requer 22.
Portanto, o uso do shellsort em vez da inserção por inserção reduz a média de casos em 14%. Embora o melhor caso seja aumentado em 56%, o pior caso é reduzido em 24%, o que é significativo em aplicações em que é importante manter o pior desempenho possível. O caso inverso é reduzido em 51%.
Como você parece familiarizado com a classificação por inserção, pode implementar o algoritmo como uma rede de classificação para {9,6} e, em seguida, aplicar a classificação por inserção ({1}) depois disso:
fonte