O STL deve ser evitado em grandes aplicações?


24

Isso pode parecer uma pergunta estranha, mas no meu departamento estamos tendo problemas com a seguinte situação:

Estamos trabalhando aqui em um aplicativo de servidor, que está crescendo cada vez mais, mesmo no momento em que estamos pensando em dividi-lo em diferentes partes (arquivos DLL), carregando dinamicamente quando necessário e descarregando posteriormente, para poder lidar com os problemas de desempenho.

Mas: as funções que estamos usando estão transmitindo parâmetros de entrada e saída como objetos STL e, como mencionado em uma resposta de estouro de pilha , essa é uma péssima idéia. (A postagem contém algumas soluções e hacks, mas tudo não parece muito sólido.)

Obviamente, poderíamos substituir os parâmetros de entrada / saída por tipos C ++ padrão e criar objetos STL a partir daqueles que estavam dentro das funções, mas isso pode estar causando queda no desempenho.

Tudo bem concluir que, caso você esteja pensando em construir um aplicativo, que pode crescer tanto que um único PC não aguenta mais, você não deve usar o STL como uma tecnologia?

Mais informações sobre esta questão:
Parece haver alguns mal-entendidos sobre a questão: o problema é o seguinte:
Meu aplicativo está usando uma quantidade enorme de desempenho (CPU, memória) para concluir seu trabalho, e eu gostaria de dividir este trabalho em partes diferentes (como o programa já está dividido em várias funções), não é tão difícil criar algumas DLLs fora do meu aplicativo e colocar algumas das funções na tabela de exportação dessas DLLs. Isso resultaria na seguinte situação:

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 é a instância do aplicativo, instalada na Máquina1, enquanto App_Inst2 é a instância do mesmo aplicativo, instalada na Máquina2.
DLL1.x é uma DLL, instalada na Máquina1, enquanto DLL2.x é uma DLL, instalada na Máquina2.
DLLx.1 abrange a função exportada1.
DLLx.2 abrange a função exportada2.

Agora na Machine1 eu gostaria de executar function1 e function2. Sei que isso sobrecarregará o Machine1, portanto, gostaria de enviar uma mensagem para App_Inst2, solicitando que a instância do aplicativo execute a função2.

Os parâmetros de entrada / saída de function1 e function2 são objetos STL (C ++ Standard Type Library), e regularmente posso esperar que o cliente faça atualizações de App_Inst1, App_Inst2, DLLx.y (mas não todos, o cliente pode atualizar o Machine1, mas não Machine2, ou apenas atualize os aplicativos, mas não as DLLs ou vice-versa, ...). Obviamente, se a interface (parâmetros de entrada / saída) mudar, o cliente será forçado a fazer atualizações completas.

No entanto, como mencionado no URL StackOverflow referido, uma simples recompilação de App_Inst1 ou de uma das DLLs pode fazer com que todo o sistema desmorone, portanto, meu título original desta postagem, desaconselha o uso de STL (C ++ Standard Template Library) para grandes aplicações.

Espero que, por este meio, tenha esclarecido algumas dúvidas / dúvidas.


44
Tem certeza de que está tendo problemas de desempenho devido ao seu tamanho do executável ? Você pode adicionar alguns detalhes sobre se é realista assumir que todo o seu software é compilado com o mesmo compilador (por exemplo, de uma só vez no servidor de compilação) ou se você realmente deseja se dividir em equipes independentes?
Nvoigt

5
Basicamente, você precisa de uma pessoa cujo trabalho dedicado seja "build manager" e "release manager", para garantir que todos os projetos C ++ estejam sendo compilados na mesma versão do compilador e com configurações idênticas do compilador C ++, compiladas a partir de um instantâneo (versão) consistente da fonte código, etc. Normalmente, isso é tratado sob a bandeira da "integração contínua". Se você pesquisar online, encontrará muitos artigos e ferramentas. Práticas desatualizadas podem se auto-reforçar - uma prática desatualizada pode levar todas as práticas a serem desatualizadas.
rwong

8
A resposta aceita na pergunta vinculada afirma que o problema está nas chamadas C ++ em geral. Portanto, "C ++, mas não STL" não ajuda, você precisa usar o C simples para estar do lado seguro (mas também veja as respostas, a serialização é provavelmente uma solução melhor).
Frax

52
carregamento dinâmico quando necessário e descarregamento posteriormente, para poder lidar com os problemas de desempenho Quais "problemas de desempenho"? Não conheço outros problemas além de usar muita memória que pode ser corrigida descarregando coisas como DLLs da memória - e se esse é o problema, a solução mais fácil é comprar mais RAM. Você definiu o perfil de seu aplicativo para identificar os gargalos reais de desempenho? Como isso soa como um problema XY - você tem "problemas de desempenho" não especificados e alguém já decidiu a solução.
Andrew Henle

4
@MaxBarraclough "O STL" é perfeitamente aceito como um nome alternativo para os contêineres e funções de modelo que foram incluídos na Biblioteca Padrão C ++. De fato, as Diretrizes Principais do C ++, escritas por Bjarne Stroustrup e Herb Sutter, fazem repetidamente referência ao "STL" ao falar sobre elas. Você não pode obter uma fonte muito mais autoritativa do que isso.
Sean Burton

Respostas:


110

Esse é um problema XY clássico, extremamente frio.

Seu verdadeiro problema são problemas de desempenho. No entanto, sua pergunta deixa claro que você não fez nenhum perfil ou outra avaliação de onde os problemas de desempenho realmente vêm. Em vez disso, você espera que dividir seu código em DLLs resolva magicamente o problema (o que não acontecerá, para constar) e agora você está preocupado com um aspecto dessa não solução.

Em vez disso, você precisa resolver o problema real. Se você tiver vários executáveis, verifique qual deles está causando a desaceleração. Enquanto você está nisso, verifique se o programa está demorando todo o tempo de processamento, e não um driver Ethernet mal configurado ou algo assim. E depois disso, comece a criar um perfil das várias tarefas no seu código. O temporizador de alta precisão é seu amigo aqui. A solução clássica é monitorar os tempos médios e de pior caso de processamento para obter um pedaço de código.

Quando você tiver dados, poderá descobrir como lidar com o problema e, então, onde otimizar.


54
"Em vez disso, você espera que dividir seu código em DLLs resolva magicamente o problema (o que não acontecerá, para o registro)" - +1 para isso. Seu sistema operacional quase certamente implementa paginação por demanda, que alcança exatamente o mesmo resultado que a funcionalidade de carregamento e descarregamento de DLLs, apenas automaticamente, em vez de exigir intervenção manual. Mesmo se você for melhor em prever quanto tempo um pedaço de código deve permanecer, uma vez usado, do que o sistema de memória virtual do sistema operacional (o que é realmente improvável), o sistema operacional armazenará em cache o arquivo DLL e negará seus esforços de qualquer maneira .
Jules

@Jules Ver atualização - eles esclareceram que as DLLs existem apenas em máquinas separadas, então talvez eu possa ver essa solução funcionando. Agora há uma sobrecarga de comunicação, tão difícil de ter certeza.
Izkata 23/05

2
@ Izkata - ainda não está totalmente claro, mas acho que o que está descrito é que eles desejam selecionar dinamicamente (com base na configuração de tempo de execução) uma versão de cada função que seja local ou remota. Mas qualquer parte do arquivo EXE que nunca é usada em uma determinada máquina simplesmente nunca será carregada na memória, portanto, o uso de DLLs para esse fim não é necessário. Apenas inclua as duas versões de todas as funções na compilação padrão e crie uma tabela de ponteiros de função (ou objetos que podem ser chamados pelo C ++ ou qualquer outro método que você preferir) para invocar a versão apropriada de cada função.
Jules

38

Se você precisar dividir um software entre várias máquinas físicas, precisará de alguma forma de serialização ao transmitir dados entre máquinas, pois somente em alguns casos você pode realmente enviar o mesmo binário exato entre máquinas. A maioria dos métodos de serialização não tem problemas ao lidar com tipos de STL, portanto, esse caso não é algo que me preocuparia.

Se você precisar dividir um aplicativo em DLLs (Bibliotecas Compartilhadas) (antes de fazer isso por motivos de desempenho, verifique se ele realmente resolveria seus problemas de desempenho) a passagem de objetos STL pode ser um problema, mas não precisa ser. Como o link que você forneceu já descreve, a passagem de objetos STL funcionará se você usar o mesmo compilador e as mesmas configurações. Se os usuários fornecerem as DLLs, talvez você não consiga contar com facilidade. Se você fornecer todas as DLLs e compilar tudo junto, no entanto, poderá contar com isso e o uso de objetos STL através dos limites da DLL se tornará muito possível. Você ainda precisa observar as configurações do compilador para não obter vários heaps diferentes se passar a propriedade do objeto, embora esse não seja um problema específico da STL.


11
Sim, e especialmente a parte sobre a passagem de objetos alocados entre os limites da DLL / so. De um modo geral, a única maneira de evitar absolutamente o problema do alocador múltiplo é garantir que a DLL / so (ou biblioteca!) Que alocou a estrutura também a liberte. É por isso que você vê muitas APIs de estilo C escritas dessa maneira: uma API gratuita explícita para cada API que retorna um array / estrutura alocado. O problema adicional com o STL é que o responsável pela chamada pode esperar modificar a estrutura de dados complexa passada (adicionar / remover elementos) e isso também não pode ser permitido. Mas é difícil de aplicar.
Davidbak

11
Se eu tivesse que dividir um aplicativo como esse, provavelmente usaria COM, mas isso geralmente aumenta o tamanho do código, pois cada componente traz suas próprias bibliotecas C e C ++ (que podem ser compartilhadas quando são iguais, mas podem divergir quando necessário, . por exemplo, durante as transições não estou convencido de que este é o curso de ação apropriado para o problema do OP, no entanto.
Simon Richter

2
Como um exemplo específico, o programa é altamente provável em algum lugar querer enviar um texto para outra máquina. Em algum momento, haverá um ponteiro para alguns caracteres envolvidos na representação desse texto. Você absolutamente não pode apenas transmitir os bits dos ponteiros e esperar um comportamento definido do lado do destinatário
Caleth

20

Estamos trabalhando aqui em um aplicativo de servidor, que está crescendo cada vez mais, mesmo no momento em que estamos pensando em dividi-lo em diferentes partes (DLLs), carregando dinamicamente quando necessário e descarregando posteriormente, para poder lidar com o Problemas de desempenho

A RAM é barata e, portanto, o código inativo é barato. O código de carregamento e descarregamento (especialmente o descarregamento) é um processo frágil e é improvável que tenha um efeito significativo no desempenho de seus programas no hardware moderno de desktop / servidor.

O cache é mais caro, mas isso afeta apenas o código que está ativo recentemente, e não o código que está na memória sem uso.

Em geral, os programas superam seus computadores por causa do tamanho dos dados ou do tempo da CPU, não do tamanho do código. Se o tamanho do seu código está ficando tão grande que está causando grandes problemas, é provável que você queira ver por que isso está acontecendo.

Mas: as funções que estamos usando estão transmitindo parâmetros de entrada e saída como objetos STL e, como mencionado neste URL StackOverflow, essa é uma péssima idéia.

Deve estar tudo bem, desde que as dlls e o executável sejam todos criados com o mesmo compilador e vinculados dinamicamente à mesma biblioteca de tempo de execução C ++. Daqui resulta que, se a aplicação e as dlls associadas forem construídas e implantadas como uma única unidade, isso não deverá ser um problema.

O problema é quando as bibliotecas são construídas por pessoas diferentes ou podem ser atualizadas separadamente.

Tudo bem concluir que, caso você esteja pensando em construir um aplicativo, que pode crescer tanto que um único PC não aguenta mais, você não deve usar o STL como uma tecnologia?

Na verdade não.

Depois de começar a espalhar um aplicativo por várias máquinas, você tem uma série de considerações sobre como passa os dados entre essas máquinas. Os detalhes de se tipos STL ou mais básicos são usados ​​provavelmente se perderão no ruído.


2
O código inativo provavelmente nunca é carregado na RAM em primeiro lugar. A maioria dos sistemas operacionais carrega páginas de executáveis ​​apenas se forem realmente necessárias.
Jules

11
@Jules: Se o código morto for misturado com o código ativo (com tamanho da página = granularidade de 4k), ele será mapeado + carregado. O cache funciona com granularidade muito mais fina (64B), portanto, ainda é verdade que as funções não utilizadas não prejudicam muito. Porém, cada página precisa de uma entrada TLB e (ao contrário da RAM), que é um recurso de tempo de execução escasso. (Os mapeamentos suportados por arquivos normalmente não usam páginas enormes, pelo menos não no Linux; uma página enorme é 2MiB no x86-64, para que você possa cobrir muito mais código ou dados sem obter nenhuma falha no TLB com páginas enormes.)
Peter Cordes

11
O que o @PeterCordes observa: Portanto, certifique-se de usar o "PGO" como parte do seu processo de compilação para lançamento!
JDługosz 25/05

13

Não, não acho que essa conclusão se segue. Mesmo que seu programa seja distribuído por várias máquinas, não há razão para que o uso do STL o force internamente a usá-lo na comunicação entre módulos / processos.

Na verdade, eu argumentaria que você deve separar o design de interfaces externas da implementação interna desde o início, pois as primeiras serão mais sólidas / difíceis de mudar em comparação com as usadas internamente


7

Você está perdendo o objetivo dessa pergunta.

Existem basicamente dois tipos de DLL. Você e os de outra pessoa. O "problema STL" é que você e eles podem não estar usando o mesmo compilador. Obviamente, isso não é um problema para sua própria DLL.


5

Se você criar as DLLs da mesma árvore de origem ao mesmo tempo com as mesmas opções de compilação e compilação, funcionará OK.

No entanto, a maneira "com sabor do Windows" de dividir um aplicativo em várias partes, algumas das quais reutilizáveis, são componentes COM . Eles podem ser pequenos (controles ou codecs individuais) ou grandes (o IE está disponível como um controle COM, em mshtml.dll).

carregar dinamicamente quando necessário e descarregar posteriormente

Para um aplicativo de servidor, isso provavelmente terá uma eficiência terrível ; só é realmente viável quando você tem um aplicativo que se move por várias fases por um longo período de tempo, para saber quando algo não será necessário novamente. Isso me lembra jogos do DOS usando o mecanismo de sobreposição.

Além disso, se o seu sistema de memória virtual estiver funcionando corretamente, ele cuidará disso paginando páginas de códigos não utilizadas.

pode crescer tanto que um único PC não aguenta mais

Compre um PC maior.

Não esqueça que, com a otimização correta, um laptop pode superar um cluster hadoop.

Se você realmente precisa de vários sistemas, precisa pensar muito cuidadosamente sobre os limites entre eles, pois é aí que está o custo de serialização. É aqui que você deve começar a olhar para estruturas como o MPI.


11
"só é realmente viável quando você tem um aplicativo que se move por várias fases por um longo período de tempo, para saber quando algo não será necessário novamente" - mesmo assim, é improvável que ajude muito, porque o sistema operacional irá armazene em cache os arquivos DLL, o que provavelmente acabará consumindo mais memória do que apenas incluir as funções diretamente no executável base. As sobreposições são úteis apenas em sistemas sem memória virtual ou quando o espaço de endereço virtual é o fator limitante (presumo que este aplicativo seja de 64 bits, não de 32 ...).
Jules

3
"Compre um PC maior" +1. Agora você pode adquirir sistemas com vários terabytes de RAM. Você pode contratar um da Amazon por menos do que a taxa horária de um único desenvolvedor. Quanto tempo você gastará para otimizar seu código para reduzir o uso de memória?
Jules

2
O maior problema que enfrentei com "compre um PC maior" estava relacionado à pergunta "até que ponto seu aplicativo será escalado?". Minha resposta foi: "quanto você está disposto a gastar em um teste? Porque eu espero que ele cresça tanto que alugar uma máquina adequada e configurar um teste adequadamente grande custará milhares de dólares. Nenhum de nossos clientes está nem perto ao que um PC com CPU única pode fazer. " Muitos programadores mais antigos não têm idéia realista de quanto PC cresceu; a placa de vídeo sozinha nos PCs modernos é um supercomputador para os padrões do século XX.
MSalters

Componentes COM? Nos anos 90, talvez, mas agora?
Peter Mortensen

@MSalters - certo ... qualquer pessoa com alguma dúvida sobre até que ponto um aplicativo pode ser dimensionado em um único PC deve examinar as especificações do tipo de instância Amazon EC2 x1e.32xlarge - 72 núcleos de processador físico no total na máquina, fornecendo 128 núcleos virtuais em 2,3 GHz (expansível para 3,1 GHz), potencialmente uma largura de banda de memória de 340 GB / s (dependendo do tipo de memória instalada, que não está descrita nas especificações) e 3,9TiB de RAM. Possui cache suficiente para executar a maioria dos aplicativos sem nunca tocar na RAM principal. Mesmo sem uma GPU é tão poderoso quanto um cluster de supercomputador de 500 nó a partir de 2000.
Jules

0

Estamos trabalhando aqui em um aplicativo de servidor, que está crescendo cada vez mais, mesmo no momento em que estamos pensando em dividi-lo em diferentes partes (arquivos DLL), carregando dinamicamente quando necessário e descarregando posteriormente, para poder lidar com os problemas de desempenho.

A primeira parte faz sentido (dividir o aplicativo em máquinas diferentes, por razões de desempenho).

A segunda parte (carregar e descarregar bibliotecas) não faz sentido, pois esse é um esforço extra a ser feito e não melhorará (realmente) as coisas.

O problema que você está descrevendo é melhor resolvido com máquinas de computação dedicadas, mas elas não devem funcionar com o mesmo aplicativo (principal).

A solução clássica é assim:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

Entre as máquinas front-end e de computação, você pode ter coisas extras, como balanceadores de carga e monitoramento de desempenho, e manter o processamento especializado em máquinas dedicadas é bom para otimizações de cache e taxa de transferência.

Isso de forma alguma implica carregamento / descarregamento extra de DLLs, nem nada a ver com o STL.

Ou seja, use o STL internamente, conforme necessário, e serialize seus dados entre os elementos (consulte buffers de protocolo e protocolo grpc e o tipo de problemas que eles resolvem).

Dito isto, com as informações limitadas que você forneceu, parece o clássico problema xy (como disse @Graham).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.