Além da excelente resposta de ajuste de hardware / configuração do @jimwise, "linux de baixa latência" está implicando:
- C ++ por razões de determinismo (sem atraso surpresa enquanto o GC entra em ação), acesso a instalações de baixo nível (E / S, sinais), potência do idioma (uso total de TMP e STL, tipo de segurança).
- prefira velocidade sobre memória:> 512 Gb de RAM é comum; os bancos de dados são produtos NoSQL in-memory, com cache antecipado ou exóticos.
- escolha do algoritmo: tão rápido quanto possível versus sã / compreensível / extensível, por exemplo, livre de bloqueios, várias matrizes de bits em vez de propriedades de matriz de objetos com propriedades booleanas.
- uso completo de recursos do SO, como Memória Compartilhada entre processos em diferentes núcleos.
- seguro. O software HFT geralmente é colocado em uma bolsa de valores, portanto as possibilidades de malware são inaceitáveis.
Muitas dessas técnicas se sobrepõem ao desenvolvimento de jogos, que é uma das razões pelas quais a indústria de software financeiro absorve todos os programadores de jogos recentemente redundantes (pelo menos até que paguem o aluguel atrasado).
A necessidade subjacente é poder ouvir um fluxo muito alto de largura de banda de dados do mercado, como preços de ações (commodities, commodities, câmbio) e, em seguida, tomar uma decisão muito rápida de comprar / vender / não fazer nada com base na segurança, no preço e participações atuais.
Obviamente, tudo isso pode dar errado também.
Então, vou elaborar o ponto das matrizes de bits . Digamos que temos um sistema de negociação de alta frequência que opera em uma longa lista de pedidos (compre 5k IBM, venda 10k DELL etc.). Digamos que precisamos determinar rapidamente se todos os pedidos foram atendidos, para que possamos avançar para a próxima tarefa. Na programação OO tradicional, será semelhante a:
class Order {
bool _isFilled;
...
public:
inline bool isFilled() const { return _isFilled; }
};
std::vector<Order> orders;
bool needToFillMore = std::any_of(orders.begin(), orders.end(),
[](const Order & o) { return !o.isFilled(); } );
a complexidade algorítmica desse código será O (N), pois é uma varredura linear. Vamos dar uma olhada no perfil de desempenho em termos de acessos à memória: cada iteração do loop dentro de std :: any_of () chamará o.isFilled (), que está embutido, tornando-se um acesso à memória de _isFilled, 1 byte (ou 4, dependendo da sua arquitetura, compilador e configurações do compilador) em um objeto digamos 128 bytes no total. Portanto, estamos acessando 1 byte em cada 128 bytes. Quando lemos o byte de 1 byte, presumindo que seja o pior caso, obteremos uma falha no cache de dados da CPU. Isso fará com que uma solicitação de leitura para a RAM leia uma linha inteira da RAM ( veja aqui para mais informações ) apenas para ler 8 bits. Portanto, o perfil de acesso à memória é proporcional a N.
Compare isso com:
const size_t ELEMS = MAX_ORDERS / sizeof (int);
unsigned int ordersFilled[ELEMS];
bool needToFillMore = std::any_of(ordersFilled, &ordersFilled[ELEMS+1],
[](int packedFilledOrders) { return !(packedOrders == 0xFFFFFFFF); }
o perfil de acesso à memória disso, assumindo o pior caso novamente, é ELEMS dividido pela largura de uma linha de RAM (varia - pode ser de canal duplo ou triplo, etc.).
Com efeito, estamos otimizando algoritmos para padrões de acesso à memória. Nenhuma quantidade de RAM ajudará - é o tamanho do cache de dados da CPU que causa essa necessidade.
Isso ajuda?
Há uma excelente conversa sobre CPPCon sobre programação de baixa latência (para HFT) no YouTube: https://www.youtube.com/watch?v=NH1Tta7purM