Borular, kaymalar veya parametre genişlemesi daha verimli midir?


26

Bir boşluktan ayrılmış sözcükler listesinde birbirinden uzakta tutarlı sayıda değer olan belirli değerleri yinelemenin en etkili yolunu bulmaya çalışıyorum (dizi kullanmak istemiyorum). Örneğin,

list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"

Bu yüzden sadece liste boyunca yinelemek ve sadece 1,5,6,9 ve 15 erişebilmek istiyorum.

EDIT: Listeden almaya çalıştığım değerlerin listenin geri kalanından farklı olması gerekmediğini açıkça belirtmeliydim. Onları özel kılan sadece listedeki pozisyonlarıdır (Bu durumda, pozisyon 1,4,7 ...). Yani liste olabilirdi1 2 3 5 9 8 6 90 84 9 3 2 15 75 55ama yine de aynı numaraları istiyorum. Ve ayrıca, listenin uzunluğunu bilmediğimi varsayarak bunu yapabilmek istiyorum.

Şimdiye kadar düşündüğüm yöntemler:

Yöntem 1

set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
    if [ "${@:count:1}" -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
done

Yöntem 2

set list
found=false
find=9
while [ $# ne 0 ]; do
    if [ $1 -eq $find ]; then
    found=true
    break
    fi
    shift 3
done

Yöntem 3 Piping bu boru en kötü seçenek yapar emin değilim, ama meraktan set, kullanmayan bir yöntem bulmaya çalışıyordum.

found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
    if [ $num -eq $find ]; then
    found=true
    break
    fi
    count=`expr $count + 3`
    num=`echo $list | cut -d ' ' -f$count`
done

Peki en verimli ne olurdu, yoksa daha basit bir metodu mu özlüyorum?


10
Verimlilik önemli bir endişe ise, ilk başta bir kabuk betiği kullanmazdım. Listeniz ne kadar büyük bir fark yaratıyor?
Barmar


2
Sorununuzun gerçek durumları hakkında istatistikler yapmadan hiçbir şey bilmezsiniz. Bu, "awk cinsinden programlama" vb. İle karşılaştırmayı içerir. İstatistikler çok pahalıysa, o zaman verimliliği aramak muhtemelen değmez.
David Tonhofer

2
Levi, tanımındaki "etkili" yol tam olarak nedir? Yinelemenin daha hızlı bir yolunu bulmak ister misin?
Sergiy Kolodyazhnyy

Yanıtlar:


18

İle oldukça basit awk. Bu size herhangi bir uzunluktaki girdi için dördüncü alanın değerini verecektir:

$ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
1 5 6 9 15

Bu awk, NF(kayıttaki alanların sayısı) gibi yerleşik değişkenlerden yararlanma forve zamanın ne kadar olacağını bilmenize gerek kalmadan size istediğiniz alanları vermek için alanlar boyunca yineleme yapmak için basit bir döngü uygulamaktır.

Ya da gerçekten sadece örneğinizde belirtildiği gibi bu belirli alanları istiyorsanız:

$ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
1 5 6 9 15

Verimlilik hakkındaki soruya gelince, en basit yol bunu veya diğer yöntemlerinizi test etmek ve timene kadar sürdüğünü göstermek için kullanmak olacaktır; Ayrıca stracesistemin nasıl akış dediğini görmek gibi araçlar kullanabilirsiniz . Benzeyen kullanımı time:

$ time ./script.sh

real    0m0.025s
user    0m0.004s
sys     0m0.008s

Hangisinin zaman açısından en verimli olduğunu görmek için bu çıktıyı çeşitli yöntemler arasında karşılaştırabilirsiniz; diğer araçlar, diğer verimlilik ölçümleri için kullanılabilir.


1
İyi nokta, @MichaelHomer; "Hangi yöntemin en verimli olduğunu nasıl belirleyebilirim" sorusunu ele alan bir kenara ekledim .
DopeGhoti

2
@LeviUzodike echovs ile ilgili olarak <<<, "özdeş" çok güçlü bir kelimedir. stuff <<< "$list"Neredeyse aynı olduğunu söyleyebiliriz printf "%s\n" "$list" | stuff. echoVs ile ilgili olarak printf, sizi bu cevaba yönlendiririm
JoL

5
@ DopeGhoti Aslında öyle. <<<sonunda yeni bir satır ekler. Bu $()yeni bir çizgiyi sondan nasıl kaldırdığına benzer . Bunun nedeni, satırların yeni satırlarla sonlandırılmasıdır. <<<bir ifadeyi bir satır olarak besler, bu nedenle yeni bir satırla sonlandırılmalıdır. "$()"satırları alır ve bir argüman olarak onları sağlar, bu nedenle sonlanan yeni satırı kaldırarak dönüştürmek mantıklı olur.
JoL,

3
@LeviUzodike awk çok az takdir edilen bir araçtır. Her türlü görünüşte karmaşık problemi çözmeyi kolaylaştıracak. Özellikle sed gibi bir şey için karmaşık bir regex yazmaya çalıştığınızda, yordamda awk yazarak sık sık saatlerce tasarruf edebilirsiniz. Bunu öğrenmek, büyük kâr payları sağlayacak.
Joe,

1
@LeviUzodike: Evet awk, başlaması gereken bağımsız bir ikili dosyadır . Perl veya özellikle Python aksine, awk tercüman hızlı açılıyor (epeyce sistem çağrıları yapma hala her zamanki dinamik bağlayıcı havai fakat awk yalnızca kullanır libc / libm ve libdl. Örn kullanım straceawk başlangıçta sistem-çağrıları kontrol etmek) . Birçok kabuk (bash gibi) oldukça yavaştır, bu nedenle bir awk işlemini başlatmak, küçük ish listesi büyüklükleri için bile kabuk yerleşiklerine sahip bir listede belirteçler üzerinde döngü yapmaktan daha hızlı olabilir. Ve bazen bir yazabilirsiniz #!/usr/bin/awkkomut yerine a #!/bin/shsenaryo.
Peter Cordes

35
  • Yazılım optimizasyonunun ilk kuralı: Yapmayın .

    Programın hızının bir sorun olduğunu öğrenene kadar, bunun ne kadar hızlı olduğunu düşünmenize gerek yok. Listeniz bu uzunlukla ilgiliyse veya sadece ~ 100-1000 ürün uzunsa, muhtemelen ne kadar sürdüğünü bile fark etmezsiniz. Optimizasyon hakkında düşünmek için farkın ne olacağından daha fazla zaman harcadığınız bir şans var.

  • İkinci kural: Ölçün .

    Bunu öğrenmenin kesin yolu ve sisteminize cevap veren yol budur. Özellikle de mermilerde çok fazla var ve hepsi aynı değil. Bir kabuk için bir cevap sizin için geçerli olmayabilir.

    Daha büyük programlarda, profil oluşturma da burada devam eder. En yavaş kısmı, düşündüğün gibi olmayabilir.

  • Üçüncüsü, kabuk komut dosyası optimizasyonunun ilk kuralı: Kabuğu kullanmayın .

    Evet gerçekten. Birçok merminin hızlı olması sağlanmamıştır (çünkü harici programları başlatmak zorunda olmak zorunda değildir) ve kaynak kodun satırlarını her seferinde tekrar ayrıştırabilirler.

    Bunun yerine awk veya Perl gibi bir şey kullanın. Yaptığım önemsiz bir mikro kıyaslamada, awkbasit bir döngü (G / Ç olmadan) çalıştırma konusunda genel kabuklulardan onlarca kat daha hızlıydı.

    Ancak, kabuğu kullanırsanız, dış komutlar yerine kabuğun yerleşik işlevlerini kullanın. Burada, exprsistemimde bulduğum herhangi bir mermide yerleşik olmayan, ancak standart aritmetik genişleme ile değiştirilebilecek şekilde kullanıyorsunuz. Örneğin , artış i=$((i+1))yerine . Son örnekte kullanımınız, standart parametre genişletmeleriyle de değiştirilebilir.i=$(expr $i + 1)icut

    Ayrıca bakınız: Metni işlemek için neden bir kabuk döngüsü kullanıyorsunuz, uygulamanın kötü olduğu düşünülüyor?

Sorunuz için 1. ve 2. adımlar uygulanmalıdır.


12
# 0, genişletmelerinizi
isteyin

8
Bu awkilmeklerin kabuklu ilmeklerden daha iyi ya da daha kötü olması gerekmez. Kabuğun komutları çalıştırmada ve girdi ve çıktıları işlemlere ve işlemlerden yönetmede gerçekten iyi olduğu ve açıkçası her şeyden çok tuhaf olduğu; araçları gibi süre awkolan fantastik ne gibi kabukları ve araçlar o en çünkü metin verilerin işlenmesi de awkilk etapta (sırasıyla) için yapılır.
DopeGhoti

2
@ DopeGhoti, kabukları olsa da, nesnel olarak yavaş görünüyor. Bazı çok basit döngüler içinde 25 kat daha yavaş> görünmektedir ederken dashile daha gawkve dasholdu en hızlı ben test kabuk ...
ilkkachu

1
@Joe, bu :) dashve busyboxdesteklemiyor (( .. ))- bence standart olmayan bir uzantı. açıkça söyleyebildiğim kadarıyla ya da güvenli olanlar ++da açıkça istenmediği belirtiliyor . i=$((i+1)): $(( i += 1))
ilkkachu

1
Re "daha fazla zaman düşünme" : Bu önemli bir faktörü ihmal eder. Ne sıklıkla çalışır ve kaç kullanıcı için? Bir program 1 saniye harcarsa ve programcı tarafından 30 dakika düşünerek düzeltilebilecekse, bir kez çalıştırması gereken bir kullanıcı olması zaman kaybı olabilir. Öte yandan, bir milyon kullanıcı varsa, bu bir milyon saniye veya 11 günlük kullanıcı süresidir. Kod bir milyon kullanıcısını bir dakika boşa harcarsa, bu yaklaşık 2 yıllık kullanıcı süresi demektir.
agc

13

Ben sadece bu cevapta bazı genel tavsiyeler vereceğim, ölçütler değil. Karşılaştırmalar, performansla ilgili soruları güvenilir bir şekilde cevaplamanın tek yoludur. Ancak, ne kadar veri işlediğinizi ve bu işlemi ne sıklıkta gerçekleştirdiğinizi söylemediğinizden, yararlı bir kıyaslama yapmanın bir yolu yoktur. 10 ürün için daha verimli ve 1000000 ürün için daha verimli olan genellikle aynı değildir.

Genel bir kural olarak, dış komutları çağırmak, saf kabuk kodu bir döngü içermediği sürece, saf kabuk yapılarıyla bir şey yapmaktan daha pahalıdır. Öte yandan, büyük bir dize veya büyük miktarda dize üzerinde yinelenen bir kabuk halkasının, özel amaçlı bir aracın çağrılmasından daha yavaş olması muhtemeldir. Örneğin, döngü çağırmanız cutuygulamada farkedilir derecede yavaş olabilir, ancak her şeyi tek bir cutbaşlatma ile yapmanın bir yolunu bulursanız , kabuğunda dize işlemiyle aynı şeyi yapmaktan daha hızlı olması muhtemeldir.

Kesme noktasının sistemler arasında çok fazla değişiklik gösterebileceğini unutmayın. Çekirdeğe, çekirdeğin zamanlayıcısının nasıl yapılandırıldığına, harici çalıştırılabilir dosyaları içeren dosya sistemine, şu anda ne kadar CPU - bellek baskısı olduğuna ve diğer birçok faktöre bağlı olabilir.

exprPerformansla ilgili endişeleriniz varsa, aritmetik yapmak için arama yapmayın . Aslında, hiç expraritmetik yapmak için arama . Kabuklar yerleşik aritmetiktir ve bu da çağırmaktan daha net ve hızlıdır expr.

Bash kullanıyor görünüyorsunuz, çünkü sh içinde bulunmayan bash yapıları kullanıyorsunuz. Öyleyse neden dünyada bir dizi kullanmıyorsun? Bir dizi en doğal çözümdür ve bunun da en hızlı olması muhtemeldir. Dizi indekslerinin 0'dan başladığını unutmayın.

list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
for ((count = 0; count += 3; count < ${#list[@]})); do
  echo "${list[$count]}"
done

Eğer sh kullanıyorsanız betiğiniz daha hızlı olabilir, eğer sisteminizde shbash yerine tire veya ksh varsa . Eğer sh kullanırsanız, dizileri adlandırmazsınız, ancak diziyi yine de ayarlayabileceğiniz konumsal parametrelerden birini alırsınız set. Bir elemana çalışma zamanına kadar bilinmeyen bir pozisyonda erişmek için kullanmanız gerekir eval(doğru şekilde alıntı yapmak için dikkatli olun!).

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
count=1
while [ $count -le $# ]; do
  eval "value=\${$count}"
  echo "$value"
  count=$((count+1))
done

Diziye yalnızca bir kez erişmek istiyorsanız ve soldan sağa gidiyorsanız (bazı değerleri atlayarak), shiftdeğişken dizinler yerine kullanabilirsiniz .

# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
while [ $# -ge 1 ]; do
  echo "$1"
  shift && shift && shift
done

Hangi yaklaşımın daha hızlı olduğu, kabuğa ve elementlerin sayısına bağlıdır.

Başka bir olasılık string işlemenin kullanılmasıdır. Konumsal parametreleri kullanmama avantajına sahiptir, böylece bunları başka bir şey için kullanabilirsiniz. Büyük miktarda veri için daha yavaştır, ancak küçük miktarlarda veri için fark edilebilir bir fark yaratması mümkün değildir.

# List elements must be separated by a single space (not arbitrary whitespace)
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
while [ -n "$list" ]; do
  echo "${list% *}"
  case "$list" in *\ *\ *\ *) :;; *) break;; esac
  list="${list#* * * }"
done

" Öte yandan, büyük bir dize veya büyük miktarda dize üzerinde yinelenen bir kabuk döngüsünün, özel amaçlı bir aracın çağrılmasından daha yavaş olması muhtemeldir ", ancak bu araç içinde awk gibi bir döngüye sahipse ne olur? @ikkachu awk döngülerinin daha hızlı olduğunu söyler, ancak şunu söyler misiniz: <1000 alanla yinelemeli, daha hızlı döngülerin yararı, harici bir komut olduğundan (aynı görevi kabuğumda yapabildiğimi varsayarak) awk'yi çağırmanın maliyetinden daha ağır basmaz. sadece yerleşik komutların kullanımı ile döngüler)?
Levi Uzodike

@LeviUzodike Lütfen cevabımın ilk paragrafını tekrar okuyunuz.
Gilles 'SO- kötülükten vazgeç'

Ayrıca yerini alabilecek shift && shift && shiftile shift 3üçüncü örnekte - Kullandığınız kabuk bunu desteklemez sürece.
Joe,

2
@Joe Aslında hayır. shift 3Kalan argümanlar az olsaydı başarısız olur. Gibi bir şeye ihtiyacınız olacakif [ $# -gt 3 ]; then shift 3; else set --; fi
Gilles 'KÖTÜ-kötü olmayı'

3

awkharika bir seçim olduğunu eğer sen Awk script tüm işlem içini yapabilirsiniz. Aksi takdirde, sadece Awk çıktısını başka tesislere yöneltip performans kazancını mahvedersiniz awk.

bashBir dizinin üzerindeki yineleme de harikadır, listenin tamamını dizinin içine sığdırabilirseniz (ki bu modern kabukları için bir garantidir) ve dizi sözdizimi jimnastiğine aldırış etmiyorsanız.

Ancak, bir boru hattı yaklaşımı:

xargs -n3 <<< "$list" | while read -ra a; do echo $a; done | grep 9

Nerede:

  • xargs boşlukla ayrılmış listeyi, her biri yeni satırdan ayrılmış üçlü gruplar halinde gruplandırır
  • while read bu listeyi tüketir ve her grubun ilk sütununu çıkarır
  • grep ilk sütunu filtreler (orijinal listedeki her üçüncü konuma karşılık gelir)

Bence anlaşılabilirliği arttırıyor. İnsanlar bu araçların ne yaptığını zaten biliyor, bu yüzden soldan sağa okunması kolay ve ne olacağı hakkında sebep. Bu yaklaşım ayrıca adım uzunluğunu ( -n3) ve filtre şablonunu ( 9) da açıkça belgeler , bu nedenle değişkenliği kolaydır:

count=3
find=9
xargs -n "$count" <<< "$list" | while read -ra a; do echo $a; done | grep "$find"

"Verimlilik" ile ilgili sorular sorduğumuzda, "toplam ömür boyu verim" hakkında düşünmeye dikkat edin. Bu hesaplama, bakımcıların kodu çalışır halde tutma çabalarını içerir ve biz et torbaları, tüm operasyonda en az verimli makineleriz.


2

Belki bu?

cut -d' ' -f1,4,7,10,13 <<<$list
1 5 6 9 15

Üzgünüm, daha önce net değildim, ancak listenin uzunluğunu bilmeden bu konumlardaki sayıları alabilmeyi istedim. Ama teşekkürler, kesmeyi yapabileceğimi unuttum.
Levi Uzodike

1

Verimli olmak istiyorsanız kabuk komutlarını kullanmayın. Borular, yönlendirmeler, ikame vb. Programlarla kendinizi sınırlandırın. Bu yüzden xargsve parallelkamu hizmetleri var - çünkü döngüler yetersiz ve çok yavaş. Bash döngülerini yalnızca son çözüm olarak kullanın.

list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
if 
    <<<"$list" tr -d -s '[0-9 ]' | 
    tr -s ' ' | tr ' ' '\n' | 
    grep -q -x '9'
then
    found=true
else 
    found=false
fi
echo ${found} 

Fakat muhtemelen iyi ile biraz daha hızlı olmalısın awk.


Maalesef daha önce net değildim, ancak değerleri yalnızca listedeki konumlarına göre çıkarabilecek bir çözüm arıyordum. Orijinal listeyi bu şekilde yaptım çünkü istediğim değerlerin açık olmasını istedim.
Levi Uzodike

1

Bence en net çözüm (ve muhtemelen en çok performans gösteren) RS ve ORS awk değişkenlerini kullanmak:

awk -v RS=' ' -v ORS=' ' 'NR % 3 == 1' <<< "$list"

1
  1. GNU sed ve POSIX kabuk betiğini kullanarak :

    echo $(printf '%s\n' $list | sed -n '1~3p')
  2. Veya bash'in parametre değişikliği ile :

    echo $(sed -n '1~3p' <<< ${list// /$'\n'})
  3. Non GNU ( yani POSIX ) sedve bash:

    sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g' <<< "$list"

    Ya da daha çok, hem POSIX hem de sedkabuk betiğini kullanarak :

    echo "$list" | sed 's/\([^ ]* \)[^ ]* *[^ ]* */\1/g'

Bunlardan herhangi birinin çıktısı:

1 5 6 9 15
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.