AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 698627
Accepted
wviana
wviana
Asked: 2022-04-10 19:53:35 +0800 CST2022-04-10 19:53:35 +0800 CST 2022-04-10 19:53:35 +0800 CST

Quem matou meu tipo? ou Como contar valores distintos de forma eficiente de uma coluna csv

  • 772

Estou fazendo algum processamento tentando obter quantas linhas diferentes em um arquivo contendo 160.353.104 linhas. Aqui está minha saída de pipeline e stderr.

$ tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 |\
  sort -T. -S1G | tqdm --total=160353104 | uniq -c | sort -hr > users

100%|████████████████████████████| 160353104/160353104 [0:15:00<00:00, 178051.54it/s]
 79%|██████████████████████      | 126822838/160353104 [1:16:28<20:13, 027636.40it/s]

zsh: done tail -n+2 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | 
zsh: killed sort -T. -S1G | 
zsh: done tqdm --total=160353104 | uniq -c | sort -hr > users

Minha linha de comando PS1 ou PS2 imprimiu os códigos de retorno de todos os processos do pipeline. ✔ 0|0|0|KILL|0|0|0O primeiro caractere é uma marca de seleção verde que significa que o último processo retornou 0 (sucesso). Outros números são códigos de retorno para cada um dos processos em pipeline, na mesma ordem. Então, notei que meu quarto comando obteve KILLstatus, este é meu comando de classificação sort -T. -S1Gdefinindo o diretório local para armazenamento temporário e buffer de até 1GiB.

A questão é, por que ele retornou KILL, isso significa que algo enviado KILL SIGNa ele? Existe uma maneira de saber "quem matou"?

Atualizações

Depois de ler Marcus Müller Answer , primeiro tentei carregar os dados no Sqlite.

Então, talvez este seja um bom momento para lhe dizer que, não, não use um fluxo de dados baseado em CSV. Um simples

sqlite3 place.sqlite

e nesse shell (supondo que seu CSV tenha uma linha de título que o SQLite possa usar para determinar as colunas) (é claro, substitua $second_column_name pelo nome dessa coluna)

.import 022_place_canvas_history.csv canvas_history --csv
SELECT $second_column_name, count($second_column_name)   FROM canvas_history 
GROUP BY $second_column_name;

Isso estava demorando muito, então deixei processando e fui fazer outras coisas. Enquanto eu pensava mais sobre este outro parágrafo de Marcus Müller Resposta :

Você só quer saber com que frequência cada valor apareceu na segunda coluna. Classificar isso antes só acontece porque sua ferramenta ( uniq -c) é ruim e precisa que as linhas sejam classificadas antes (literalmente não há uma boa razão para isso. Apenas não está implementado que ela possa conter um mapa de valores e sua frequência e aumentar isso à medida que eles aparecer).

Então eu pensei, eu posso implementar isso. Quando voltei ao computador, meu processo de importação do Sqlite parou por causa de um SSH Broken Pip, acho que ele não transmitiu dados por um longo tempo, fechou a conexão. Ok, que boa oportunidade para implementar um contador usando um dict/map/hashtable. Então eu escrevi o seguinte distinctarquivo:

#!/usr/bin/env python3
import sys

conter = dict()

# Create a key for each distinct line and increment according it shows up. 
for l in sys.stdin:
    conter[l] = conter.setdefault(l, 0) + 1 # After Update2 note: don't do this, do just `couter[l] = conter.get(l, 0) + 1`

# Print entries sorting by tuple second item ( value ), in reverse order
for e in sorted(conter.items(), key=lambda i: i[1], reverse=True):
    k, v = e
    print(f'{v}\t{k}')

Então eu usei pelo pipeline de comando follow.

tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | ./distinct > users2

Estava indo muito rápido, projeção de tqdmmenos de 30 minutos, mas quando chegou em 99% estava ficando cada vez mais lento. Este processo estava usando muita RAM, cerca de 1,7 GB. Máquina que estou trabalhando com esses dados, a máquina que tenho armazenamento suficiente, é um VPS com apenas 2GiB de RAM e ~1TiB de armazenamento. Pensei que poderia estar ficando tão lento porque o SO estava tendo que lidar com essa memória enorme, talvez fazendo alguma troca ou outras coisas. Eu esperei de qualquer maneira, quando finalmente chegou em 100% no tqdm, todos os dados foram enviados para o ./distinctprocesso, depois de alguns segundos obtive a seguinte saída:

160353105it [30:21, 88056.97it/s]                                                                                            
zsh: done       tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 | 
zsh: killed     ./distinct > users2

Desta vez, principalmente por causa do assassino sem memória, como visto na seção Marcus Müller Answer TLDR.

Então, acabei de verificar e não tenho swap ativado nesta máquina. Desativei depois de concluir sua configuração com dmcrypt e LVM, pois você pode obter mais informações nestas minhas respostas .

Então, o que estou pensando é habilitar minha partição de troca LVM e tentar executá-la novamente. Também em algum momento acho que vi tqdm usando 10GiB de RAM. Mas tenho certeza de que vi erroneamente ou btopsaída confusa, pois depois mostrou apenas 10MiB, não acho que o tqdm usaria muita memória, pois apenas conta e atualiza algumas estatísticas ao ler um novo arquivo \n.

No comentário de Stéphane Chazelas a esta questão dizem:

Os logs do sistema possivelmente lhe dirão.

Gostaria de saber mais sobre isso, devo encontrar algo no journalctl? Se for o caso, como fazer?

De qualquer forma, como Marcus Müller Answer diz, carregar o csv no Sqlite pode ser de longe a solução mais inteligente, pois permitirá operar em dados de várias maneiras e provavelmente tem alguma maneira inteligente de importar esses dados sem falta de memória.

Mas agora estou duas vezes curioso sobre como descobrir por que o processo foi morto, como quero saber sobre o meu sort -T. -S1Ge agora sobre o meu ./distinct, o último quase certo que era sobre memória. Então, como verificar os logs que dizem por que esses processos foram eliminados?

Atualização2

Então, habilitei minha partição SWAP e aceitei a sugestão de Marcus Müller deste comentário de pergunta. Usando coleções de pythons.Counter. Então meu novo código ( distinct2) fica assim:

#!/usr/bin/env python3
from collections import Counter
import sys

print(Counter(sys.stdin).most_common())

Então, eu executei o Gnu Screen para que, mesmo se eu obtivesse um pipe quebrado novamente, eu pudesse simplesmente retomar a sessão e executá-lo no seguinte pipeline:

tail -n+1 2022_place_canvas_history.csv | cut -d, -f2 | tqdm --total=160353104 --unit-scale=1 | ./distinct2 | tqdm --unit-scale=1 > users5

Isso me deu a seguinte saída:

160Mit [1:07:24, 39.6kit/s]
1.00it [7:08:56, 25.7ks/it]

Como você pode ver, levou muito mais tempo para classificar os dados do que contá-los. Uma outra coisa que você pode notar é que a saída da segunda linha do tqdm mostra apenas 1.00it, significa que tem apenas uma única linha. Então eu verifiquei o arquivo user5 usando head:

head -c 150 users5 
[('kgZoJz//JpfXgowLxOhcQlFYOCm8m6upa6Rpltcc63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n', 795), ('JMlte6XKe+nnFvxcjT0hHDYYNgiDXZVOkhr6KT60EtJAGa

Como você pode ver, ele imprimiu toda a lista de tuplas em uma única linha. Para resolver isso eu usei o bom e velho sed como segue sed 's/),/)\n/g' users5 > users6. Depois disso, verifiquei o conteúdo de users6 usando head, como segue com sua saída:

$ head users6
[('kgZoJz/...c63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n', 795)
 ('JMlte6X...0EtJAGaezxc4e/eah6JzTReWNdTH4fLueQ20A4drmfqbqsw==\n', 781)
 ('LNbGhj4...apR9YeabE3sAd3Rz1MbLFT5k14j0+grrVgqYO1/6BA/jBfQ==\n', 777)
 ('K54RRTU...NlENRfUyJTPJKBC47N/s2eh4iNdAKMKxa3gvL2XFqCc9AqQ==\n', 767)
 ('8USqGo1...1QSbQHE5GFdC2mIK/pMEC/qF1FQH912SDim3ptEFkYPrYMQ==\n', 767)
 ('DspItMb...abcd8Z1nYWWzGaFSj7UtRC0W75P7JfJ3W+4ne36EiBuo2YQ==\n', 766)
 ('6QK00ig...abcfLKMUNur4cedRmY9wX4vL6bBoV/JW/Gn6TRRZAJimeLw==\n', 765)
 ('VenbgVz...khkTwy/w5C6jodImdPn6bM8izTHI66HK17D4Bom33ZrwuGQ==\n', 758)
 ('jjtKU98...Ias+PeaHE9vWC4g7p2KJKLBdjKvo+699EgRouCbeFjWsjKA==\n', 730)
 ('VHg2OiSk...3c3cr2K8+0RW4ILyT1Bmot0bU3bOJyHRPW/w60Y5so4F1g==\n', 713)

Bom o suficiente para trabalhar mais tarde. Agora acho que devo adicionar uma atualização depois de tentar verificar quem matou meu tipo usando dmesg ou journalctl. Eu também estou querendo saber se existe uma maneira de tornar este script mais rápido. Talvez criando um threadpool, mas tenho que verificar o comportamento do dict de pythons, também pensei em outras estruturas de dados, pois a coluna que estou contando é uma string de largura fixa, talvez usando uma lista para armazenar a frequência de cada user_hash diferente. Também li a implementação python do Counter, é apenas um dict, praticamente a mesma implementação que eu tinha antes, mas em vez de usar dict.setdefaultapenas used dict[key] = dict.get(key, 0) + 1, foi um uso incorreto de setdefaultnenhuma necessidade real para esse cenário.

Atualização3

Então eu estou ficando tão fundo na toca do coelho, totalmente perdido o foco do meu objetivo. Comecei a procurar por ordenação mais rápida, talvez escrever algum C ou Rust, mas percebi que já tenho os dados que vim buscar processados. Então, estou aqui para mostrar a saída do dmesg e uma dica final sobre o script python. A dica é: pode ser melhor apenas contar usando dict ou Counter, do que classificar sua saída usando a ferramenta de classificação gnu. Provavelmente classificar classifica mais rápido que a função buitin classificada em python.

Sobre o dmesg, foi bem simples encontrar memória insuficiente, basta sudo dmesg | lesspressionar Gpara ir até o fim, do ?que pesquisar de volta, do que pesquisar por Outstring. Encontrei dois deles, um para o meu script python e outro para o meu tipo, aquele que iniciou esta pergunta. Aqui estão essas saídas:

[1306799.058724] Out of memory: Killed process 1611241 (sort) total-vm:1131024kB, anon-rss:1049016kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:2120kB oom_score_adj:0
[1306799.126218] oom_reaper: reaped process 1611241 (sort), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
[1365682.908896] Out of memory: Killed process 1611945 (python3) total-vm:1965788kB, anon-rss:1859264kB, file-rss:0kB, shmem-rss:0kB, UID:1000 pgtables:3748kB oom_score_adj:0
[1365683.113366] oom_reaper: reaped process 1611945 (python3), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

É isso, muito obrigado por ajudar até agora, espero que ajude os outros também.

pipe python
  • 2 2 respostas
  • 3667 Views

2 respostas

  • Voted
  1. Best Answer
    Marcus Müller
    2022-04-11T02:02:57+08:002022-04-11T02:02:57+08:00

    TL;DR: falta de memória-killer ou falta de espaço em disco para arquivos temporários kills sort. Recomendação: Use uma ferramenta diferente.


    Eu dei uma olhada no GNU coreutils sort.cagora¹. Seu -S 1Gapenas significa que o sortprocesso tenta alocar memória em um pedaço de 1 GB e voltará para tamanhos cada vez menores se isso não for possível.

    Depois de esgotar esse buffer, ele criará um arquivo temporário para armazenar as linhas já classificadas² e classificará o próximo bloco de entrada na memória.

    Depois que toda a entrada for consumida, sortmesclará/classificará dois dos arquivos temporários em um arquivo temporário (estilo mergesort) e mesclará sucessivamente todos os temporários até que a mesclagem produza a saída total classificada, que é então enviada para stdout.

    Isso é inteligente, porque significa que você pode classificar a entrada maior do que a memória disponível.

    Ou é inteligente em sistemas em que esses arquivos temporários não são mantidos na RAM, o que normalmente são atualmente ( /tmp/normalmente é um tmpfs, que é um sistema de arquivos somente RAM). Então, escrever esses arquivos temporários consome exatamente a RAM que você está tentando salvar e está ficando sem RAM: seu arquivo tem 160 milhões de linhas, e um rápido google sugere que são 11 GB de dados não compactados.

    Você pode "ajudar" sortcom isso alterando o diretório temporário que ele usa. Você já está fazendo isso, -T., colocando os arquivos temporários em seu diretório atual. Pode ser que você esteja ficando sem espaço lá? Ou é esse diretório atual tmpfsou similar?

    Você tem um arquivo CSV com uma quantidade média de dados (160 milhões de linhas não são tantos dados para um PC moderno). Em vez de colocar isso em um sistema destinado a lidar com tantos dados, você está tentando operá-lo com ferramentas da década de 1990 (sim, acabei de ler o sorthistórico do git), quando 16 MB de RAM pareciam bastante generosos.

    CSV é apenas o formato de dados errado para processar qualquer quantidade significativa de dados, e seu exemplo é a ilustração perfeita disso. Ferramentas ineficientes trabalhando em uma estrutura de dados ineficiente (um arquivo de texto com linhas) de maneiras ineficientes para atingir uma meta com uma abordagem ineficiente:

    Você só quer saber com que frequência cada valor apareceu na segunda coluna. Classificar isso antes só acontece porque sua ferramenta ( uniq -c) é ruim e precisa que as linhas sejam classificadas antes (literalmente não há uma boa razão para isso. Apenas não está implementado que ela possa conter um mapa de valores e sua frequência e aumentar isso à medida que eles aparecer).


    Então, talvez este seja um bom momento para lhe dizer que, não, não use um fluxo de dados baseado em CSV. Um simples

    sqlite3 place.sqlite
    

    e nesse shell (supondo que seu CSV tenha uma linha de título que o SQLite possa usar para determinar as colunas) (é claro, substitua $second_column_namepelo nome dessa coluna)

    .import 022_place_canvas_history.csv canvas_history --csv
    SELECT $second_column_name, count($second_column_name)
      FROM canvas_history
      GROUP BY $second_column_name;
    

    é provável que seja tão rápido, e bônus, você obtém um arquivo de banco de dados real place.sqlite. Você pode brincar com isso de forma muito mais flexível – por exemplo, crie uma tabela onde você extraia coordenadas e converta os tempos em registros de data e hora numéricos e, em seguida, seja muito mais rápido e flexível com o que você analisa.


    ¹ Os globais e a inconsistência sobre o que é usado quando. Eles magoam. Foi uma época diferente para os autores C. E definitivamente não é C ruim, apenas... não é o que você está acostumado em bases de código mais modernas. Obrigado a Jim Meyering e Paul Eggert por escrever e manter esta base de código!

    ² você pode tentar fazer o seguinte: ordenar um arquivo que não seja muito grande, digamos, ls.ccom 5577 linhas, e registre o número de arquivos abertos:

    strace -o /tmp/no-size.strace -e openat sort ls.c
    strace -o /tmp/s1kB-size.strace -e openat sort -S 1 ls.c
    strace -o /tmp/s100kB-size.strace -e openat sort -S 100 ls.c
    wc -l /tmp/*-size.strace
    
    • 30
  2. user232326
    2022-04-15T21:53:22+08:002022-04-15T21:53:22+08:00

    A resposta de @MarcusMüller é clara o suficiente sobre "Quem matou meu tipo?". E você confirmou o problema.

    No entanto, a segunda parte não foi muito abordada: ou Como contar valores distintos de forma eficiente de uma coluna csv . Além de tentar encontrar maneiras melhores/mais rápidas de classificar.

    Isso porque seus pipes (todos) foram baseados no uso do uniq. E uniqrequer dados classificados.

    existe alguma outra solução?

    Sim. Construa uma matriz com os dados da coluna 2 como chave e adicione 1 cada vez que esse valor for encontrado. Essa é a maneira usual em que o awk processa dados:

    $ awk -F, '{count[$2]++}END{for (i in count) {print i,count[i]}}'
    

    Isso não precisa reter todo o arquivo na memória como o sort. Mas apenas a lista de chaves (como a 'kgZoJz//JpfXgowLxOhcQlFYOCm8m6upa6Rpltcc63K6Cz0vEWJF/RYmlsaXsIQEbXrwz+Il3BkD8XZVx7YMLQ==\n'que você mostrou e um float para a contagem).

    Isso processará cada linha do arquivo uma vez e na ordem em que aparecem, sem necessidade de classificação para contar usuários únicos. Mas sim, é necessária uma classificação final para classificar as contagens.

    Assim, o tempo para processar o arquivo será proporcional ao ninvés do tempo para ordenar n*log(n)e o uso de memória será proporcional ao número de usuários 'm' (segundo campo uniq keys).

    Se a contagem média de cada usuário for 350 (assumindo que ~795 é o máximo, 1 é o mínimo e a contagem vai linearmente entre as duas contagens), então o tamanho da memória usada deve ser proporcional a 88 (tamanho de uma chave ) vezes 160353104/350 (número de chaves distintas), ou menos de 40 megabytes mais alguma sobrecarga.

    • 1

relate perguntas

  • Função Python add () de conjunto na compreensão da lista

  • Como obter a versão padrão de um aplicativo no unix

  • Como canalizar um comando bash e manter Ctrl + C funcionando?

  • Por que canalizar `mysql` para 'tail' altera o formato de saída?

  • ordem de substituição de processos `te` e `bash`

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve