Bash มีปัญหาประสิทธิภาพการทำงานโดยใช้รายการอาร์กิวเมนต์?


11

แก้ไขในทุบตี 5.0

พื้นหลัง

สำหรับพื้นหลัง (และความเข้าใจ (และพยายามหลีกเลี่ยง downvotes คำถามนี้ดูเหมือนจะดึงดูด)) ฉันจะอธิบายเส้นทางที่พาฉันไปที่ปัญหานี้ (ดีที่สุดที่ฉันจำได้ในสองเดือนต่อมา)

สมมติว่าคุณทำการทดสอบเชลล์สำหรับรายการอักขระ Unicode:

printf "$(printf '\\U%x ' {33..200})"

และมีอักขระ Unicode มากกว่า 1 ล้านตัวการทดสอบ 20,000 ตัวนั้นดูเหมือนจะไม่มาก
นอกจากนี้สมมติว่าคุณตั้งค่าอักขระเป็นอาร์กิวเมนต์ตำแหน่ง:

set -- $(printf "$(printf '\\U%x ' {33..20000})")

ด้วยความตั้งใจที่จะส่งผ่านตัวละครไปยังแต่ละฟังก์ชั่นเพื่อประมวลผลพวกเขาในรูปแบบที่แตกต่างกัน ดังนั้นฟังก์ชั่นควรมีรูปแบบtest1 "$@"หรือคล้ายกัน ตอนนี้ฉันรู้แล้วว่าความคิดนี้เลวร้ายแค่ไหน

ทีนี้สมมติว่าจำเป็นต้องใช้เวลา (n = 1,000) แต่ละวิธีแก้ปัญหาเพื่อค้นหาว่าดีกว่าภายใต้เงื่อนไขเช่นนี้คุณจะจบลงด้วยโครงสร้างที่คล้ายกับ:

#!/bin/bash --
TIMEFORMAT='real: %R'  # '%R %U %S'

set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000

test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }

main1(){ time for i in $(seq $n); do test1 "$@"; done
         time for i in $(seq $n); do test2 "$@"; done
         time for i in $(seq $n); do test3 "$@"; done
       }

main1 "$@"

ฟังก์ชั่นtest#นั้นทำง่ายมาก ๆ เพียงนำเสนอที่นี่
ต้นฉบับถูกลดระดับลงอย่างช้าๆเพื่อค้นหาว่ามีความล่าช้ามากแค่ไหน

สคริปต์ด้านบนใช้งานได้คุณสามารถเรียกใช้และเสียเวลาไม่กี่วินาทีในการทำน้อยมาก

ในกระบวนการลดความซับซ้อนของการค้นหาว่ามีความล่าช้าตรงไหน (และลดฟังก์ชั่นการทดสอบแต่ละอย่างแทบจะไม่เหลืออะไรเลยหลังจากการทดลองหลายครั้ง) ฉันตัดสินใจที่จะลบการส่งผ่านข้อโต้แย้งไปยังแต่ละฟังก์ชั่นการทดสอบ ปัจจัย 6 ไม่มาก

ในการลองด้วยตัวเองให้ลบ"$@"ฟังก์ชั่นทั้งหมดmain1(หรือทำสำเนา) และทดสอบอีกครั้ง (หรือทั้งสองอย่างmain1และคัดลอกmain2(พร้อมmain2 "$@")) เพื่อเปรียบเทียบ นี่คือโครงสร้างพื้นฐานด้านล่างในโพสต์ต้นฉบับ (OP)

แต่ฉันสงสัยว่าทำไมเปลือกถึงใช้เวลานานในการ "ไม่ทำอะไรเลย" ใช่เพียง "สองสามวินาที" แต่ยังคงทำไม?

นี่ทำให้ฉันทดสอบในกระสุนอื่น ๆ เพื่อค้นพบว่า bash มีปัญหานี้เท่านั้น
ลองksh ./script(สคริปต์เดียวกันข้างต้น)

สิ่งนี้นำไปสู่คำอธิบายนี้: การเรียกใช้ฟังก์ชั่น ( test#) โดยไม่มีการโต้แย้งใด ๆ เกิดความล่าช้าโดยอาร์กิวเมนต์ในพาเรนต์ ( main#) นี่คือคำอธิบายที่ตามมาและเป็นบทความต้นฉบับ (OP) ด้านล่าง

โพสต์ต้นฉบับ

โทรฟังก์ชั่น (ในทุบตี 4.4.12 (1) -release) จะทำอะไรf1(){ :; }เป็นพันครั้งช้ากว่า:แต่เฉพาะในกรณีที่มีข้อโต้แย้งที่กำหนดไว้ในปกครองเรียกฟังก์ชั่นทำไม?

#!/bin/bash
TIMEFORMAT='real: %R'

f1   () { :; }

f2   () {
   echo "                     args = $#";
   printf '1 function no   args yes '; time for ((i=1;i<$n;i++)); do  :   ; done 
   printf '2 function yes  args yes '; time for ((i=1;i<$n;i++)); do  f1  ; done
   set --
   printf '3 function yes  args no  '; time for ((i=1;i<$n;i++)); do  f1  ; done
   echo
        }

main1() { set -- $(seq $m)
          f2  ""
          f2 "$@"
        }

n=1000; m=20000; main1

ผลลัพธ์ของtest1:

                     args = 1
1 function no   args yes real:  0.013
2 function yes  args yes real:  0.024
3 function yes  args no  real:  0.020

                     args = 20000
1 function no   args yes real:  0.010
2 function yes  args yes real: 20.326
3 function yes  args no  real:  0.019

ไม่มีข้อโต้แย้งหรืออินพุตหรือเอาต์พุตที่ใช้ในฟังก์ชันf1ความล่าช้าของปัจจัยหนึ่งพัน (1,000) เป็นสิ่งที่ไม่คาดคิด 1


การขยายการทดสอบไปยังหลาย ๆ เชลล์ผลลัพธ์นั้นสอดคล้องกันเชลล์ส่วนใหญ่ไม่มีปัญหาหรือเกิดความล่าช้า (ใช้ n และ m เดียวกัน):

test2(){
          for sh in dash mksh ksh zsh bash b50sh
      do
          echo "$sh" >&2
#         \time -f '\t%E' seq "$m" >/dev/null
#         \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
          \time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
      done
}

test2

ผล:

dash
        0:00.01
        0:00.01
mksh
        0:00.01
        0:00.02
ksh
        0:00.01
        0:00.02
zsh
        0:00.02
        0:00.04
bash
        0:10.71
        0:30.03
b55sh             # --without-bash-malloc
        0:00.04
        0:17.11
b56sh             # RELSTATUS=release
        0:00.03
        0:15.47
b50sh             # Debug enabled (RELSTATUS=alpha)
        0:04.62
        xxxxxxx    More than a day ......

ยกเลิกการคอมเม้นท์อีกสองการทดสอบเพื่อยืนยันว่าไม่มีseqหรือการประมวลผลรายการอาร์กิวเมนต์เป็นแหล่งที่มาสำหรับความล่าช้า

1มันเป็นที่รู้จักกันว่าผ่านผลการค้นหาตามข้อโต้แย้งที่จะเพิ่มเวลาในการดำเนินการ ขอบคุณ@slm


3
บันทึกโดยเมตาเฟกต์ unix.meta.stackexchange.com/q/5021/3562
Joshua

คำตอบ:


9

คัดลอกมาจาก: ทำไมความล่าช้าในการวนซ้ำ ตามคำขอของคุณ:

คุณสามารถย่อกรณีทดสอบให้สั้นลงเพื่อ:

time bash -c 'f(){ :;};for i do f; done' {0..10000}

มันกำลังเรียกฟังก์ชั่นในขณะที่$@มีขนาดใหญ่ที่ดูเหมือนว่าจะเรียกมัน

ฉันเดาว่าจะเป็นเวลาที่ใช้ในการบันทึก$@ลงในสแต็กและเรียกคืนหลังจากนั้น อาจเป็นไปbashได้ว่ามันจะไร้ประสิทธิภาพโดยการทำซ้ำค่าทั้งหมดหรืออะไรทำนองนั้น เวลาดูเหมือนจะอยู่ใน o (n²)

คุณได้เวลาแบบเดียวกันในกระสุนอื่น ๆ สำหรับ:

time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}

นั่นคือที่ที่คุณส่งรายการของอาร์กิวเมนต์ไปยังฟังก์ชันและในเวลานี้เชลล์จำเป็นต้องคัดลอกค่า ( bashจบลงด้วยการเป็น 5 เท่าช้าสำหรับอันนั้น)

(ตอนแรกฉันคิดว่ามันแย่กว่าใน bash 5 (ปัจจุบันอยู่ใน alpha) แต่นั่นก็คือการดีบัก malloc ที่เปิดใช้งานในเวอร์ชันการพัฒนาตามที่ระบุไว้โดย @egmont พร้อมทั้งตรวจสอบว่าการกระจายของคุณสร้างได้bashอย่างไรถ้าคุณต้องการเปรียบเทียบ ระบบหนึ่งตัวอย่างเช่น Ubuntu ใช้--without-bash-malloc)


การดีบักถูกนำออกไปอย่างไร
ไอแซก

@isaac, ฉันไม่ได้โดยการเปลี่ยนRELSTATUS=alphaไปRELSTATUS=releaseในconfigureสคริปต์
Stéphane Chazelas

เพิ่มผลการทดสอบสำหรับทั้ง--without-bash-mallocและRELSTATUS=releaseไปยังผลลัพธ์ของคำถาม ที่ยังคงแสดงปัญหากับการโทรไปยัง f
ไอแซค

@Isaac ใช่ฉันเพิ่งพูดว่าฉันเคยผิดที่จะบอกว่ามันเลวร้ายกว่าใน bash5 มันไม่ได้เลวร้ายมันก็แย่เหมือนกัน
Stéphane Chazelas

ไม่มีก็ไม่ได้เลวร้าย Bash5 แก้ปัญหาการโทร:และปรับปรุงการโทรfเล็กน้อย ดูการกำหนดเวลา test2 ในคำถาม
ไอแซค
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.