2009-05-01 18 views
13

Mam skrypt Perl, który przechwytuje wiele danych. Istnieje kilka zmiennych łańcuchowych, które zaczynają się od niewielkich rozmiarów, ale rosną bardzo długo ze względu na wielokrotne użycie operatora kropki (konkatentacji). Czy wyhodowanie sznurka w ten sposób spowoduje wielokrotne ponowne rozmieszczenie? Jeśli tak, czy istnieje sposób wstępnego przydzielenia ciągu?Jak mogę wstępnie przydzielić ciąg w Perlu?

Odpowiedz

7

Alternatywna sugestia, z którą łatwiej będzie sobie poradzić: push łańcuchy na tablicę i join, gdy skończysz.

+7

Chociaż każdy element w tablicy tworzy SV ze wszystkimi narzutami. W ten sposób zużyjesz o wiele więcej pamięci. –

-2

Tak, wstępnie rozwijające się łańcuchy, które, jak wiesz, będą się rozwijać, to dobry pomysł.

Możesz użyć operatora "x", aby to zrobić. Na przykład, do przydzielenia 1000 obowiązuje:

$ s = „” x 1000:

+0

A następnie użyj substr na lhs przydziałów. Uuuugly. – chaos

+0

Podczas gdy utworzysz ciąg znaków zawierający 1000 spacji, kiedy wtedy powiem "$ s = 'foo'", otrzymam ciąg znaków składający się z 1000 znaków z tylko trzema pierwszymi używanymi lub da mi nowy ciąg 3-znakowy i wyrzucić swoje? (Podejrzewam, że to drugie, ale tak naprawdę nie wiem jak Perl sobie z tym poradzi.) –

+1

Jeśli zmienisz przydział, wyrzuci stary wynik (zakładając, że nie ma na nim odniesienia). Będziesz musiał zastąpić ciąg, jak powiedział Dave, aby zmodyfikować tylko jego części. ++ array-then-join – Anonymous

7

ciągi Perla są zmienne, więc dołączenie do łańcucha robi NIE ponieść karę ciąg duplikacji.

Możesz spróbować wszystkiego, co chcesz, aby znaleźć "szybszy" sposób, ale ten zapach naprawdę źle wpływa na przedwczesną optymalizację.

Na przykład podniosłem klasę, która usunęła ciężką pracę. Działa perfekcyjnie, ale jest naprawdę powolny, pomimo wszystkich swoich głupich sztuczek.

Oto wynik:

  Rate magic normal 
magic 1.72/s  -- -93% 
normal 23.9/s 1289%  -- 

Tak, to prawda, Perl jest 1200% szybciej niż myślałem, był szanowany realizacja.

Zapoznaj się z kodem i znajdź prawdziwe problemy, nie próbuj optymalizować rzeczy, które nie są znanym problemem.

#!/usr/bin/perl 

use strict; 
use warnings; 

{ 

    package MagicString; 
    use Moose; 

    has _buffer => (
     isa => 'Str', 
     is => 'rw', 
    ); 
    has _buffer_size => (
     isa  => 'Int', 
     is  => 'rw', 
     default => 0, 
    ); 
    has step_size => (
     isa  => 'Int', 
     is  => 'rw', 
     default => 32768, 
    ); 
    has _tail_pos => (
     isa  => 'Int', 
     is  => 'rw', 
     default => 0, 
    ); 

    sub BUILD { 
     my $self = shift; 
     $self->_buffer(chr(0) x $self->step_size); 
    } 

    sub value { 
     my $self = shift; 
     return substr($self->{buffer}, 0, $self->{_tail_pos}); 
    } 

    sub append { 
     my $self = shift; 
     my $value = shift; 
     my $L  = length($value); 
     if (($self->{_tail_pos} + $L) > $self->{_buffer_size }){ 
      $self->{buffer} .= (chr(0) x $self->{step_size}); 
      $self->{_buffer_size} += $self->{step_size}; 
     } 
     substr($self->{buffer}, $self->{_tail_pos}, $L, $value); 
     $self->{_tail_pos} += $L; 
    } 
    __PACKAGE__->meta->make_immutable; 
} 


use Benchmark qw(:all :hireswallclock); 

cmpthese(-10 , { 
     magic => sub{ 
      my $x = MagicString->new(); 
      for (1 .. 200001){ 
       $x->append("hello"); 
      } 
      my $y = $x->value(); 
     }, 
     normal =>sub{ 
      my $x = ''; 
      for (1 .. 200001){ 
       $x .= 'hello'; 
      } 
      my $y = $x; 
     } 
    }); 
#use Data::Dumper; 
#print Dumper(length($x->value())); 
+3

Saying Perl nie powiela łańcucha jest tylko połowę prawdy. Perl przydziela tylko kilka znaków do łańcucha znaków, więc Perl najprawdopodobniej wyhoduje pamięć zawierającą ciąg podczas dodawania. Może to spowodować skopiowanie pamięci. Ale dzieje się to w menedżerze pamięci twojego systemu, który jest bardzo szybki. Pamiętaj, że O (n) pokona O (logn) w klasie matematycznej, ale w rzeczywistym świecie liczy się stały czas algorytmu. C jest szybkie. – Schwern

+0

Rzeczywiście, O (1) nie jest zbyt dobre, jeśli O (1) jest kilka dni na jeden krok, a O (n^2) może zająć tylko kilka sekund :) Choć może być zaletą, jeśli rozmiar danych jest tak duży że podejście O (n^2) przekracza kilka tygodni, a ten zestaw danych o wielkości jest wspólny. –

15

Tak, Perl, który rozwija łańcuch, spowoduje powtarzające się ponowne przydziały. Perl przydziela trochę dodatkowych spacji do łańcuchów, ale tylko kilka bajtów. Możesz to zobaczyć używając Devel :: Peek. Ta realokacja jest bardzo szybka i często nie kopiuje pamięci. Zaufaj swojemu menedżerowi pamięci, dlatego programujesz w Perlu, a nie na C. Benchmark!

Możesz wstępnie przydzielić tablice z $#array = $num_entries i hash z keys %hash = $num_keys, ale length $string = $strlen nie działa. Oto clever trick I dug up on Perlmonks.

my $str = ""; 
vec($str, $length, 8)=0; 
$str = ""; 

Albo jeśli chcesz dostać się do XS można nazwać SvGROW().

Propozycja chaosu, aby użyć tablicy, a następnie połączyć ją razem, spowoduje użycie ponad dwukrotnie więcej pamięci. Pamięć dla tablicy. Pamięć dla każdego skalara przydzielona dla każdego elementu w tablicy. Pamięć dla ciągu przechowywanego w każdym elemencie skalarnym. Pamięć do kopii podczas dołączania. Jeśli powoduje to prostszy kod, zrób to, ale nie myśl, że oszczędzasz pamięć.

0

pójdę tablica/join sposób:

push(@array, $crunched_bit) 

A potem $str = join('', @array), jeśli nic więcej, aby mieć dostęp do wszystkich elementów do debugowania w późniejszym czasie.

+0

Spowoduje to zużycie dodatkowej pamięci, ponieważ każdy element tablicy potrzebuje nowego SV. –

3

Nie wiem dokładnie, w jaki sposób są stosowane łańcuchy Perla, ale całkiem nieźle przypuszczam, że jest to constant amortized time. Oznacza to, że nawet jeśli znajdziesz sposób na wstępną alokację szans na ciąg, to że łączny czas, który zostanie zapisany dla wszystkich użytkowników skryptu, będzie mniejszy niż czas, który upłynął na zadaniu this question w przepełnieniu stosu.

Powiązane problemy