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 / coding / Perguntas / 77840739
Accepted
con
con
Asked: 2024-01-18 23:54:16 +0800 CST2024-01-18 23:54:16 +0800 CST 2024-01-18 23:54:16 +0800 CST

Perl: Diminuir o comprimento da string aumenta o uso de memória na matriz de strings

  • 772

Estou lendo um arquivo enorme para armazenar dados em um hash muito grande. Estou tentando manter o uso de RAM o menor possível.

Eu tenho um MWE que mostra um comportamento estranho em Perl:

#!/usr/bin/env perl

use 5.038;
use warnings FATAL => 'all';
use autodie ':default';
use DDP {output => 'STDOUT', array_max => 10, show_memsize => 1}; # pretty print with "p"

my @l = split /\s+/, 'OC   Pimascovirales; Iridoviridae; Betairidovirinae; Iridovirus.';
p @l;
$_ =~ s/[\.;]$// foreach @l; # single line keeps code shorter
p @l;

que tem saída:

[
    [0] "OC",
    [1] "Pimascovirales;",
    [2] "Iridoviridae;",
    [3] "Betairidovirinae;",
    [4] "Iridovirus."
] (356B)
[
    [0] "OC",
    [1] "Pimascovirales",
    [2] "Iridoviridae",
    [3] "Betairidovirinae",
    [4] "Iridovirus"
] (400B)

Embora este exemplo seja trivialmente pequeno, farei isso muitas vezes, portanto, o gerenciamento de RAM é importante.

como diminuir o comprimento da string aumentou o tamanho da RAM dessa matriz de 356B para 400B?

Se possível, posso evitar aumentos como esse?

perl
  • 3 3 respostas
  • 98 Views

3 respostas

  • Voted
  1. Best Answer
    choroba
    2024-01-19T00:17:29+08:002024-01-19T00:17:29+08:00

    É uma consequência do Copy on Write. Em outras palavras, até você começar a alterar as strings, Perl apenas sabe onde procurar na string original para encontrá-las, mas não as copia.

    Use Devel::Peek para ver:

    use Devel::Peek qw{ Dump };
    Dump @l;
    

    Antes da substituição:

    SV = PVAV(0x5565ec854f20) at 0x5565ec8d8dc8
      REFCNT = 1
      FLAGS = ()
      ARRAY = 0x5565ecde8350
      FILL = 400
      MAX = 473
      FLAGS = (REAL)
      Elt No. 0
      SV = PV(0x5565ec853de0) at 0x5565ec853220
        REFCNT = 1
        FLAGS = (POK,pPOK)
        PV = 0x5565ec8d6dd0 "OC"\0
        CUR = 2
        LEN = 10
      Elt No. 1
      SV = PV(0x5565ec853eb0) at 0x5565ec853418
        REFCNT = 1
        FLAGS = (POK,IsCOW,pPOK)
        PV = 0x5565eca6b9d0 "Pimascovirales;"\0
        CUR = 15
        LEN = 17
        COW_REFCNT = 0
      Elt No. 2
    ...
    

    Depois:

    SV = PVAV(0x5565ec854f20) at 0x5565ec8d8dc8
      REFCNT = 1
      FLAGS = ()
      ARRAY = 0x5565ecde8350
      FILL = 400
      MAX = 473
      FLAGS = (REAL)
      Elt No. 0
      SV = PV(0x5565ec853de0) at 0x5565ec853220
        REFCNT = 1
        FLAGS = (POK,pPOK)
        PV = 0x5565ec8d6dd0 "OC"\0
        CUR = 2
        LEN = 10
      Elt No. 1
      SV = PV(0x5565ec853eb0) at 0x5565ec853418
        REFCNT = 1
        FLAGS = (POK,pPOK)
        PV = 0x5565ecdf1030 "Pimascovirales"\0
        CUR = 14
        LEN = 32
      Elt No. 2
    ...
    

    Todos os elementos (exceto o primeiro ) tinham originalmente a IsCOWbandeira.

    • 5
  2. ikegami
    2024-01-19T02:37:22+08:002024-01-19T02:37:22+08:00

    Por que a estrutura de dados pós-modificação do OP usa mais memória?

    • s///cria novos escalares em vez de modificar a string no local, e
    • s///acontece que cria novos escalares com buffers de string maiores do que splitno exemplo do OP.

    Eu explico ambos com mais detalhes abaixo, mas é realmente isso.


    Por que não s///modifica a string no local?

    Pelo menos nas formas que importam para esta postagem, os dois trechos a seguir são equivalentes desde 5.20:

    $_ =~ s/[\.;]$//
    
    $_ = $_ =~ s/[\.;]$//r
    

    O ponto chave aqui é que os escalares existentes estão sendo substituídos por novos.

    Mas nem sempre foi assim. Era uma vez, Perl simplesmente reduzia o tamanho usado do buffer ao removê-lo de seu final using s///, resultando em nenhuma memória adicional usada. Isto é demonstrado pelo seguinte programa simples:

    $ 5.18t/bin/perl -MDevel::Peek -e'$_ = "abc"; $_ .= "d"; Dump($_); s/d\z//; Dump($_);'
    SV = PV(0x55da3c065ce0) at 0x55da3c0a4830
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x55da3c08e7c0 "abcd"\0
      CUR = 4
      LEN = 16
    SV = PV(0x55da3c065ce0) at 0x55da3c0a4830
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x55da3c08e7c0 "abc"\0
      CUR = 3
      LEN = 16
    

    Observe que o buffer de string está 0x55da3c08e7c0antes e depois. Somente a quantidade usada do buffer ( CUR) foi alterada.

    Avance para 5.20 e você terá algo diferente.

    $ 5.20t/bin/perl -MDevel::Peek -e'$_ = "abc"; $_ .= "d"; Dump($_); s/d\z//; Dump($_);'
    SV = PV(0x55ee06d20d20) at 0x55ee06d61ee0
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x55ee06d4d530 "abcd"\0
      CUR = 4
      LEN = 10
    SV = PV(0x55ee06d20d20) at 0x55ee06d61ee0
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x55ee06d3acf0 "abc"\0
      CUR = 3
      LEN = 10
    

    Observe que o buffer de string foi movido de 0x55ee06d4d530para 0x55ee06d3acf0.

    Uma cópia do buffer está sendo feita, resultando no uso adicional de memória pelo menos temporário.

    O que mudou é que o 5.20 introduziu o mecanismo copy-on-write ("COW"). Graças a esse mecanismo, cópias de escalares contendo strings não copiam mais o buffer de strings. Somente o ponteiro para o buffer é copiado e o buffer de string é sinalizado como compartilhado com o IsCOWsinalizador.

    Quando você executa uma correspondência de regex, é feita uma cópia do escalar que está sendo correspondido. Esta cópia é anexada através de magia a todos os vars de captura aplicáveis ​​( $1, etc), incluindo $&e similares. Mas graças ao novo mecanismo COW, nenhuma cópia é feita do buffer de string. Tanto o original quanto a cópia compartilham o mesmo buffer de string até que um seja alterado.

    No nosso cenário, um deles é alterado um momento depois, já que estamos realizando uma substituição no local. $_portanto, obtém um novo buffer para armazenar o valor modificado. É isso que nos dá a equivalência que descrevi no início desta resposta.

    Podemos ver o mecanismo COW em ação se evitarmos alterar o escalar original.

    $ 5.20t/bin/perl -MDevel::Peek -e'$_ = "abc"; $_ .= "d"; Dump($_); my $y = s/d\z//r; Dump($_); Dump($y);'
    SV = PV(0x55b9dacc8d20) at 0x55b9dad09ee0
      REFCNT = 1
      FLAGS = (POK,pPOK)
      PV = 0x55b9dacf5790 "abcd"\0
      CUR = 4
      LEN = 10
    SV = PV(0x55b9dacc8d20) at 0x55b9dad09ee0
      REFCNT = 1
      FLAGS = (POK,IsCOW,pPOK)
      PV = 0x55b9dacf5790 "abcd"\0
      CUR = 4
      LEN = 10
      COW_REFCNT = 1
    SV = PV(0x55b9dacc8e50) at 0x55b9dacf4548
      REFCNT = 1
      FLAGS = (PADMY,POK,pPOK)
      PV = 0x55b9dacf5be0 "abc"\0
      CUR = 3
      LEN = 10
    

    Observe que o escalar tem o IsCOWsinalizador definido após a correspondência da regex. Seu buffer( 0x55b9dacf5790) está sendo compartilhado com um escalar associado a $&.

    O uso do COW para variáveis ​​de captura tornou o código mais limpo, corrigiu bugs e melhorou o desempenho.

    A memória usada pela cópia da string correspondente será liberada na próxima vez que você fizer uma correspondência no mesmo escopo, para que a memória "perdida" nesta cópia não seja acumulada. Isso significa que a memória perdida não está relacionada ao comprimento do @lexemplo do OP.


    Por que s///cria escalares com buffers de string maiores que split?

    Porque s///"constrói" a string, onde splitconhece as strings que deseja retornar antes de criar os escalares para elas.

    Perl favorece a velocidade em detrimento da memória (geralmente de quantidades substanciais). Uma maneira de fazer isso é alocando buffers de string maiores que o necessário. Neste caso, os novos escalares estão sendo criados com buffers maiores.

    splitnão "construi" a string. Ele sabe o comprimento exato da string que deseja colocar no escalar quando cria o escalar.

    s///rnão sabe o comprimento final da string que retornará antecipadamente. Ele "constrói" anexando a um escalar que criou. À medida que o buffer de string do escalar fica cheio, ele sofre expansões de tamanho.

    Essa diferença na forma como a string é construída explica as diferenças no tamanho dos buffers.

    • splitaloca escalares com buffers de tamanho 16, 17, 16, 19, 16 no exemplo do OP.
    • s///aloca escalares com buffers de tamanho 16, 40, 16, 40, 16 no exemplo do OP.
    • 4
  3. hobbs
    2024-01-19T04:44:22+08:002024-01-19T04:44:22+08:00

    Para responder à segunda parte da sua pergunta: você pode usar split /[;.\s]+/e o array resultante será 354B e conterá os valores desejados sem necessidade de pós-processamento (e sem cópia de string).

    Isso pressupõe que não haja ponto e vírgula ou pontos em nenhum lugar, exceto no final das palavras; se isso não for verdade, você pode usar o menos bonito (e provavelmente um pouco mais lento) split /(?:[;.](?=\s))?\s+/.

    • 1

relate perguntas

  • Como posso usar Perl, cada um com uma lista?

  • erro perl ao tentar inserir uma linha no final de um arquivo [duplicado]

  • Não é possível localizar o módulo perl

  • Como adicionar endereço de email a string em Perl? ( "@" personagem )

  • Como pular o resto no perl como o próximo no awk?

Sidebar

Stats

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

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

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