tenho alguns problemas com strings e processos "long-running".
Aparentemente, o Python não devolve a memória utilizada para armazenar
strings, mesmo na versão 2.5, onde isso não deveria acontecer [1].
Testei em diversas máquinas com as versões 2.4, 2.5 e 2.6 do Python,
sempre em Linux.
Fiz um pequeno script para ilustrar em http://pastebin.com/f6a30478b .
Basta executá-lo (comentando a linha da variável "stringsize" no
começo do arquivo) e observar a memória residente através do "top" em
linux.
Nem um gc.collect faz a memória ser devolvida caso a string seja maior
que 232 caracteres. Alguém tem alguma idéia do motivo disto? Alguma
solução a ser tentada?
[1] http://evanjones.ca/python-memory.html
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
------------------------------------
,-----------------------------------------------------------.
| Antes de enviar um e-mail para o grupo leia: |
| http://www.pythonbrasil.com.br/moin.cgi/AntesDePerguntar |
| E se você é usuário do BOL lembre-se de cadastrar o |
| e-mail do grupo na lista branca do seu sistema anti-spam. |
`-----------------------------------------------------------´Links do Yahoo! Grupos
<*> Para visitar o site do seu grupo na web, acesse:
http://br.groups.yahoo.com/group/python-brasil/
<*> Para sair deste grupo, envie um e-mail para:
python-brasi...@yahoogrupos.com.br
<*> O uso que você faz do Yahoo! Grupos está sujeito aos:
http://br.yahoo.com/info/utos.html
Testei aqui (Ubuntu 9.04 x86_64) e deu pico de 320MB e terminou com
290MB nos dois casos (232 e 233). Talvez o Python libere a memória mas o
SO não pega de volta (acho que já vi alguém falando sobre isso aqui).
Mas que é feio, isso é... `:^(
--
[]s, Narcélio
Imagino que não seja bem isso. Rodei diversas vezes esse script, e o
Kernel do Linux dificilmente iria preferir fazer swap a pegar de volta
memória física disponível. Mas é exatamente isso que acontece: o que
está em memória residente simplesmente vai para a swap, em vez de ser
liberado pelo SO.
No Python-2.5 (Linux 2.6.27-gentoo-r7), usando strings de 232 o
consumo sobe até aproximadamente 213MB, e cai para 8884 KB logo em
seguida.
No mesmo ambiente, usando strings de 233, o consumo vai a pouco mais
de 213MB, mas só cai para 104MB ao fim da execução.
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
Experimente ver o que o Garbage Collector está fazendo:
===
import gc
stringsize = 233
class Foo(object):
def __init__(self):
self.bla = 's' * stringsize
def trash_memory():
tmp = []
for i in range(500000):
tmp.append(Foo())
return tmp
print "References: %d, %d, %d" % gc.get_count()
trash_memory()
gc.collect()
print "References: %d, %d, %d" % gc.get_count()
print "Fim"
raw_input()
===
A partir de 233 (ou na verdade, a partir do tamanho definido em
SMALL_REQUEST_THRESHOLD @ obmalloc.c), o Python deixa de usar o
gerenciamento de memória interno (pymalloc) e passa a usar o malloc()
da stdlib. Então na verdade, quando você usa gc.collect(), ele fez sim
o que tinha que fazer. A memória continua alocada simplesmente porque
é como o malloc() no Linux gerencia a memória: ele não faz nada
enquanto ninguém solicitar essa memória de volta.
Bom, não tem nada de feio mas é assim que funciona a gerencia de memória
em sistemas que implementam o padrão POSIX.
Quando um processo requisita uma quantidade de memória e libera parte de
seu uso (e o processo continua executando) esta quantia que foi liberada
fica em "cache", para quando o processo fizer outra requisição de
memória não ter que ser realocada a memória novamente, por outro lado
quando estiver perto de faltar memória o sistema irá desalocar o que não
estiver sendo utilizado por outros processos.
Parece simples mas economiza várias chamadas de sistema e processamento,
principalmente quando você está trabalhando com bastante freqüência na
alocação e desalocação de memória.
A[]'s
Então, a memória vai voltar, é isso que importa.
[As partes desta mensagem que não continham texto foram removidas]
[]'s
Junior Polegato
E seria normal a memória ser colocada em swap antes de ser liberada?
Pq eu tenho um processo de kernel [kswapd0] consumindo bastante
processamento se eu executar esse script vezes suficiente, e minha
swap crescendo constantemente.
Não vejo este tipo de comportamento em outros programas. Tenho um
processador de traps SNMP em C++ que roda por meses seguidos sem
consumir mais de 1MB de memória, não importa quanto processamento ele
faça ao mesmo tempo.
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
Não dá para se basear muito nesse script de teste que você criou, ele
é muito sintético: aloca milhares de objetos de uma vez, não há troca
de contexto, etc. Foi educativo para mostrar como Python passa a usar
malloc() a partir de objetos com 256 bytes, e como o comportamento do
malloc() é diferente do garbage collector da VM, mas é muito difícil
querer comparar com um aplicativo de verdade. Na esmagadora maioria
das vezes, o algoritmo de um programa qualquer faz algo mais
inteligente do que alocar milhares de objetos numa lista.
Portanto, eu não duvidaria que o sistema começasse a tentar usar swap,
ou algum outro comportamento derivado da heurística do alocador para
tentar otimizar o sistema. Isso é equivalente a um programa em C com
malloc(256) dentro de um loop 50000 vezes - o que é bem diferente de
malloc(256*50000). Experimente fazer um teste assim em C, acredito que
terá o mesmo comportamento.
Acho feio porque no fim das contas o top diz que aquele processo está
ocupando muito% de memória. Já recebi até uma reclamação de um cliente
por causa disso.
> (...)quando estiver perto de faltar memória o sistema irá desalocar o
> que não estiver sendo utilizado por outros processos.
Mas testando aqui no Ubuntu 9.04 (Linux 2.6.28) isso não aconteceu
não. A memória do programa em Python acabou indo pro swap. Já no 9.10
(Linux 2.6.31), o processo foi liberando a memória à medida em que era
necessário mesmo. Então parece que realmente o problema está em outro
lugar.
De qualquer forma, acho que seria interessante se houvesse alguma
forma de liberar "de verdade" essa memória via Python. Você sabe se
tem?
--
[]s, Narcélio
O lance é que estou tendo este problema em uma aplicação real, em um
caso de uso muito simples: listar alguns poucos milhares de objetos
pelo SQL Alchemy em um pequeno "servidor de aplicação" que escrevi.
O problema não é fazer swap enquanto está alocando ou usando a
memória. Não há dúvida que isso seria normal. O problema é dar free()
em todos os ponteiros de memória alocada e, mesmo depois de outros
processos necessitarem de memória, o SO não ter de onde pegar.
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
Em nenhum dos meus testes, a memória foi liberada durante a execução
do programa... no máximo libera uns 10% no final.
> No mesmo ambiente, usando strings de 233, o consumo vai a pouco mais
> de 213MB, mas só cai para 104MB ao fim da execução.
Testei aqui no Ubuntu Jaunty 9.04 (Linux 2.6.28) e no Karmic 9.10
(Linux 2.6.31). Usei esse programa em C pra alocar 1G de memória:
main() { int *x = calloc(1024, 1024*1024); scanf("%d", x); }
No Jaunty, a memória do seu programa em Python foi pro swap em vez de
ser liberada, mas no Karmic ela ia sendo liberada à medida que o
calloc() ia alocando. Então pode ser diferença de versão do Linux, libc
ou outra coisa no meio do caminho.
--
[]s, Narcélio
Grande, Narcélio!
Realmente, não testei em nenhuma máquina com o kernel tão novo
(2.5.31.6 é a última versão estável do kernel. Ainda não mudamos para
uma versão desta série em nenhum dos servidores a que tenho acesso, e
infelizmente estou usando uma máquina Windows como estação de
trabalho).
Obrigado pela luz no fim do tunel que vc me deu. ;)
Acho que não há mesmo uma maneira de pedir a "desalocação imediata" da
memória, já que isso é gerenciado pelo kernel do SO através das
chamadas feitas pela libc. Desta maneira, qualquer coisa que necessite
de um malloc da vida vai ter este comportamento, e não vejo nada de
errado nisso.
Mas valeu pela luz mesmo!
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
import gc
stringsize = 233
class Foo(object):
def __init__(self):
self.bla = 's' * stringsize
for j in range(100):
print "Vez", j
tmp = []
for i in range(500000):
tmp.append(Foo())
del tmp
gc.collect()
print "Fim"
print "Fim"
raw_input()
[]'s
Junior Polegato
--- Em python...@yahoogrupos.com.br, Narcélio Filho <narcelio@...> escreveu
>
> > Bom, não tem nada de feio mas é assim que funciona a gerencia de
> > memória em sistemas que implementam o padrão POSIX.
>
> Acho feio porque no fim das contas o top diz que aquele processo está
> ocupando muito% de memória. Já recebi até uma reclamação de um cliente
> por causa disso.
>
>
> > (...)quando estiver perto de faltar memória o sistema irá desalocar o
> > que não estiver sendo utilizado por outros processos.
>
> Mas testando aqui no Ubuntu 9.04 (Linux 2.6.28) isso não aconteceu
> não. A memória do programa em Python acabou indo pro swap. Já no 9.10
> (Linux 2.6.31), o processo foi liberando a memória à medida em que era
> necessário mesmo. Então parece que realmente o problema está em outro
> lugar.
>
> De qualquer forma, acho que seria interessante se houvesse alguma
> forma de liberar "de verdade" essa memória via Python. Você sabe se
> tem?
>
>
> --
> []s, Narcélio
>
Certo, então no segundo caso tudo ocorreu como deveria não? pois é assim que funciona.
No primeiro caso, certifique-se que tenha todas as correções e paths de segurança e de kernel fornecidos por sua distribuição instalados, pois não é normal o excesso de utilização de swap, apenas para casos específicos.
Mas se você insiste em liberar o cache manualmente pode jogar um "3" para o arquivo "/proc/sys/vm/drop_caches", ou em python abrir o arquivo e inserir o mesmo valor (apenas para a memória, não para o swap):
f = open("/proc/sys/vm/drop_caches","w")
f.write('3')
f.close()
Lembrando que isso é totalmente desnecessário pois o gerenciador de memória do sistema faz isso toda vez que precisar.
Outra coisa, você precisa verificar e estudar uma possível otimização para sua aplicação (a real não a de testes) e passar a utilizar bons habitos (para não atrapalhar seu cliente) de vivencia em ambientes *nix, como principalmente não executar um processo como root ao menos que seja *estritamente* necessário, definir um nível de prioridade apropriado para cada processo, faça um programa que faça poucas coisas mas faça bem feito, faça com que o seu programa possibilite a interação entre outros.
Pode também utilizar as chamadas de sistema setrlimit/getrlimit para limitar a utilização de recursos em sua aplicação, em python você pode fazer isso através do modulo "resource".
E não esqueça, verifique o kernel da versão da distribuição que deu problema e busque por uma atualização, caso a distribuição não forneça estude a hipótese de atualizar o kernel ou versão da distro.
A[]'s
No segundo caso, sim. Espero que eu tenha o mesmo resultado que o Narcélio.
> No primeiro caso, certifique-se que tenha todas as correções e paths de segurança e de kernel fornecidos por sua distribuição instalados, pois não é normal o excesso de utilização de swap, apenas para casos específicos.
O problema ocorreu em versões bem recentes do kernel do Linux. Então,
não posso jogar a culpa no administrador das máquinas por não
atualizá-lo com tanta frequencia.
> Mas se você insiste em liberar o cache manualmente [cut]
> Lembrando que isso é totalmente desnecessário pois o gerenciador de memória do sistema faz isso toda vez que precisar.
Sem dúvida. Isso realmente não é necessário.
> Outra coisa, você precisa verificar e estudar uma possível otimização para sua aplicação (a real não a de testes) e passar a utilizar bons habitos (para não atrapalhar seu cliente) de vivencia em ambientes *nix, como principalmente não executar um processo como root ao menos que seja *estritamente* necessário, definir um nível de prioridade apropriado para cada processo, faça um programa que faça poucas coisas mas faça bem feito, faça com que o seu programa possibilite a interação entre outros.
A aplicação real não precisa de otimizações para este caso. Na
verdade, meu workaround por 2 anos foi executar cada requisição em um
processo separado, o que nem de longe é o ideal - já que rodar a
requisição em uma thread resolveria com muito menos overhead de system
calls -, mas não me gerava um memory leak.
A aplicação sempre executou como usuário comum. Mas isso não impede um
DoS por alta taxa de utilização da memória. Tente, como usuário comum,
colar um texto qualquer em TODAS as células do OpenOffice, ou fazer um
fork bomb [2], por exemplo. ;)
Minha aplicação faz poucas coisas, mas uma lista grande de objetos era
o suficiente para dar dor de cabeça. Infelizmente, acho que o problema
estava longe de ser apenas boas práticas.
> Pode também utilizar as chamadas de sistema setrlimit/getrlimit para limitar a utilização de recursos em sua aplicação, em python você pode fazer isso através do modulo "resource".
>
> E não esqueça, verifique o kernel da versão da distribuição que deu problema e busque por uma atualização, caso a distribuição não forneça estude a hipótese de atualizar o kernel ou versão da distro.
Como disse, o kernel 2.6.31 entrou na árvore do portage dia 27 Sep
2009 [1]. Minha aplicação tem 2 anos. É sempre bom ter tudo
atualizado, mas acho que o último kernel há 2 anos atrás não
resolveria... ;)
Anyway, valeu pelos comentários de todos!
[1] http://sources.gentoo.org/viewcvs.py/gentoo-x86/sys-kernel/gentoo-sources/ChangeLog?view=markup
[2] http://en.wikipedia.org/wiki/Fork_bomb
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
No Ubuntu não resolveu não. Nem no Jaunty nem no Karmic. O programa em
Python continuou ocupando o mesmo tanto de memória. Não "devolveu" o
espaço para o SO.
> Pode também utilizar as chamadas de sistema setrlimit/getrlimit para
> limitar a utilização de recursos em sua aplicação, em python você pode
> fazer isso através do modulo "resource".
Não é bem isso que queremos. Quero usar toda a memória disponível em
um instante, mas quero que ela seja liberada para os outros programas,
em seguida, sem ter que terminar o meu processo atual.
> E não esqueça, verifique o kernel da versão da distribuição que deu
> problema e busque por uma atualização, caso a distribuição não forneça
> estude a hipótese de atualizar o kernel ou versão da distro.
No Karmic, o processo em Python libera a memória, mas somente se
algum outro processo precisa dela. No Jaunty, nem isso.
O que eu quero é que meu programa não apareça como o que mais está
consumindo memória no sistema. Acho isso feio. Não ligo se ele consumir
90% de memória em algum instante, mas queria liberar essa memória em
algum momento.
--
[]s, Narcélio
Se não me engano, drop_caches vale para cache/buffer de gravação em
disco e código-objeto de programas e bibliotecas [1], coisas que nem
são reportadas como "memória residente" dos programas pelo "top".
Você pode ver o quanto da sua memória está sendo usada para
cache/buffer facilmente através do "free -m", e constatar que os
vários MBs "agarrados" no processo do Python não são cache/buffer.
Acho que colocar "3" nesta opção só serve para mandar o kernel gravar
imediatamente toda alteração de dados em disco, o que só pioraria o
desempenho geral do sistema em outros aspectos.
[1] http://www.faqs.org/docs/linux_admin/buffer-cache.html
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
Acho que você ainda não entendeu como o gerenciamento de memória no
Linux funciona. O 'top' acusar um numero é uma coisa, entender como o
gerenciamento de memória funciona é outra.
Enquanto o processo está em execução, mesmo que já tenha usado a
memória que precisa, o alocador só marca aquela memória como livre
para uso novamente, e será realocada para outro *processo* assim que
necessário. O 'top' continuará acusando memória residente, porque as
páginas apontam para o último processo que as usou. Mas ele *não*
devolve a memória para o sistema operacional. Simplesmente porque a
estratégia do Linux é não mexer na memória enquanto ninguém precisa
dela, muito menos encorrer no overhead de liberar tudo novamente para
o sistema operacional, não importa o quão feio você ache que ter um
número baixo no 'free' seja.
Para entender um pouquinho mais: http://linux-mm.org/Low_On_Memory
Gente,
vamos esclarecer algumas coisas:
- O problema original não é o RES parecer alto, é o SO fazer swap
quando deveria haver memória física disponível;
- Vocês ficariam surpresos com a quantidade de grandes empresas sem
gente preparada, que apenas olha um gráfico qualquer e reclama do alto
consumo de CPU e memória exclusivamente dedicada a uma aplicação
(comprou a máquina pra que, se não for utilizá-la ao máximo? hehehe)
- O sistema operacional não desaloca imediatamente a memória, a menos
que ele queira usar, seja por solicitação de um processo, seja como
buffer/cache. Entretanto, ferramentas como o top e o free mostram
muito bem o quanto de memória é efetivamente utilizável.
- Ninguém precisa ler o memory.c ou o sched.h no kernel do linux.
Precisa apenas entender o que está sendo mostrado pelas ferramentas,
como ps, top e free.
- O RES do top é uma estimativa bem precisa do quanto da memória RAM
física está sendo usada por um programa, inclusive mostrando quanto é
shared. Nos nossos exemplos, o RES diminuia algumas vezes em que
tentávamos liberar memória, e outra vezes não.
- As páginas de memória q vão para a swap não contam mais como RES no
top, e por isso muitos acharam que a memória estava sendo liberada,
quando, na verdade, estava apenas indo para swap.
- O free diferencia o que é memória passível de reuso e o que não é: O
que está em "buffer/cache" pode ser alocado a qualquer momento para um
processo. Deste modo, a memória física disponível está na segunda
linha do free (a que diz "-buffer/cache").
- Aliás, como eu disse, buffer/cache é para arquivos em disco, não
para memória alocada para um processo.
- Há algumas mensagens deixamos de discutir Python e os nossos
caríssimos moderadores têm todo o direito de xingar a gente por poluir
tanto a lista com estas discussões sobre o uso das ferramentas *nix.
[]'s,
Thiago F. Pappacena
Good things come for those who wait...
... and for the efficient people who doesn't wait too, but faster:
Open source, XP, OO, Python, VIM, Bash and Gentoo.
A memória vai ser liberada, quando os demais programas realmente
precisarem dela ;), pq o sistema entrou no nível que o kernel
considera crítico, e passa a liberar a memória que tá em cache.
Acho que ajuda ver de um outro ponto de vista: se o kernel fosse
entrar em ação pra recuperar essa memória o tempo todo, o impacto no
desempenho do sistema seria mais negativo do que positivo, pq fazer
essa faxina tem um preço. Por isso a política do kernel é deixar as
coisas quietas e só mexer com elas quando realmente precisa.
--
Henrique Baggio
O método "Lazy" de fazer as coisas é muito bom. O PIL e o Django respiram
lazy evaluation.
[As partes desta mensagem que não continham texto foram removidas]
------------------------------------