Cara terbaik untuk melakukan iterasi melalui larik Perl

94

Manakah implementasi terbaik (dalam hal kecepatan dan penggunaan memori) untuk iterasi melalui larik Perl? Apakah ada cara yang lebih baik? ( @Arraytidak perlu disimpan).

Implementasi 1

foreach (@Array)
{
      SubRoutine($_);
}

Implementasi 2

while($Element=shift(@Array))
{
      SubRoutine($Element);
}

Implementasi 3

while(scalar(@Array) !=0)
{
      $Element=shift(@Array);
      SubRoutine($Element);
}

Implementasi 4

for my $i (0 .. $#Array)
{
      SubRoutine($Array[$i]);
}

Implementasi 5

map { SubRoutine($_) } @Array ;
Jean
sumber
2
Mengapa ada yang "terbaik"? Terutama mengingat bahwa kami tidak tahu bagaimana Anda akan mengukur satu sama lain (apakah kecepatan lebih penting daripada penggunaan memori? Adalah mapdan jawaban yang dapat diterima ?. dll.)
Max Lybbert
2
Dua dari tiga yang Anda posting akan membuat saya berkata "WTH ?!" kecuali ada konteks sekitarnya tambahan untuk menjadikannya alternatif yang masuk akal. Bagaimanapun, pertanyaan ini berada pada level “ Apa cara terbaik untuk menambahkan dua angka? ” Seringkali, hanya ada satu cara. Lalu, ada situasi di mana Anda membutuhkan cara yang berbeda. Voting untuk ditutup.
Sinan Ünür
4
@ SinanÜnür Saya berempati dengan pendapat Anda (bahwa hanya ada satu cara untuk menambahkan dua angka), tetapi analoginya tidak cukup kuat untuk digunakan secara meremehkan. Jelas, ada lebih dari satu cara, dan OP ingin memahami mana ide yang bagus dan mana yang tidak.
CodeClown42
2
Bab 24 dari edisi ketiga Programming Perl memiliki bagian tentang efisiensi yang merupakan bacaan yang baik. Ini membahas berbagai jenis efisiensi seperti waktu, programmer, pengelola. Bagian ini dimulai dengan pernyataan "Perhatikan bahwa pengoptimalan waktu terkadang dapat merugikan Anda dalam hal ruang atau efisiensi pemrogram (ditunjukkan dengan petunjuk yang bertentangan di bawah). Itulah jeda."
1
Satu 1 cara untuk menambahkan dua angka? Tidak jika Anda melihat ke dalam panggilan tingkat yang lebih rendah / implementasi .... berpikir carry LookAhead, carry save penambah dll
workwise

Jawaban:

76
  • Dalam hal kecepatan: # 1 dan # 4, tetapi tidak banyak dalam banyak kasus.

    Anda dapat menulis patokan untuk mengonfirmasi, tetapi saya curiga Anda akan menemukan # 1 dan # 4 menjadi sedikit lebih cepat karena pekerjaan iterasi dilakukan di C, bukan di Perl, dan tidak ada penyalinan elemen array yang tidak perlu terjadi. ( $_adalah alias elemen di # 1, tetapi # 2 dan # 3 sebenarnya menyalin skalar dari array.)

    # 5 mungkin serupa.

  • Dalam hal penggunaan memori: Semuanya sama kecuali # 5.

    for (@a)dilapisi khusus untuk menghindari perataan array. Perulangan melakukan iterasi pada indeks array.

  • Dalam hal keterbacaan: # 1.

  • Dalam hal fleksibilitas: # 1 / # 4 dan # 5.

    # 2 tidak mendukung elemen yang salah. # 2 dan # 3 bersifat merusak.

ikegami
sumber
3
Wow, Anda menambahkan banyak sekali informasi dalam kalimat pendek dan sederhana.
jaypal singh
1
# 2 baik ketika Anda melakukan antrian (misalnya pencarian luas-pertama):my @todo = $root; while (@todo) { my $node = shift; ...; push @todo, ...; ...; }
ikegami
Bukankah implementasi 4 membuat larik indeks perantara, yang mungkin memperkenalkan sejumlah besar memori untuk digunakan? Jika demikian, sepertinya orang tidak boleh menggunakan pendekatan itu. stackoverflow.com/questions/6440723/… rt.cpan.org/Public/Bug/Display.html?id=115863
Thorsten Schöning
@ikegami Sesuai dengan gaya juara Anda - jawaban yang bagus :)
skeetastax
26

Jika Anda hanya peduli tentang elemen @Array, gunakan:

for my $el (@Array) {
# ...
}

atau

Jika indeks penting, gunakan:

for my $i (0 .. $#Array) {
# ...
}

Atau, pada perl 5.12.1, Anda dapat menggunakan:

while (my ($i, $el) = each @Array) {
# ...
}

Jika Anda membutuhkan elemen dan indeksnya di badan loop, Saya harapkan menggunakan each menjadi yang tercepat, tapi kemudianAnda akan melepaskan kompatibilitas dengan pra-5.12.1 perls.

Beberapa pola lain selain ini mungkin sesuai dalam keadaan tertentu.

Sinan Ünür
sumber
Saya berharap eachmenjadi yang paling lambat. Itu melakukan semua pekerjaan yang lain tanpa alias, ditambah tugas daftar, dua salinan skalar dan dua pembersihan skalar.
ikegami
Dan, dengan kemampuan pengukuran terbaik saya, Anda benar. Sekitar 45% lebih cepat dengan melakukan foriterasi pada indeks array, dan 20% lebih cepat saat melakukan iterasi pada indeks referensi array (saya melakukan akses $array->[$i]di dalam body), dibandingkan menggunakan eachdalam hubungannya dengan while.
Sinan Ünür
3

IMO, implementasi # 1 adalah tipikal dan pendek dan idiomatis karena Perl mengalahkan yang lain untuk itu saja. Tolok ukur dari tiga pilihan mungkin menawarkan Anda wawasan tentang kecepatan, setidaknya.

JRFerguson
sumber
2

1 secara substansial berbeda dari 2 dan 3, karena meninggalkan array dengan bijaksana, sedangkan dua lainnya membiarkannya kosong.

Saya akan mengatakan # 3 cukup aneh dan mungkin kurang efisien, jadi lupakan itu.

Yang membuat Anda mendapat # 1 dan # 2, dan mereka tidak melakukan hal yang sama, jadi yang satu tidak bisa "lebih baik" dari yang lain. Jika arraynya besar dan Anda tidak perlu menyimpannya, umumnya scope akan menghadapinya ( tapi lihat CATATAN ), jadi secara umum , # 1 masih merupakan metode yang paling jelas dan paling sederhana. Mematikan setiap elemen tidak akan mempercepat apa pun. Bahkan jika ada kebutuhan untuk membebaskan array dari referensi, saya hanya akan pergi:

undef @Array;

ketika selesai.

  • CATATAN : Subrutin yang berisi ruang lingkup larik sebenarnya menyimpan larik dan menggunakan kembali ruang di lain waktu. Secara umum , itu seharusnya bagus (lihat komentar).
CodeClown42
sumber
@Array = ();tidak membebaskan array yang mendasarinya. Bahkan tidak keluar dari ruang lingkup akan melakukan itu. Jika Anda ingin membebaskan array yang mendasarinya, Anda harus menggunakan undef @Array;.
ikegami
2
Demo; perl -MDevel::Peek -e'my @a; Dump(\@a,1); @a=qw( a b c ); Dump(\@a,1); @a=(); Dump(\@a,1); undef @a; Dump(\@a,1);' 2>&1 | grep ARRAY
ikegami
APA??? Saya pikir seluruh poin GC pernah menjadi ref count == 0, memori yang terlibat menjadi dapat didaur ulang.
CodeClown42
@ikegami: Saya melihat hal tentang ()vs undef, tetapi jika keluar dari ruang lingkup tidak melepaskan memori yang digunakan oleh array lokal ke ruang lingkup itu, bukankah itu membuat perl bencana bocor? Itu tidak benar.
CodeClown42
Mereka juga tidak bocor. Sub masih memilikinya, dan akan menggunakannya kembali saat sub dipanggil lagi. Dioptimalkan untuk kecepatan.
ikegami
1

Dalam satu baris untuk mencetak elemen atau larik.

cetak $ _ untuk (@array);

CATATAN: ingat bahwa $ _ secara internal merujuk ke elemen @array dalam perulangan. Setiap perubahan yang dibuat di $ _ akan terlihat di @array; ex.

my @array = qw( 1 2 3 );
for (@array) {
        $_ = $_ *2 ;
}
print "@array";

keluaran: 2 4 6

Sandeep_black
sumber
0

Cara terbaik untuk memutuskan pertanyaan seperti ini untuk membandingkannya:

use strict;
use warnings;
use Benchmark qw(:all);

our @input_array = (0..1000);

my $a = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    foreach my $element (@array) {
       die unless $index == $element;
       $index++;
    }
};

my $b = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (defined(my $element = shift @array)) {
       die unless $index == $element;
       $index++;
    }
};

my $c = sub {
    my @array = @{[ @input_array ]};
    my $index = 0;
    while (scalar(@array) !=0) {
       my $element = shift(@array);
       die unless $index == $element;
       $index++;
    }
};

my $d = sub {
    my @array = @{[ @input_array ]};
    foreach my $index (0.. $#array) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $e = sub {
    my @array = @{[ @input_array ]};
    for (my $index = 0; $index <= $#array; $index++) {
       my $element = $array[$index];
       die unless $index == $element;
    }
};

my $f = sub {
    my @array = @{[ @input_array ]};
    while (my ($index, $element) = each @array) {
       die unless $index == $element;
    }
};

my $count;
timethese($count, {
   '1' => $a,
   '2' => $b,
   '3' => $c,
   '4' => $d,
   '5' => $e,
   '6' => $f,
});

Dan menjalankan ini pada perl 5, versi 24, subversi 1 (v5.24.1) dibangun untuk x86_64-linux-gnu-thread-multi

Saya mendapat:

Benchmark: running 1, 2, 3, 4, 5, 6 for at least 3 CPU seconds...
         1:  3 wallclock secs ( 3.16 usr +  0.00 sys =  3.16 CPU) @ 12560.13/s (n=39690)
         2:  3 wallclock secs ( 3.18 usr +  0.00 sys =  3.18 CPU) @ 7828.30/s (n=24894)
         3:  3 wallclock secs ( 3.23 usr +  0.00 sys =  3.23 CPU) @ 6763.47/s (n=21846)
         4:  4 wallclock secs ( 3.15 usr +  0.00 sys =  3.15 CPU) @ 9596.83/s (n=30230)
         5:  4 wallclock secs ( 3.20 usr +  0.00 sys =  3.20 CPU) @ 6826.88/s (n=21846)
         6:  3 wallclock secs ( 3.12 usr +  0.00 sys =  3.12 CPU) @ 5653.53/s (n=17639)

Jadi 'foreach (@Array)' kira-kira dua kali lebih cepat dari yang lain. Semua yang lain sangat mirip.

@ikegami juga menunjukkan bahwa ada beberapa perbedaan dalam implimentasi ini selain kecepatan.

G. Allen Morris III
sumber
1
Perbandingannya $index < $#arraysebenarnya harus $index <= $#arraykarena $#arraybukan panjang array tetapi indeks terakhirnya.
josch